Skip to content

Commit cc7383f

Browse files
committed
Use moving sum for GC duration.
1 parent 6011a45 commit cc7383f

File tree

1 file changed

+46
-76
lines changed

1 file changed

+46
-76
lines changed

core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/MemoryOverflowModel.java

Lines changed: 46 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
import java.util.Optional;
2727
import java.util.Set;
2828
import java.util.concurrent.ConcurrentHashMap;
29+
import java.util.concurrent.CopyOnWriteArrayList;
30+
31+
import javax.management.NotificationEmitter;
32+
import javax.management.openmbean.CompositeData;
2933

3034
import org.eclipse.rdf4j.common.io.FileUtil;
3135
import org.eclipse.rdf4j.model.IRI;
@@ -42,6 +46,9 @@
4246
import org.slf4j.Logger;
4347
import org.slf4j.LoggerFactory;
4448

49+
import com.sun.management.GarbageCollectionNotificationInfo;
50+
import com.sun.management.GcInfo;
51+
4552
/**
4653
* Model implementation that stores in a {@link LinkedHashModel} until more than 10KB statements are added and the
4754
* 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
7582

7683
private final SimpleValueFactory vf = SimpleValueFactory.getInstance();
7784

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+
78118
public MemoryOverflowModel(boolean verifyAdditions) {
79119
this.verifyAdditions = verifyAdditions;
80120
memory = new LinkedHashModel(LARGE_BLOCK);
@@ -148,7 +188,7 @@ public boolean addAll(Collection<? extends Statement> c) {
148188
if (buffer.size() >= 1024) {
149189
ret |= getDelegate().addAll(buffer);
150190
buffer.clear();
151-
innerCheckMemoryOverflow();
191+
checkMemoryOverflow();
152192
}
153193
}
154194
if (!buffer.isEmpty()) {
@@ -266,85 +306,15 @@ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundEx
266306
}
267307
}
268308

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() {
307310
if (disk == getDelegate()) {
308311
return;
309312
}
310313

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();
348318
}
349319
}
350320

0 commit comments

Comments
 (0)