JFR Memory Monitoring

May 7, 2026 · View on GitHub

The driver can publish DuckDB memory-usage statistics as a periodic Java Flight Recorder (JFR) event, so any JFR-aware tool (JMC, jfr CLI, async-profiler, Datadog/New Relic continuous profilers, …) can ingest DuckDB memory metrics alongside the rest of the JVM signal.

The feature is strictly opt-in per connection and is a silent no-op on JVMs without JFR support. Nothing is emitted unless the application both sets the JDBC property on a connection and has an active JFR recording that enables the event.

Enabling emission

Pass jdbc_jfr_memory_monitor=<component-id> when opening a connection:

Properties props = new Properties();
props.setProperty(DuckDBDriver.JDBC_JFR_MEMORY_MONITOR, "pricing-service");
Connection conn = DriverManager.getConnection("jdbc:duckdb:/tmp/pricing.db", props);

…or in the URL:

jdbc:duckdb:/tmp/pricing.db;jdbc_jfr_memory_monitor=pricing-service

The <component-id> is an arbitrary label chosen by the application; it is attached to every event as the component field so operators can attribute memory to logical components in dashboards and queries.

Rules:

Property valueEffect
absentno events emitted for this connection
empty stringno events emitted (same as absent)
non-empty stringevents emitted, tagged with the given value

The JDBC property is purely an opt-in switch and a label. It does not control whether JFR is actually recording, nor the sampling period — those are governed by JFR recording settings (see below).

Controlling period and enabled state

Sampling rate and enabled state are JFR-native settings. Configure them in a .jfc profile, via JMC, or programmatically:

try (Recording r = new Recording()) {
    r.enable("duckdb.MemoryUsage").withPeriod(Duration.ofSeconds(1));
    r.start();
    // ... application work ...
    r.stop();
    r.dump(Path.of("app.jfr"));
}

Equivalent .jfc snippet:

<event name="duckdb.MemoryUsage">
  <setting name="enabled">true</setting>
  <setting name="period">1 s</setting>
</event>

When no recording enables the event, the driver performs zero work — the DuckDB duckdb_memory() query is never issued.

Event schema

Event name: duckdb.MemoryUsage — one event per memory tag per JFR tick.

FieldTypeMeaning
componentStringApplication-supplied identifier (the JDBC property value).
tagStringDuckDB memory tag (e.g. BASE_TABLE, HASH_TABLE, ALLOCATOR).
dbAddresslongNative address of the DuckDB instance — stable per-instance id.
memoryUsageByteslongBytes currently allocated for this tag.
temporaryStorageByteslongBytes spilled to temporary storage for this tag.

Plus the standard JFR fields startTime, duration, eventThread (stack traces are disabled for this event).

Attribution model

The monitor is keyed on the native DuckDB instance address (exposed as dbAddress), not on the JDBC connection:

  • One sample stream per distinct DuckDB instance — no double-counting of shared memory.
  • component is captured from the first opted-in connection to an instance; later opted-in connections to the same instance do not change the label.
  • The monitor is created when the first opted-in connection opens and torn down when the last one closes; a subsequent getConnection starts a fresh monitor.

When two getConnection calls share an instance

URL / operationSame dbAddress?
jdbc:duckdb: (unnamed in-memory)No — fresh instance per call
jdbc:duckdb::memory:<name>Yes, when <name> matches
jdbc:duckdb:/path/to/file.dbYes, when the path matches
conn.duplicate()Yes, always

Connections that share an instance share a dbAddress and therefore a single component label (the one supplied by the first opted-in connection). For per-component attribution, open each component against a URL that yields a fresh instance — unnamed in-memory URLs are the simplest choice — and give each connection a unique jdbc_jfr_memory_monitor value.

Requirements

A JFR-capable JVM:

  • OpenJDK/HotSpot 11 and newer: JFR is included.
  • Amazon Corretto 8, OpenJDK 8u272+, and several other Java 8 distributions: JFR backport included (jdk.jfr package).
  • JVMs without jdk.jfr (e.g. some stripped Java 8 builds): the feature is a silent no-op; the jdbc_jfr_memory_monitor property is ignored and no classes that depend on jdk.jfr are loaded.

No additional JVM flags are required.

Inspecting a recording

With the jfr CLI bundled with the JDK:

jfr summary app.jfr | grep duckdb.MemoryUsage
jfr print --events duckdb.MemoryUsage app.jfr | head -12
jfr metadata app.jfr | sed -n '/class MemoryUsage/,/^}/p'

Or open app.jfr in JMC for an interactive view.

Manual verification

Two shell scripts reproduce the above end-to-end and are the recommended way to sanity-check a new build:

./scripts/verify-jfr.sh          # Java >= 9
./scripts/verify-jfr-java8.sh    # Java 8 (covers both JFR and no-JFR paths)

Switch the active JDK first (for example sdk u java 25.0.3-amzn or sdk u java 8.0.462-amzn). Each script builds any missing artifacts, runs the four test_jfr_memory_event* unit tests, and — on Java ≥ 9 — captures a live recording and verifies the event with jfr summary and jfr print. The Java 8 script additionally asserts the JFR-less fallback by running the driver with jfr.jar stripped from the bootclasspath.