14
14
import java .io .IOException ;
15
15
import java .io .ObjectInputStream ;
16
16
import java .io .ObjectOutputStream ;
17
+ import java .lang .management .GarbageCollectorMXBean ;
18
+ import java .lang .management .ManagementFactory ;
17
19
import java .nio .file .Files ;
18
- import java .util .Collection ;
19
20
import java .util .Iterator ;
21
+ import java .util .List ;
22
+ import java .util .Map ;
20
23
import java .util .Optional ;
21
24
import java .util .Set ;
25
+ import java .util .concurrent .ConcurrentHashMap ;
22
26
23
27
import org .eclipse .rdf4j .common .io .FileUtil ;
24
28
import org .eclipse .rdf4j .model .IRI ;
41
45
* estimated memory usage is more than the amount of free memory available. Once the threshold is cross this
42
46
* implementation seamlessly changes to a disk based {@link SailSourceModel}.
43
47
*/
44
- abstract class MemoryOverflowModel extends AbstractModel {
48
+ abstract class MemoryOverflowModel extends AbstractModel implements AutoCloseable {
45
49
46
50
private static final long serialVersionUID = 4119844228099208169L ;
47
51
48
52
private static final Runtime RUNTIME = Runtime .getRuntime ();
49
53
50
54
private static final int LARGE_BLOCK = 10000 ;
51
55
52
- // To reduce the chance of OOM we will always overflow once we get close to running out of memory even if we think
53
- // we have space for one more block. The limit is currently set at 32 MB
56
+ // To reduce the chance of OOM we will always overflow once we get close to running out of memory.
57
+ // The limit is currently set at 32 MB
54
58
private static final int MIN_AVAILABLE_MEM_BEFORE_OVERFLOWING = 32 * 1024 * 1024 ;
55
59
56
60
final Logger logger = LoggerFactory .getLogger (MemoryOverflowModel .class );
@@ -63,27 +67,17 @@ abstract class MemoryOverflowModel extends AbstractModel {
63
67
64
68
transient SailSourceModel disk ;
65
69
66
- private long baseline = 0 ;
70
+ private final boolean verifyAdditions ;
67
71
68
- private long maxBlockSize = 0 ;
72
+ private final SimpleValueFactory vf = SimpleValueFactory . getInstance () ;
69
73
70
- SimpleValueFactory vf = SimpleValueFactory .getInstance ();
71
-
72
- public MemoryOverflowModel () {
74
+ public MemoryOverflowModel (boolean verifyAdditions ) {
75
+ this .verifyAdditions = verifyAdditions ;
73
76
memory = new LinkedHashModel (LARGE_BLOCK );
74
77
}
75
78
76
- public MemoryOverflowModel (Model model ) {
77
- this (model .getNamespaces ());
78
- addAll (model );
79
- }
80
-
81
- public MemoryOverflowModel (Set <Namespace > namespaces , Collection <? extends Statement > c ) {
82
- this (namespaces );
83
- addAll (c );
84
- }
85
-
86
- public MemoryOverflowModel (Set <Namespace > namespaces ) {
79
+ public MemoryOverflowModel (Set <Namespace > namespaces , boolean verifyAdditions ) {
80
+ this .verifyAdditions = verifyAdditions ;
87
81
memory = new LinkedHashModel (namespaces , LARGE_BLOCK );
88
82
}
89
83
@@ -227,40 +221,71 @@ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundEx
227
221
}
228
222
}
229
223
224
+ static class GcInfo {
225
+ long count ;
226
+ long time ;
227
+ }
228
+
229
+ private final Map <String , GcInfo > prevGcInfo = new ConcurrentHashMap <>();
230
+
231
+ private synchronized boolean highGcLoad () {
232
+ boolean highLoad = false ;
233
+
234
+ // get all garbage collector MXBeans.
235
+ List <GarbageCollectorMXBean > gcBeans = ManagementFactory .getGarbageCollectorMXBeans ();
236
+ for (GarbageCollectorMXBean gcBean : gcBeans ) {
237
+ long count = gcBean .getCollectionCount ();
238
+ long time = gcBean .getCollectionTime ();
239
+
240
+ GcInfo prevInfo = prevGcInfo .get (gcBean .getName ());
241
+ if (prevInfo != null ) {
242
+ long countDiff = count - prevInfo .count ;
243
+ long timeDiff = time - prevInfo .time ;
244
+ if (countDiff != 0 ) {
245
+ double gcLoad = (double ) timeDiff / countDiff ;
246
+ // TODO find good threshold
247
+ if (gcLoad > 30 ) {
248
+ highLoad = true ;
249
+ }
250
+ }
251
+ } else {
252
+ prevInfo = new GcInfo ();
253
+ prevGcInfo .put (gcBean .getName (), prevInfo );
254
+ }
255
+ prevInfo .count = count ;
256
+ prevInfo .time = time ;
257
+ }
258
+ return highLoad ;
259
+ }
260
+
230
261
private synchronized void checkMemoryOverflow () {
231
262
if (disk == null ) {
232
263
int size = size ();
233
264
if (size >= LARGE_BLOCK && size % LARGE_BLOCK == 0 ) {
234
- // maximum heap size the JVM can allocate
235
- long maxMemory = RUNTIME .maxMemory ();
265
+ boolean overflow = highGcLoad ();
266
+ if (!overflow ) {
267
+ // maximum heap size the JVM can allocate
268
+ long maxMemory = RUNTIME .maxMemory ();
236
269
237
- // total currently allocated JVM memory
238
- long totalMemory = RUNTIME .totalMemory ();
270
+ // total currently allocated JVM memory
271
+ long totalMemory = RUNTIME .totalMemory ();
239
272
240
- // amount of memory free in the currently allocated JVM memory
241
- long freeMemory = RUNTIME .freeMemory ();
273
+ // amount of memory free in the currently allocated JVM memory
274
+ long freeMemory = RUNTIME .freeMemory ();
242
275
243
- // estimated memory used
244
- long used = totalMemory - freeMemory ;
276
+ // estimated memory used
277
+ long used = totalMemory - freeMemory ;
245
278
246
- // amount of memory the JVM can still allocate from the OS (upper boundary is the max heap)
247
- long freeToAllocateMemory = maxMemory - used ;
279
+ // amount of memory the JVM can still allocate from the OS (upper boundary is the max heap)
280
+ long freeToAllocateMemory = maxMemory - used ;
248
281
249
- if (baseline > 0 ) {
250
- long blockSize = used - baseline ;
251
- if (blockSize > maxBlockSize ) {
252
- maxBlockSize = blockSize ;
253
- }
254
-
255
- // Sync if either the estimated size of the next block is larger than remaining memory, or
256
- // if less than 15% of the heap is still free (this last condition to avoid GC overhead limit)
257
- if (freeToAllocateMemory < MIN_AVAILABLE_MEM_BEFORE_OVERFLOWING ||
258
- freeToAllocateMemory < Math .min (0.15 * maxMemory , maxBlockSize )) {
259
- logger .debug ("syncing at {} triples. max block size: {}" , size , maxBlockSize );
260
- overflowToDisk ();
261
- }
282
+ // try to prevent OOM
283
+ overflow = freeToAllocateMemory < MIN_AVAILABLE_MEM_BEFORE_OVERFLOWING ;
284
+ }
285
+ if (overflow ) {
286
+ logger .debug ("syncing at {} triples." , size );
287
+ overflowToDisk ();
262
288
}
263
- baseline = used ;
264
289
}
265
290
}
266
291
}
@@ -271,26 +296,7 @@ private synchronized void overflowToDisk() {
271
296
dataDir = Files .createTempDirectory ("model" ).toFile ();
272
297
logger .debug ("memory overflow using temp directory {}" , dataDir );
273
298
store = createSailStore (dataDir );
274
- disk = new SailSourceModel (store ) {
275
-
276
- @ Override
277
- protected void finalize () throws Throwable {
278
- logger .debug ("finalizing {}" , dataDir );
279
- if (disk == this ) {
280
- try {
281
- store .close ();
282
- } catch (SailException e ) {
283
- logger .error (e .toString (), e );
284
- } finally {
285
- FileUtil .deleteDir (dataDir );
286
- dataDir = null ;
287
- store = null ;
288
- disk = null ;
289
- }
290
- }
291
- super .finalize ();
292
- }
293
- };
299
+ disk = new SailSourceModel (store , verifyAdditions );
294
300
disk .addAll (memory );
295
301
memory = new LinkedHashModel (memory .getNamespaces (), LARGE_BLOCK );
296
302
logger .debug ("overflow synced to disk" );
@@ -299,4 +305,22 @@ protected void finalize() throws Throwable {
299
305
logger .error ("Error while writing to overflow directory " + path , e );
300
306
}
301
307
}
308
+
309
+ @ Override
310
+ public void close () throws IOException {
311
+ if (disk != null ) {
312
+ logger .debug ("closing {}" , dataDir );
313
+ disk .close ();
314
+ try {
315
+ store .close ();
316
+ } catch (SailException e ) {
317
+ logger .error (e .toString (), e );
318
+ } finally {
319
+ FileUtil .deleteDir (dataDir );
320
+ dataDir = null ;
321
+ store = null ;
322
+ disk = null ;
323
+ }
324
+ }
325
+ }
302
326
}
0 commit comments