88 *
99 * SPDX-License-Identifier: BSD-3-Clause
1010 *******************************************************************************/
11+
1112package org .eclipse .rdf4j .sail .lmdb ;
1213
1314import java .io .File ;
1617import java .io .ObjectOutputStream ;
1718import java .nio .file .Files ;
1819import java .util .Collection ;
20+ import java .util .HashSet ;
1921import java .util .Iterator ;
2022import java .util .Optional ;
2123import java .util .Set ;
3234import org .eclipse .rdf4j .model .impl .LinkedHashModel ;
3335import org .eclipse .rdf4j .model .impl .SimpleValueFactory ;
3436import org .eclipse .rdf4j .sail .SailException ;
35- import org .eclipse .rdf4j .sail .base .SailStore ;
3637import org .slf4j .Logger ;
3738import org .slf4j .LoggerFactory ;
3839
@@ -47,21 +48,24 @@ abstract class MemoryOverflowModel extends AbstractModel {
4748
4849 private static final Runtime RUNTIME = Runtime .getRuntime ();
4950
50- private static final int LARGE_BLOCK = 10000 ;
51+ private static final int LARGE_BLOCK = 5 * 1024 ;
52+
53+ private static volatile boolean overflow ;
5154
5255 // 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
54- private static final int MIN_AVAILABLE_MEM_BEFORE_OVERFLOWING = 32 * 1024 * 1024 ;
56+ // we have space for one more block. The limit is currently set at 32 MB for small heaps and 128 MB for large heaps.
57+ private static final int MIN_AVAILABLE_MEM_BEFORE_OVERFLOWING = RUNTIME .maxMemory () >= 1024 ? 128 * 1024 * 1024
58+ : 32 * 1024 * 1024 ;
5559
5660 final Logger logger = LoggerFactory .getLogger (MemoryOverflowModel .class );
5761
58- private LinkedHashModel memory ;
62+ private volatile LinkedHashModel memory ;
5963
60- transient File dataDir ;
64+ private transient File dataDir ;
6165
62- transient SailStore store ;
66+ private transient LmdbSailStore store ;
6367
64- transient SailSourceModel disk ;
68+ private transient volatile SailSourceModel disk ;
6569
6670 private long baseline = 0 ;
6771
@@ -70,7 +74,7 @@ abstract class MemoryOverflowModel extends AbstractModel {
7074 SimpleValueFactory vf = SimpleValueFactory .getInstance ();
7175
7276 public MemoryOverflowModel () {
73- memory = new LinkedHashModel (LARGE_BLOCK );
77+ memory = new LinkedHashModel (LARGE_BLOCK * 2 );
7478 }
7579
7680 public MemoryOverflowModel (Model model ) {
@@ -137,6 +141,33 @@ public boolean add(Statement st) {
137141 return getDelegate ().add (st );
138142 }
139143
144+ @ Override
145+ public boolean addAll (Collection <? extends Statement > c ) {
146+ checkMemoryOverflow ();
147+ if (disk != null || c .size () <= 1024 ) {
148+ return getDelegate ().addAll (c );
149+ } else {
150+ boolean ret = false ;
151+ HashSet <Statement > buffer = new HashSet <>();
152+ for (Statement st : c ) {
153+ buffer .add (st );
154+ if (buffer .size () >= 1024 ) {
155+ ret |= getDelegate ().addAll (buffer );
156+ buffer .clear ();
157+ innerCheckMemoryOverflow ();
158+ }
159+ }
160+ if (!buffer .isEmpty ()) {
161+ ret |= getDelegate ().addAll (buffer );
162+ buffer .clear ();
163+ }
164+
165+ return ret ;
166+
167+ }
168+
169+ }
170+
140171 @ Override
141172 public boolean remove (Resource subj , IRI pred , Value obj , Resource ... contexts ) {
142173 return getDelegate ().remove (subj , pred , obj , contexts );
@@ -191,13 +222,27 @@ public synchronized void removeTermIteration(Iterator<Statement> iter, Resource
191222 }
192223 }
193224
194- protected abstract SailStore createSailStore (File dataDir ) throws IOException , SailException ;
225+ protected abstract LmdbSailStore createSailStore (File dataDir ) throws IOException , SailException ;
195226
196- synchronized Model getDelegate () {
197- if (disk == null ) {
227+ private Model getDelegate () {
228+ var memory = this .memory ;
229+ if (memory != null ) {
198230 return memory ;
231+ } else {
232+ var disk = this .disk ;
233+ if (disk != null ) {
234+ return disk ;
235+ }
236+ synchronized (this ) {
237+ if (this .memory != null ) {
238+ return this .memory ;
239+ }
240+ if (this .disk != null ) {
241+ return this .disk ;
242+ }
243+ throw new IllegalStateException ("MemoryOverflowModel is in an inconsistent state" );
244+ }
199245 }
200- return disk ;
201246 }
202247
203248 private void writeObject (ObjectOutputStream s ) throws IOException {
@@ -227,51 +272,84 @@ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundEx
227272 }
228273 }
229274
230- private synchronized void checkMemoryOverflow () {
231- if (disk == null ) {
232- int size = size ();
233- if (size >= LARGE_BLOCK && size % LARGE_BLOCK == 0 ) {
234- // maximum heap size the JVM can allocate
235- long maxMemory = RUNTIME .maxMemory ();
275+ private void checkMemoryOverflow () {
276+ if (disk == getDelegate ()) {
277+ return ;
278+ }
236279
237- // total currently allocated JVM memory
238- long totalMemory = RUNTIME .totalMemory ();
280+ if (overflow ) {
281+ innerCheckMemoryOverflow ();
282+ }
283+ int size = size () + 1 ;
284+ if (size >= LARGE_BLOCK && size % LARGE_BLOCK == 0 ) {
285+ innerCheckMemoryOverflow ();
286+ }
239287
240- // amount of memory free in the currently allocated JVM memory
241- long freeMemory = RUNTIME .freeMemory ();
288+ }
242289
243- // estimated memory used
244- long used = totalMemory - freeMemory ;
290+ private void innerCheckMemoryOverflow () {
291+ if (disk == getDelegate ()) {
292+ return ;
293+ }
245294
246- // amount of memory the JVM can still allocate from the OS (upper boundary is the max heap)
247- long freeToAllocateMemory = maxMemory - used ;
295+ // maximum heap size the JVM can allocate
296+ long maxMemory = RUNTIME . maxMemory () ;
248297
249- if (baseline > 0 ) {
250- long blockSize = used - baseline ;
251- if (blockSize > maxBlockSize ) {
252- maxBlockSize = blockSize ;
253- }
298+ // total currently allocated JVM memory
299+ long totalMemory = RUNTIME .totalMemory ();
254300
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- }
301+ // amount of memory free in the currently allocated JVM memory
302+ long freeMemory = RUNTIME .freeMemory ();
303+
304+ // estimated memory used
305+ long used = totalMemory - freeMemory ;
306+
307+ // amount of memory the JVM can still allocate from the OS (upper boundary is the max heap)
308+ long freeToAllocateMemory = maxMemory - used ;
309+
310+ if (baseline > 0 ) {
311+ long blockSize = used - baseline ;
312+ if (blockSize > maxBlockSize ) {
313+ maxBlockSize = blockSize ;
314+ }
315+ if (overflow && freeToAllocateMemory < MIN_AVAILABLE_MEM_BEFORE_OVERFLOWING * 2 ) {
316+ // stricter memory requirements to not overflow if other models are overflowing
317+ logger .debug ("syncing at {} triples. max block size: {}" , size (), maxBlockSize );
318+ overflowToDisk ();
319+ System .gc ();
320+ } else if (freeToAllocateMemory < MIN_AVAILABLE_MEM_BEFORE_OVERFLOWING ||
321+ freeToAllocateMemory < Math .min (0.15 * maxMemory , maxBlockSize )) {
322+ // Sync if either the estimated size of the next block is larger than remaining memory, or
323+ // if less than 15% of the heap is still free (this last condition to avoid GC overhead limit)
324+
325+ logger .debug ("syncing at {} triples. max block size: {}" , size (), maxBlockSize );
326+ overflowToDisk ();
327+ System .gc ();
328+ } else {
329+ if (overflow ) {
330+ overflow = false ;
262331 }
263- baseline = used ;
264332 }
265333 }
334+ baseline = used ;
266335 }
267336
268337 private synchronized void overflowToDisk () {
338+ overflow = true ;
339+ if (memory == null ) {
340+ assert disk != null ;
341+ return ;
342+ }
343+
269344 try {
345+ LinkedHashModel memory = this .memory ;
346+ this .memory = null ;
347+
270348 assert disk == null ;
271349 dataDir = Files .createTempDirectory ("model" ).toFile ();
272350 logger .debug ("memory overflow using temp directory {}" , dataDir );
273351 store = createSailStore (dataDir );
274- disk = new SailSourceModel (store ) {
352+ disk = new SailSourceModel (store , memory ) {
275353
276354 @ Override
277355 protected void finalize () throws Throwable {
@@ -291,8 +369,7 @@ protected void finalize() throws Throwable {
291369 super .finalize ();
292370 }
293371 };
294- disk .addAll (memory );
295- memory = new LinkedHashModel (memory .getNamespaces (), LARGE_BLOCK );
372+
296373 logger .debug ("overflow synced to disk" );
297374 } catch (IOException | SailException e ) {
298375 String path = dataDir != null ? dataDir .getAbsolutePath () : "(unknown)" ;
0 commit comments