Summary
org.rocksdb.RocksDB allocates a ReadOptions in its constructor and never releases it:
private final ReadOptions defaultReadOptions_; // assigned in RocksDB(long)
RocksDB.close() closes the owned column family handles, calls closeDatabase(nativeHandle_),
and disposes its own handle — but never references defaultReadOptions_. AbstractNativeReference
has no finalizer, and there is no Cleaner or phantom-reference cleanup registered anywhere in the
AbstractNativeReference / AbstractImmutableNativeReference / RocksObject hierarchy, so an
explicit Java close() is the only path to disposeInternal(). That call never comes.
Every RocksDB instance opened leaks one native ReadOptions for the lifetime of the JVM.
Garbage collecting the Java wrapper does not help.
Affected versions
Verified by decompiling released jars from Maven Central:
| Version |
defaultReadOptions_ |
Released by close() |
| 7.9.2, 8.0.0, 8.6.7, 8.9.1 |
absent |
n/a |
| 8.10.0 |
present |
no |
| 8.10.2, 8.11.3, 8.11.5 |
present |
no |
| 9.0.0, 9.3.1, 9.7.3 |
present |
no |
| 10.1.3 |
present |
no |
| 10.10.1, 10.10.1.1 (latest) |
present |
no |
Introduced in 8.10.0 — the same change that switched newIterator() from native iterator(J)
to iterator(JJJ), so an explicit column family handle and read options handle are passed through
to JNI. close() was not updated to match, and has not been since.
In 10.10.1 the field is referenced from exactly four places: its declaration, the RocksDB(long)
constructor, newIterator(), and newIterator(ColumnFamilyHandle). Nothing closes it.
Reproduction
Self-contained, no dependencies beyond rocksdbjni:
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.util.HashSet;
import java.util.Set;
import org.rocksdb.Options;
import org.rocksdb.ReadOptions;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksObject;
public class ReadOptionsLeak {
public static void main(final String[] args) throws Exception {
RocksDB.loadLibrary();
final Field f = RocksDB.class.getDeclaredField("defaultReadOptions_");
f.setAccessible(true);
final Field h = RocksObject.class.getDeclaredField("nativeHandle_");
h.setAccessible(true);
final String dir = Files.createTempDirectory("leak-").toString();
// 1. after close(), the DB handle is released but its ReadOptions is not
try (Options o = new Options().setCreateIfMissing(true)) {
final RocksDB db = RocksDB.open(o, dir);
final ReadOptions defaults = (ReadOptions) f.get(db);
db.close();
System.out.println("db owns=" + db.isOwningHandle()
+ " defaultReadOptions_ owns=" + defaults.isOwningHandle());
}
// 2. the native block is never returned to the allocator: with nothing
// freed, no address can ever be reused.
for (final boolean fix : new boolean[] {false, true}) {
final Set<Long> addrs = new HashSet<>();
for (int i = 0; i < 2000; i++) {
try (Options o = new Options().setCreateIfMissing(true)) {
final RocksDB db = RocksDB.open(o, dir);
final ReadOptions defaults = (ReadOptions) f.get(db);
addrs.add(h.getLong(defaults));
db.close();
if (fix) defaults.close();
}
}
System.out.println((fix ? "with close()" : "as-is")
+ ": " + addrs.size() + " distinct addresses / 2000 cycles");
}
}
}
Actual output (rocksdbjni 10.10.1, JDK 17, macOS/aarch64)
db owns=false defaultReadOptions_ owns=true
as-is: 2000 distinct addresses / 2000 cycles
with close(): 2 distinct addresses / 2000 cycles
Summary
org.rocksdb.RocksDBallocates aReadOptionsin its constructor and never releases it:RocksDB.close()closes the owned column family handles, callscloseDatabase(nativeHandle_),and disposes its own handle — but never references
defaultReadOptions_.AbstractNativeReferencehas no finalizer, and there is no
Cleaneror phantom-reference cleanup registered anywhere in theAbstractNativeReference/AbstractImmutableNativeReference/RocksObjecthierarchy, so anexplicit Java
close()is the only path todisposeInternal(). That call never comes.Every
RocksDBinstance opened leaks one nativeReadOptionsfor the lifetime of the JVM.Garbage collecting the Java wrapper does not help.
Affected versions
Verified by decompiling released jars from Maven Central:
defaultReadOptions_close()Introduced in 8.10.0 — the same change that switched
newIterator()from nativeiterator(J)to
iterator(JJJ), so an explicit column family handle and read options handle are passed throughto JNI.
close()was not updated to match, and has not been since.In 10.10.1 the field is referenced from exactly four places: its declaration, the
RocksDB(long)constructor,
newIterator(), andnewIterator(ColumnFamilyHandle). Nothing closes it.Reproduction
Self-contained, no dependencies beyond rocksdbjni:
Actual output (rocksdbjni 10.10.1, JDK 17, macOS/aarch64)