Remove finalize() methods (1.5)#535
Merged
staticlibs merged 1 commit intoduckdb:v1.5-variegatafrom Jan 22, 2026
Merged
Conversation
This is a backport of the PR duckdb#533 to `v1.5-variegata` stable branch. Problem ------- In highly parallel environments, creating DuckDBResultSet and DuckDBPreparedStatement objects causes severe mutex contention on the global java.lang.ref.Finalizer lock. Profiling with Pyroscope showed that ~48% of mutex contentions originated from: DuckDBResultSet.<init> → Object.<init> → Finalizer.register → Finalizer.<init> [GLOBAL LOCK] When a class overrides finalize(), the JVM registers every new instance with the Finalizer system using a global lock. In applications executing many concurrent queries (e.g., using ZIO, Akka, or thread pools), this single lock becomes a severe bottleneck, significantly degrading throughput. Background ---------- The finalize() methods were added in April 2020 (commit 934af9f) as a safety net to release native JNI resources if users forgot to call close(). However, finalize() was deprecated in Java 9 (2017) due to: - Unpredictable execution timing (GC-dependent) - Performance overhead (extra GC cycles for weak reachability) - Global lock contention in the Finalizer registration - Single-threaded Finalizer thread becoming a bottleneck The modern replacement (java.lang.ref.Cleaner) was introduced in Java 9, but this driver targets Java 8 compatibility. Solution -------- Remove finalize() from all four classes that had it: - DuckDBConnection - DuckDBPreparedStatement - DuckDBResultSet - DuckDBSingleValueAppender All these classes already implement AutoCloseable with proper close() methods. Users should use try-with-resources: try (Connection conn = DriverManager.getConnection("jdbc:duckdb:"); PreparedStatement stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery()) { // ... } This is standard JDBC best practice and ensures deterministic resource cleanup without relying on GC finalization. Impact ------ - Eliminates Finalizer lock contention entirely - Improves throughput in high-concurrency scenarios - No behavior change for users who properly close resources - Users who relied on finalize() for cleanup will now leak resources if they don't call close() (but finalize() was never guaranteed to run anyway)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is a backport of the PR #533 to
v1.5-variegatastable branch.Problem
In highly parallel environments, creating DuckDBResultSet and DuckDBPreparedStatement objects causes severe mutex contention on the global java.lang.ref.Finalizer lock. Profiling with Pyroscope showed that ~48% of mutex contentions originated from:
DuckDBResultSet.
→ Object.
→ Finalizer.register
→ Finalizer. [GLOBAL LOCK]
When a class overrides finalize(), the JVM registers every new instance with the Finalizer system using a global lock. In applications executing many concurrent queries (e.g., using ZIO, Akka, or thread pools), this single lock becomes a severe bottleneck, significantly degrading throughput.
Background
The finalize() methods were added in April 2020 (commit 934af9f) as a safety net to release native JNI resources if users forgot to call close(). However, finalize() was deprecated in Java 9 (2017) due to:
The modern replacement (java.lang.ref.Cleaner) was introduced in Java 9, but this driver targets Java 8 compatibility.
Solution
Remove finalize() from all four classes that had it:
All these classes already implement AutoCloseable with proper close() methods. Users should use try-with-resources:
try (Connection conn = DriverManager.getConnection("jdbc:duckdb:");
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
// ...
}
This is standard JDBC best practice and ensures deterministic resource cleanup without relying on GC finalization.
Impact