|
26 | 26 | import java.util.Optional;
|
27 | 27 | import java.util.Set;
|
28 | 28 | import java.util.concurrent.ConcurrentHashMap;
|
| 29 | +import java.util.concurrent.CopyOnWriteArrayList; |
| 30 | + |
| 31 | +import javax.management.NotificationEmitter; |
| 32 | +import javax.management.openmbean.CompositeData; |
29 | 33 |
|
30 | 34 | import org.eclipse.rdf4j.common.io.FileUtil;
|
31 | 35 | import org.eclipse.rdf4j.model.IRI;
|
|
42 | 46 | import org.slf4j.Logger;
|
43 | 47 | import org.slf4j.LoggerFactory;
|
44 | 48 |
|
| 49 | +import com.sun.management.GarbageCollectionNotificationInfo; |
| 50 | +import com.sun.management.GcInfo; |
| 51 | + |
45 | 52 | /**
|
46 | 53 | * Model implementation that stores in a {@link LinkedHashModel} until more than 10KB statements are added and the
|
47 | 54 | * estimated memory usage is more than the amount of free memory available. Once the threshold is cross this
|
@@ -75,6 +82,39 @@ abstract class MemoryOverflowModel extends AbstractModel implements AutoCloseabl
|
75 | 82 |
|
76 | 83 | private final SimpleValueFactory vf = SimpleValueFactory.getInstance();
|
77 | 84 |
|
| 85 | + private static volatile boolean highGcLoad = false; |
| 86 | + private static volatile long lastGcUpdate; |
| 87 | + private static volatile long gcSum; |
| 88 | + private static volatile List<GcInfo> gcInfos = new CopyOnWriteArrayList<>(); |
| 89 | + static { |
| 90 | + List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); |
| 91 | + for (GarbageCollectorMXBean gcBean : gcBeans) { |
| 92 | + NotificationEmitter emitter = (NotificationEmitter) gcBean; |
| 93 | + emitter.addNotificationListener((notification, o) -> { |
| 94 | + while (! gcInfos.isEmpty()) { |
| 95 | + if (System.currentTimeMillis() - gcInfos.get(0).getEndTime() > 5000) { |
| 96 | + gcSum -= gcInfos.remove(0).getDuration(); |
| 97 | + } else { |
| 98 | + break; |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + // extract garbage collection information from notification. |
| 103 | + GarbageCollectionNotificationInfo gcNotificationInfo = GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData()); |
| 104 | + GcInfo gcInfo = gcNotificationInfo.getGcInfo(); |
| 105 | + gcInfos.add(gcInfo); |
| 106 | + gcSum += gcInfo.getDuration(); |
| 107 | + System.out.println("gcSum: " + gcSum); |
| 108 | + if (gcSum > 1000 || gcInfos.size() > 4) { |
| 109 | + highGcLoad = true; |
| 110 | + lastGcUpdate = System.currentTimeMillis(); |
| 111 | + } else if (System.currentTimeMillis() - lastGcUpdate > 10000) { |
| 112 | + highGcLoad = false; |
| 113 | + } |
| 114 | + }, null, null); |
| 115 | + } |
| 116 | + } |
| 117 | + |
78 | 118 | public MemoryOverflowModel(boolean verifyAdditions) {
|
79 | 119 | this.verifyAdditions = verifyAdditions;
|
80 | 120 | memory = new LinkedHashModel(LARGE_BLOCK);
|
@@ -148,7 +188,7 @@ public boolean addAll(Collection<? extends Statement> c) {
|
148 | 188 | if (buffer.size() >= 1024) {
|
149 | 189 | ret |= getDelegate().addAll(buffer);
|
150 | 190 | buffer.clear();
|
151 |
| - innerCheckMemoryOverflow(); |
| 191 | + checkMemoryOverflow(); |
152 | 192 | }
|
153 | 193 | }
|
154 | 194 | if (!buffer.isEmpty()) {
|
@@ -266,85 +306,15 @@ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundEx
|
266 | 306 | }
|
267 | 307 | }
|
268 | 308 |
|
269 |
| - static class GcInfo { |
270 |
| - long count; |
271 |
| - long time; |
272 |
| - } |
273 |
| - |
274 |
| - private final Map<String, GcInfo> prevGcInfo = new ConcurrentHashMap<>(); |
275 |
| - |
276 |
| - private synchronized boolean highGcLoad() { |
277 |
| - boolean highLoad = false; |
278 |
| - |
279 |
| - // get all garbage collector MXBeans. |
280 |
| - List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans(); |
281 |
| - for (GarbageCollectorMXBean gcBean : gcBeans) { |
282 |
| - long count = gcBean.getCollectionCount(); |
283 |
| - long time = gcBean.getCollectionTime(); |
284 |
| - |
285 |
| - GcInfo prevInfo = prevGcInfo.get(gcBean.getName()); |
286 |
| - if (prevInfo != null) { |
287 |
| - long countDiff = count - prevInfo.count; |
288 |
| - long timeDiff = time - prevInfo.time; |
289 |
| - if (countDiff != 0) { |
290 |
| - double gcLoad = (double) timeDiff / countDiff; |
291 |
| - // TODO find good threshold |
292 |
| - if (gcLoad > 100) { |
293 |
| - highLoad = true; |
294 |
| - } |
295 |
| - } |
296 |
| - } else { |
297 |
| - prevInfo = new GcInfo(); |
298 |
| - prevGcInfo.put(gcBean.getName(), prevInfo); |
299 |
| - } |
300 |
| - prevInfo.count = count; |
301 |
| - prevInfo.time = time; |
302 |
| - } |
303 |
| - return highLoad; |
304 |
| - } |
305 |
| - |
306 |
| - private void checkMemoryOverflow() { |
| 309 | + private synchronized void checkMemoryOverflow() { |
307 | 310 | if (disk == getDelegate()) {
|
308 | 311 | return;
|
309 | 312 | }
|
310 | 313 |
|
311 |
| - if (overflow) { |
312 |
| - innerCheckMemoryOverflow(); |
313 |
| - } |
314 |
| - int size = size() + 1; |
315 |
| - if (size >= LARGE_BLOCK && size % LARGE_BLOCK == 0) { |
316 |
| - if (highGcLoad()) { |
317 |
| - logger.debug("syncing at {} triples due to high gc load.", size); |
318 |
| - overflowToDisk(); |
319 |
| - } else { |
320 |
| - innerCheckMemoryOverflow(); |
321 |
| - } |
322 |
| - } |
323 |
| - } |
324 |
| - |
325 |
| - private synchronized void innerCheckMemoryOverflow() { |
326 |
| - if (disk == null) { |
327 |
| - // maximum heap size the JVM can allocate |
328 |
| - long maxMemory = RUNTIME.maxMemory(); |
329 |
| - |
330 |
| - // total currently allocated JVM memory |
331 |
| - long totalMemory = RUNTIME.totalMemory(); |
332 |
| - |
333 |
| - // amount of memory free in the currently allocated JVM memory |
334 |
| - long freeMemory = RUNTIME.freeMemory(); |
335 |
| - |
336 |
| - // estimated memory used |
337 |
| - long used = totalMemory - freeMemory; |
338 |
| - |
339 |
| - // amount of memory the JVM can still allocate from the OS (upper boundary is the max heap) |
340 |
| - long freeToAllocateMemory = maxMemory - used; |
341 |
| - |
342 |
| - // try to prevent OOM |
343 |
| - overflow = freeToAllocateMemory < MIN_AVAILABLE_MEM_BEFORE_OVERFLOWING; |
344 |
| - if (overflow) { |
345 |
| - logger.debug("syncing at {} triples.", size()); |
346 |
| - overflowToDisk(); |
347 |
| - } |
| 314 | + if (highGcLoad) { |
| 315 | + logger.debug("syncing at {} triples due to gc load"); |
| 316 | + overflowToDisk(); |
| 317 | + System.gc(); |
348 | 318 | }
|
349 | 319 | }
|
350 | 320 |
|
|
0 commit comments