>_ Johannes Bechberger

jvm tools

A collection of tools and libraries for diagnosing, profiling, and understanding running Java applications. Built at SAP as part of the SapMachine team, open-sourced for the broader JVM community.

Live JVM Inspection

jstall ready
v0.7.1 ·2026-05-17

Instant JVM insight — deadlocks, hot threads, flamegraphs — no agent needed.

You need instant insight into a running JVM without attaching a full profiler

Details
cf-cli-java-plugin ready
v4.0.2 ·2026-01-14

Trigger heap dumps, thread dumps, and profiles from the CF CLI — no SSH needed.

You run Java apps on Cloud Foundry and need heap dumps, thread dumps, or CPU profiles

Details

JFR / Profiling

firefox-profiler ready

Fork of Firefox Profiler that opens JFR and .cjfr recordings natively in the browser — drag-and-drop, no server.

You want to open a JFR or condensed-data (.cjfr) recording directly in the browser without running a local server

Details
jfr-query experimental

SQL notebook for JFR recordings and JDK GC logs — drag in a file, write SQL, get charts.

You want to run ad-hoc SQL queries over JFR event data or JDK unified log files (GC logs)

Details
condensed-data experimental

CLI tool and agent for compact long-term JFR event storage — significantly smaller than raw .jfr.

You need long-term storage of JFR recordings with minimal disk usage

Details
jfrevents ready
v0.7 ·2025-09-26

Reference site for every JFR event type across JDK versions.

You need to know what fields a JFR event has across JDK versions

Details
tiny-profiler demo

Educational Java CPU profiler — method table and HTML flame graph, no native code.

You want to understand how a sampling profiler works from first principles

Details

Heap Dump Analysis

hprof-analyzer ready

Fast Rust-based heap dump analyser — handles >10 GiB dumps, OQL shell, browser UI.

You need to analyse a heap dump without Eclipse MAT's memory overhead

Details
hprof-redact poc
v0.3.0 ·2026-06-24

Deprecated — use `hprof-analyzer redact` instead. Library available for programmatic pipeline use.

You are building tooling that needs to redact heap dumps programmatically (library use)

Details

Crash / Log Diagnostics

jhserr experimental
v0.1.0 ·2026-03-30

Parse, transform, and redact HotSpot hs_err crash files — typed model, redaction, CLI.

You need to parse hs_err files programmatically and access threads, stack frames, registers, or VM info

Details
jdklogs ready

Interactive browser tool to explore -Xlog configs — see which statements match before deploying.

Config search with autocomplete over all -Xlog tags and levels

Details

Testing

test-order experimental
v0.1.0 ·2026-07-27

Run the tests most likely to fail first — zero-config test prioritisation for Maven/Gradle.

You want faster feedback in CI by surfacing relevant failures early

Details

JAR Packaging

execjar experimental
v0.1.2 ·2026-08-12

Turn a fat JAR into a self-executing file — no java -jar needed on Linux/macOS.

You want to ship a CLI tool as a single executable without requiring users to run java -jar

Details

Agent Development

meta-agent experimental
v0.0.3 ·2025-03-04

Instrument your Java agents — record and diff the bytecode transformations they apply.

You want to see exactly what bytecode transformations a Java agent (Mockito, Dynatrace, async-profiler) applies

Details

Libraries

jthreaddump ready
v0.5.8 ·2026-01-28

Parse jstack/jcmd thread dumps into a structured Java model — all JDK formats supported.

You need to programmatically parse and analyse Java thread dumps

Details

Learning Resources

writing-a-profiler demo

Companion code for "Writing a Profiler from Scratch" — minimal JVMTI/AsyncGetCallTrace profiler.

You are reading the blog series and want to run the example code

Details
Live JVM Inspection

jstall

ready v0.7.1 · 2026-05-17 GitHub

One-shot CLI inspector for running JVMs. Detects deadlocks, identifies hot threads, captures short-burst flamegraphs via async-profiler, and provides a live TUI — all without a persistent agent or IDE attachment. Use it when you need instant insight into a misbehaving JVM in production, staging, or CF without setting up a full profiler.

When to use

  • You need instant insight into a running JVM without attaching a full profiler
  • You need deadlock detection, hot thread analysis, or a quick flamegraph
  • You are working in a Cloud Foundry Java environment
  • You want a live TUI showing thread states, GC activity, and memory in real time

When not to use

  • You need continuous long-term profiling (use condensed-data + JFR instead)
  • You need allocation or lock profiling in depth (use async-profiler directly)
  • You are on Windows (the native binary is Linux/macOS only)

Install

curl -L https://github.com/parttimenerd/jstall/releases/latest/download/jstall.jar \
  -o jstall.jar
java -jar jstall.jar --help

Usage

java -jar jstall.jar status <pid>
java -jar jstall.jar flame <pid>

Find more information at https://github.com/parttimenerd/jstall

How To

These examples may be incomplete or outdated — see the README / docs for the full reference.
Get a quick report on hottest methods and active threads

Run the default status command — it combines deadlock detection, hot thread identification, thread categorization, and a dependency graph in one shot:

java -jar jstall.jar status <pid>
# Or match by JVM name instead of PID
java -jar jstall.jar status MyApplication

Use --intelligent-filter to collapse framework internals and focus on application code, or --full for the unfiltered output.

Find out where your JVM is stuck or not responding

Run deadlock first — if there is a deadlock it is the root cause and everything else is a symptom:

java -jar jstall.jar deadlock <pid>

If no deadlock, check which threads are BLOCKED or WAITING and what they are waiting for:

java -jar jstall.jar status <pid> --intelligent-filter

Look for threads in BLOCKED state and the monitor address they are waiting on. The thread currently holding that monitor is the bottleneck. For a continuous view while the JVM is unresponsive, use the live TUI:

java -jar jstall.jar threads --live <pid>
Find out why CPU is high

most-work takes repeated thread dumps and ranks threads by on-CPU frequency — no async-profiler needed, works on any JVM:

java -jar jstall.jar most-work --dumps 5 <pid>
# Show only top 3 threads
java -jar jstall.jar most-work --dumps 5 --top=3 <pid>

For a proper CPU flamegraph (requires async-profiler, bundled on Linux/macOS):

java -jar jstall.jar flame --duration=30 --open <pid>
Check if memory is growing without taking a full heap dump

The status command includes a heap histogram showing the top object types by instance count and retained bytes — much faster than a full jmap -histo:

java -jar jstall.jar status <pid> | grep -A 20 "Heap"

Take two snapshots a minute apart and compare to see which class is accumulating:

java -jar jstall.jar record <pid> --output before.zip
sleep 60
java -jar jstall.jar record <pid> --output after.zip
java -jar jstall.jar status before.zip
java -jar jstall.jar status after.zip

If the leak is confirmed, trigger a full heap dump for analysis with hprof-analyzer:

jcmd <pid> GC.heap_dump heap.hprof
Find the threads doing the most CPU work
java -jar jstall.jar most-work <pid>
# Take 3 dumps for a more accurate picture
java -jar jstall.jar most-work --dumps 3 <pid>
# Show only the top 5 threads
java -jar jstall.jar most-work --top=5 <pid>

most-work takes multiple thread dumps and ranks threads by how often they appear on-CPU across samples.

Capture a flamegraph
java -jar jstall.jar flame <pid>
# Record for 30 seconds
java -jar jstall.jar flame --duration=30 <pid>
# Open in browser automatically
java -jar jstall.jar flame --open <pid>

Runs async-profiler for a short burst and writes an interactive HTML flamegraph. Requires async-profiler on the same host (bundled for Linux/macOS).

Record the current state to a zip for offline analysis or sharing
java -jar jstall.jar record <pid> --output diagnostics.zip
# Replay later
java -jar jstall.jar status diagnostics.zip
java -jar jstall.jar most-work diagnostics.zip

The zip contains thread dumps, heap histogram, GC log excerpt, and other diagnostic data. Use -f diagnostics.zip as a global flag to replay any command.

Inspect a Cloud Foundry app

The cf-cli-java-plugin embeds jstall and exposes it via cf java jstall. You can also use jstall directly with the --cf option:

# Via cf-cli-java-plugin
cf java jstall $APP_NAME
cf java jstall $APP_NAME --args 'deadlock all'
cf java jstall $APP_NAME --args 'most-work --dumps 3 all'
cf java jstall $APP_NAME --args 'flame all'

# Or directly with jstall
java -jar jstall.jar --cf $APP_NAME status all
Latest release notes
  • Local AI via generic OpenAI-compatible provider (`OpenAiLlmProvider`) with auto-launch of llama-server
  • Tool-calling system for AI: 8 tools (`get_thread_stack_trace`, `search_stack_frames`, `get_lock_info`, `get_top_cpu_threads`, `compare_thread_across_dumps`, `get_dependency_tree`, `get_system_properties`, `get_raw_thread_dump_section`)
  • `--no-tools` flag, `--think` flag for showing LLM reasoning, `--short` for succinct summaries
  • Retry with exponential backoff on transient HTTP errors (429, 502, 503)
  • Robust `<think>` tag handling for streaming (handles tags split across chunks)
  • `Ta…

Flamegraph capture requires async-profiler on the same host. The JAR bundles a copy for Linux/macOS; on other platforms provide it via --async-profiler-path.

cf-cli-java-plugin

ready v4.0.2 · 2026-01-14 GitHub

Cloud Foundry CLI plugin to troubleshoot Java apps running on CF without SSH. Trigger heap dumps, thread dumps, and async-profiler or JFR recordings from the cf command line, with results streamed back to your machine. Also embeds jstall for full JVM inspection via `cf java jstall`.

When to use

  • You run Java apps on Cloud Foundry and need heap dumps, thread dumps, or CPU profiles
  • You want jstall-style JVM inspection without SSH access to the container
  • You need to record JVM diagnostic data (status zip) for offline analysis

When not to use

  • You are not using Cloud Foundry (use jstall directly for non-CF JVMs)
  • You need real-time continuous profiling rather than one-shot diagnostics

Related

Install

# Pick the binary for your platform:
cf install-plugin https://github.com/SAP/cf-cli-java-plugin/releases/latest/download/cf-cli-java-plugin-macos-arm64
# linux-amd64 / linux-arm64 / windows-amd64 also available

Usage

cf java heap-dump my-app
cf java thread-dump my-app
cf java jstall my-app

Find more information at https://github.com/SAP/cf-cli-java-plugin

How To

These examples may be incomplete or outdated — see the README / docs for the full reference.
My CF app is not responding — find what it is stuck on

Run jstall first — it detects deadlocks, identifies BLOCKED threads, and shows what each thread is waiting for:

cf java jstall $APP_NAME

If there is a deadlock, it will be listed at the top with the cycle of threads and monitors. For a focused deadlock check only:

cf java jstall $APP_NAME --args 'deadlock all'

If no deadlock, look for threads in BLOCKED state and the monitor they are waiting on. The thread that holds that monitor is the bottleneck. For a plain thread dump:

cf java thread-dump $APP_NAME
My CF app is using too much CPU

most-work takes repeated thread dumps and ranks threads by on-CPU frequency — no async-profiler needed:

cf java jstall $APP_NAME --args 'most-work --dumps 5 all'

For a proper CPU flame graph (slower, but much more detail):

cf java jstall $APP_NAME --args 'flame all'
# Downloads an HTML flamegraph to your current directory

Or use the two-step async-profiler approach if you want to capture during a specific window:

cf java asprof-start-cpu $APP_NAME
# reproduce the slow operation or wait 30–60 s
cf java asprof-stop $APP_NAME
# Downloads $APP_NAME-asprof-<random>.jfr — open in JDK Mission Control
My CF app crashed with OutOfMemoryError — take a heap dump

Take a heap dump from the running (or restarted) instance and download it:

cf java heap-dump $APP_NAME
# Downloads $APP_NAME-heapdump-<random>.hprof to current directory

Analyse with hprof-analyzer for Leak Suspects and Top Consumers:

hprof-analyzer $APP_NAME-heapdump-*.hprof report.html
# Open report.html → "Leak Suspects" and "Top Consumers" tabs

Note: requires jmap, which is not bundled by default in the CF Java Buildpack. Add a full JDK via JBP_CONFIG_OPEN_JDK_JRE: '[jre: {version: 21.+}, jdk: {include: true}]' to your app’s environment if you see a “jmap not found” error.

Take a heap dump from a running CF app
cf java heap-dump $APP_NAME

Downloads $APP_NAME-heapdump-<random>.hprof to your current directory. Open it in VisualVM, Eclipse MAT, or IntelliJ’s heap analyzer.

Note: requires jmap, which is not bundled by default in the CF Java Buildpack. Add a full JDK via JBP_CONFIG_OPEN_JDK_JRE: '[jre: {version: 21.+}, jdk: {include: true}]' to your app’s environment if you see a “jmap not found” error.

Get a thread dump and spot deadlocks
cf java thread-dump $APP_NAME

Prints the full thread dump to stdout. To save it:

cf java thread-dump $APP_NAME > thread-dump.txt

For a richer analysis including deadlock detection, hot threads, and lock graphs, use jstall:

cf java jstall $APP_NAME --args 'deadlock all'
Profile CPU usage with async-profiler
cf java asprof-start-cpu $APP_NAME
# reproduce the slow operation or wait 30–60 s
cf java asprof-stop $APP_NAME
# Downloads $APP_NAME-asprof-<random>.jfr

Open the .jfr file in JDK Mission Control or IntelliJ to view the flame graph. For a one-shot flame graph without manual start/stop, use jstall:

cf java jstall $APP_NAME --args 'flame all'
Record a full diagnostic snapshot for offline analysis
# Record everything (thread dump, heap histogram, jcmd output) into a zip:
cf java record-status $APP_NAME

# Include JFR recording and flame graph (slower, larger):
cf java record-status $APP_NAME --full

# Replay the zip locally with jstall:
jstall -f $APP_NAME-status.zip status all
jstall -f $APP_NAME-status.zip threads all

Useful for sharing diagnostics with teammates or filing bug reports without giving them CF access.

Inspect a Cloud Foundry app with jstall

The plugin embeds jstall directly — no separate installation needed:

# Full status report (deadlock detection, hot threads, etc.):
cf java jstall $APP_NAME

# Run a specific jstall subcommand:
cf java jstall $APP_NAME --args 'most-work --dumps 3 all'
cf java jstall $APP_NAME --args 'flame all'

To use a newer jstall version than the one bundled in the plugin, use jstall’s own --cf option instead:

jstall --cf $APP_NAME status all
Latest release notes
  • Fix rare ssh connection issue

Requires cf ssh to be enabled on the app (`cf enable-ssh my-app`, then restart). The heap-dump command additionally needs jmap — see the How To entry above if it is missing.

JFR / Profiling

firefox-profiler

ready Docs GitHub

Fork of the Firefox Profiler web app with patches that add native JFR and condensed-data (.cjfr) loading and JFR-specific UI improvements — custom marker tracks, marker-based call trees, Java syntax highlighting, and an in-browser converter (drag-and-drop a .jfr or .cjfr file, no server needed). Used as the embedded viewer in jfrtofp-server.

When to use

  • You want to open a JFR or condensed-data (.cjfr) recording directly in the browser without running a local server
  • You are using jfrtofp-server and want the embedded profiler viewer
  • You need JFR-specific UI features (custom marker tracks, Java syntax highlighting, function table)

When not to use

  • You want the upstream Firefox Profiler without JFR customisations
  • You need a stable, production-ready viewer (this is an experimental fork, rebased irregularly)

Used by jfrtofp-server as the embedded viewer. The jfrtofp branch is rebased onto upstream periodically; the fork is squashed into one commit on top so the diff stays minimal.

jfr-query

experimental Docs GitHub

Turns JFR recordings and JDK unified log files (GC logs) into a DuckDB database and exposes them through an in-browser notebook UI. Write SQL, get charts, explore event distributions, and use built-in views for common analyses — no install needed. Use it for ad-hoc investigation when you want to correlate events across types (GC, allocations, CPU) in a single query.

When to use

  • You want to run ad-hoc SQL queries over JFR event data or JDK unified log files (GC logs)
  • You need charts and notebook-style exploration of recordings
  • You want to correlate events across types (GC, allocations, CPU) in one query
  • You need to share a JFR analysis without requiring the recipient to install anything

When not to use

  • You need a stable, production-ready tool (schema may change between versions)
  • You need IDE integration (use the profiler plugin instead)
  • You have very large recordings — DuckDB runs in-browser via WASM, memory-constrained

Install

No stable release yet — check GitHub for build instructions.

Usage

java -jar query.jar serve myrecording.jfr
# opens http://localhost:4244

Find more information at https://parttimenerd.github.io/jfr-query/

How To

These examples may be incomplete or outdated — see the README / docs for the full reference.
Explore a recording in the browser without installing anything

Visit parttimenerd.github.io/jfr-query and drag-and-drop your .jfr file. The app runs entirely in-browser via DuckDB WASM — no data leaves your machine.

Use New from template to open ready-made notebooks for GC analysis, heap allocation, threading, and exceptions.

Find the hottest methods from the command line
java -jar query.jar query myrecording.jfr "hot-methods"

hot-methods is a built-in view. List all available views and macros:

java -jar query.jar views
java -jar query.jar macros
Import once, query many times with DuckDB
# Import the recording into a persistent .db file:
java -jar query.jar import myrecording.jfr mydb.db

# Query it directly with DuckDB CLI:
duckdb mydb.db "SELECT * FROM hot_methods"

# Or open DuckDB's own browser UI:
duckdb -ui mydb.db

Useful when you want to run many queries without re-parsing the JFR file each time.

Analyse GC logs (JDK unified log format)

jfr-query also accepts JDK unified log files (the output of -Xlog:gc*:file=gc.log):

java -jar query.jar serve gc.log
# or in the browser: drag-and-drop a .log file

Built-in GC log templates cover pause analysis, heap pressure, and allocation rate. Use the New from template button and select a GC log notebook.

Start the notebook server with custom templates
java -jar query.jar serve \
  --templates-dir ~/my-jfr-templates \
  --port 4244 \
  myrecording.jfr

Point --templates-dir at a folder of .json notebook files to share team-wide analysis templates. The AI assistant (Gemini/OpenAI) can be enabled via environment variables — see the docs for configuration.

Runs entirely in-browser via DuckDB WASM — no data leaves your machine. Very large recordings (>500 MB) may be slow due to browser memory limits. Stack traces are stored at 10 frames depth.

condensed-data

experimental Docs GitHub

CLI tool and Java agent for long-term, low-overhead JFR event storage. Records events from a running JVM via live-attach and compresses them into the self-describing .cjfr format — significantly smaller than raw JFR. Supports rotating files, configurable compression, and a reader library for querying stored data. View .cjfr files in jfr-query or the JMC fork with native .cjfr support.

When to use

  • You need long-term storage of JFR recordings with minimal disk usage
  • You want live-attach to a running JVM for continuous event capture
  • You are storing GC or other continuous JFR data over days or weeks
  • You want a self-describing format readable without the original JDK

When not to use

  • You need immediate full JFR replay compatibility without a reader library
  • You need a stable format (early development, .cjfr format may change)
  • You need Windows support (live-attach agent is Linux/macOS only)

Install

No stable release yet — check GitHub for build instructions.

Usage

# Attach to a running JVM (by PID) and start recording:
java -jar condensed-data.jar agent <PID> start recording.cjfr

# Condense existing JFR files to .cjfr:
java -jar condensed-data.jar condense myrecording.jfr output.cjfr

Find more information at https://parttimenerd.github.io/condensed-data/

How To

These examples may be incomplete or outdated — see the README / docs for the full reference.
Record a running JVM and view the results

Three commands: attach, wait, stop. No JFR configuration needed — condensed-data uses a built-in JFR profile.

# 1. Start recording (replace 1234 with your JVM's PID, or use the main class name)
java -jar condensed-data.jar agent 1234 start recording.cjfr

# 2. Let it run for as long as you need, then stop
java -jar condensed-data.jar agent 1234 stop

# 3. View a summary of what was captured
java -jar condensed-data.jar summary recording.cjfr

# 4. Open an event view (e.g. GC pauses, hot methods)
java -jar condensed-data.jar view recording.cjfr gc-pauses

To view interactively with SQL queries, open the file in jfr-query or drag-and-drop onto the hosted web UI.

GC pauses are growing — capture a long recording to see the trend

GC problems often build up over hours. Start a recording, let it run while the JVM degrades, then stop and look at the pause trend:

# Attach and start (runs until you stop it)
java -jar condensed-data.jar agent <PID> start gc-investigation.cjfr

# After the issue reproduces (minutes or hours later):
java -jar condensed-data.jar agent <PID> stop

# View GC pause trend
java -jar condensed-data.jar view gc-investigation.cjfr gc-pauses

Open in jfr-query for a timeline chart showing how pause duration evolves over the recording period. Look for: increasing pause frequency, pause cause changing from G1YoungGeneration to G1OldGeneration, or heap-after-GC growing steadily (heap leak).

Keep a rolling window of diagnostics without filling disk

In production you rarely know in advance when a problem will occur. Use rotating files to keep the last N MB of history always available:

java -jar condensed-data.jar agent <PID> start \
  --rotating --max-size=100m --max-files=5 \
  recording_$index.cjfr

When an incident occurs, stop the agent — the most recent files covering the last ~500 MB of events are on disk. Condense them for analysis:

java -jar condensed-data.jar agent <PID> stop
java -jar condensed-data.jar view recording_0.cjfr gc-pauses

<PID> can also be a main-class name filter or all to target all matching JVMs.

Record with rotating files (production use)

Keep at most 3 files of 100 MB each — old files are overwritten:

java -jar condensed-data.jar agent <PID> start \
  --rotating --max-size=100m --max-files=3 \
  recording_$index.cjfr

Or start via -javaagent at JVM launch (comma-separated, no dashes):

java -javaagent:condensed-data.jar=start,rotating,max-size=100m,max-files=3,recording.cjfr \
  -jar myapp.jar
Condense existing JFR files
# Single file:
java -jar condensed-data.jar condense myrecording.jfr output.cjfr

# Whole folder or ZIP:
java -jar condensed-data.jar condense recordings/ output.cjfr

# Inflate back to JFR for tools that don't support .cjfr:
java -jar condensed-data.jar inflate output.cjfr restored.jfr
View events and summaries
# Print a summary:
java -jar condensed-data.jar summary recording.cjfr

# Use JFR named views (gc-pauses, hot-methods, allocation-by-site, …):
java -jar condensed-data.jar view recording.cjfr gc-pauses

# Open in jfr-query notebook UI:
java -jar query.jar serve recording.cjfr
# or drag-and-drop onto https://parttimenerd.github.io/jfr-query/
Open in JDK Mission Control

A JMC fork supports native .cjfr files — no inflation step required:

# Download snapshot build from:
# https://github.com/parttimenerd/jmc/releases/tag/snapshot
# Then open JMC and File > Open > select your .cjfr file

Or inflate first if you need the standard JMC release:

java -jar condensed-data.jar inflate recording.cjfr recording.jfr

No stable release yet — rolling snapshots only. Check docs/jar-releases.md for which JAR variant to download for your environment.

jfrevents

ready v0.7 · 2025-09-26 Docs GitHub

Reference site listing every JFR event across JDK versions, with field names, types, descriptions, and benchmark-derived examples showing which events fire at what rate. Use it when you are writing JFR tooling and need to know exactly which fields an event has or which events are worth subscribing to.

When to use

  • You need to know what fields a JFR event has across JDK versions
  • You are writing JFR tooling and need event documentation
  • You want to understand which events are worth subscribing to for a given use case
  • You need to compare event availability between JDK 17, 21, and 24

When not to use

  • You need a programmatic API to enumerate events at runtime (use JFR API directly)
  • You need offline access (web-only reference site)

Canonical site is at sap.github.io/jfrevents/. Maintained by the SAP SapMachine team.

tiny-profiler

demo GitHub

Educational sampling CPU profiler for Java written in pure Java 17. Uses Thread.getAllStackTraces() to sample stacks and outputs a method table and an HTML flame graph. Safepoint-biased by design — intended to demystify how profilers work, not for production use.

When to use

  • You want to understand how a sampling profiler works from first principles
  • You need a quick profiler with no native dependencies on any platform

When not to use

  • You need accurate profiling — safepoint-biased, misses async and JIT-compiled hot paths
  • You need production profiling — use async-profiler or JFR instead

Install

No stable release yet — check GitHub for build instructions.

Usage

java -javaagent:target/tiny-profiler.jar=flamegraph=flame.html YourApp
# interval=10 (ms) is the default; override with interval=5
java -javaagent:target/tiny-profiler.jar=flamegraph=flame.html,interval=5 YourApp

Find more information at https://github.com/parttimenerd/tiny-profiler

Safepoint-biased — only samples at JVM safepoints, so CPU-heavy native or JIT code may be underrepresented.

Heap Dump Analysis

hprof-analyzer

ready v0.2.0 Docs GitHub

Fast heap dump analyser (Rust + Java CLI). Reproduces Eclipse MAT's System Overview, Leak Suspects, Top Consumers, and Threads views with a fraction of the memory overhead. Handles dumps larger than 10 GiB that MAT cannot open. Also ships an OQL query engine, an HTTP server for programmatic access, a redact subcommand to zero sensitive data before sharing, and an in-browser WASM UI for dumps up to 3 GiB.

When to use

  • You need to analyse a heap dump without Eclipse MAT's memory overhead
  • You have a very large heap dump (>10 GiB) that MAT cannot handle
  • You want a quick in-browser analyser for smaller dumps (WASM, memory-limited by browser)
  • You want Leak Suspects, Top Consumers, and thread views without a GUI IDE
  • You need scriptable, CI-friendly heap analysis (JSON output, diff two dumps)

When not to use

  • You need full interactive MAT-style object graph exploration (use Eclipse MAT)
  • You need Windows support (native binary is currently Linux/macOS only)

Install

brew tap parttimenerd/hprof-analyzer
brew trust parttimenerd/hprof-analyzer  # required once for third-party taps
brew install hprof-analyzer

Usage

hprof-analyzer heap.hprof report.html

Find more information at https://parttimenerd.github.io/hprof-analyzer/

How To

These examples may be incomplete or outdated — see the README / docs for the full reference.
Analyse a heap dump and open the report
hprof-analyzer heap.hprof report.html

Opens a self-contained HTML file with System Overview, Leak Suspects, Top Consumers, and Threads — no server, no external assets.

Compressed dumps are read transparently:

hprof-analyzer heap.hprof.gz report.html
hprof-analyzer heap.hprof.zip report.html

Tip: use -XX:+HeapDumpGzip to write compressed dumps directly — typically 5–10× smaller.

Update to the latest nightly build at any time:

hprof-analyzer update nightly
Find what is consuming the most memory

The top-consumers view ranks object types by retained heap — the amount of memory that would be freed if all instances of that class were collected:

hprof-analyzer heap.hprof report.html
# Open report.html → "Top Consumers" tab

For a fast command-line answer without opening the browser:

hprof-analyzer top-consumers heap.hprof
# Show top 20 instead of default 10
hprof-analyzer top-consumers --limit 20 heap.hprof

Look for classes with high retained bytes — those are the real memory holders, not just large arrays that are themselves held by something else.

Find the likely cause of an OutOfMemoryError

The leak-suspects view groups objects into accumulation points — classes where many instances exist that share a common path from a GC root:

hprof-analyzer heap.hprof report.html
# Open report.html → "Leak Suspects" tab

Each suspect shows the accumulator class, instance count, total retained bytes, and the shortest path from a GC root to a representative instance. For a text summary:

hprof-analyzer leak-suspects heap.hprof

If the suspect is a framework collection (e.g. a HashMap inside a cache), check which application class holds a reference to it — that is where to look for a missing eviction policy or unbounded growth.

Analyse in the browser without installing anything
Visit parttimenerd.github.io/hprof-analyzer and drag-and-drop your .hprof file. Runs entirely via WebAssembly — no data leaves your machine. Performance depends on available browser memory; it may work fine above 3 GiB or struggle below it depending on your machine. For large or slow dumps use the native binary instead.
Run OQL queries — CLI REPL or browser

Interactive REPL in the terminal with tab-completion:

hprof-analyzer query heap.hprof --repl

Or start a local server and use the browser OQL shell:

hprof-analyzer server heap.hprof
# → open http://127.0.0.1:7070
/help oql        — full OQL language reference
/examples        — guided tour of OQL examples by category

The OQL dialect is modelled on Eclipse MAT’s — most MAT queries work unchanged.

Use with Claude or Cline via MCP

hprof-analyzer ships a built-in MCP server so Claude, Cline, and other MCP-compatible AI assistants can analyse heap dumps directly.

Claude Code:

claude mcp add hprof -- hprof-analyzer mcp

Cline (VS Code) — add to .vscode/mcp.json:

{ "mcpServers": { "hprof": { "command": "hprof-analyzer", "args": ["mcp"] } } }

Homebrew prints full MCP setup instructions after install.

Redact a dump before sharing

Heap dumps contain sensitive data — string values, field values, serialized objects. The redact subcommand zeroes all primitive values and array contents while preserving class/field/method names, producing a dump safe to attach to a bug report or share with colleagues:

hprof-analyzer redact heap.hprof redacted.hprof

hprof-analyzer detects redacted dumps automatically and shows a “Redacted dump” banner in the report, skipping analyses that would be meaningless on zeroed data.

Generate Eclipse MAT cache files (speed up first MAT open)

For large dumps MAT’s first parse can peak at ~55 GiB RSS. hprof-analyzer generates the same cache files with much lower memory usage:

hprof-analyzer mat caches heap.hprof /path/to/heap-dir/

Open heap.hprof in MAT as usual — it detects the cache and skips the expensive first parse.

Native binary requires Linux or macOS. The in-browser WASM version is memory-limited by the browser — it may work for larger dumps or struggle for smaller ones depending on your machine.

hprof-redact

poc v0.3.0 · 2026-06-24 GitHub

Deprecated standalone redaction tool — the `hprof-analyzer redact` subcommand now supersedes this for CLI use. The underlying stream-based library (zeroes string contents and primitive values without loading the full file into memory) remains available for programmatic pipeline integration, e.g. CF plugin tooling.

When to use

  • You are building tooling that needs to redact heap dumps programmatically (library use)

When not to use

  • You want to redact a dump interactively — use hprof-analyzer's built-in redact subcommand instead
  • You need guaranteed complete privacy — not security-audited

Install

<dependency>
  <groupId>me.bechberger</groupId>
  <artifactId>hprof-redact</artifactId>
  <version>0.3.0</version>
</dependency>

Usage

hprof-analyzer redact input.hprof redacted.hprof

Find more information at https://github.com/parttimenerd/hprof-redact

How To

These examples may be incomplete or outdated — see the README / docs for the full reference.
Redact a heap dump before sharing

For standalone use, call hprof-analyzer redact — it uses this library internally and is the recommended way to redact without writing code:

hprof-analyzer redact heap.hprof redacted.hprof

The redacted dump zeroes all string values and primitive field contents while keeping class/field/method names intact. hprof-analyzer detects it automatically and shows a “Redacted dump” banner in the report.

Redact programmatically in a pipeline

Add the Maven dependency and call HprofRedactor.redact() in a stream pipeline — the library never loads the full dump into memory:

import me.bechberger.HprofRedactor;
import java.nio.file.Path;

HprofRedactor.redact(
    Path.of("heap.hprof"),
    Path.of("heap-redacted.hprof")
);

Use this when building CF plugin tooling or any pipeline that needs to strip sensitive data before uploading a dump to a remote system.

Deprecated for standalone use — `hprof-analyzer redact` supersedes this. Library is still usable for programmatic pipeline integration. Not security-audited — review before using for compliance purposes.

Crash / Log Diagnostics

jhserr

experimental v0.1.0 · 2026-03-30 Docs GitHub

Java library and CLI for parsing, transforming, and redacting HotSpot hs_err crash report files. Full-fidelity round-trip parsing into a typed model with 20+ section types, visitor and transformer patterns for selective modification, and configurable redaction of sensitive data before sharing.

When to use

  • You need to parse hs_err files programmatically and access threads, stack frames, registers, or VM info
  • You want to redact sensitive data (usernames, paths, PIDs, env vars) before sharing crash reports
  • You need to convert hs_err files to JSON for tooling or storage

When not to use

  • You just need to read a crash report — open it in a text editor or use the VS Code extension

Install

<dependency>
  <groupId>me.bechberger</groupId>
  <artifactId>jhserr</artifactId>
  <version>0.1.0</version>
</dependency>

Usage

HsErrReport report = HsErrParser.parse(Path.of("hs_err_pid12345.log"));
System.out.println(report.header().errorType() + ": " + report.header().errorDetail());

HsErrReport redacted = RedactionTransformer.withDefaults().transform(report);
Files.writeString(Path.of("redacted.log"), redacted.toString());

Find more information at https://parttimenerd.github.io/jhserr/

How To

These examples may be incomplete or outdated — see the README / docs for the full reference.
Parse a crash report and inspect what crashed

Point HsErrParser.parse() at the file — error type, signal, problematic frame, and JVM version are in the header:

import me.bechberger.jhserr.HsErrParser;
import me.bechberger.jhserr.model.HsErrReport;

HsErrReport report = HsErrParser.parse(Path.of("hs_err_pid12345.log"));
System.out.println(report.header().errorType());    // e.g. SIGSEGV
System.out.println(report.header().errorDetail());  // problematic frame
System.out.println(report.vmInfo().version());      // JVM version string

// Java frames from the crashing thread:
report.currentThread().javaFrames()
      .forEach(f -> System.out.println(f.method()));

Or get a quick summary without writing code:

java -jar jhserr.jar summary hs_err_pid12345.log
Redact a crash report before sharing

The default preset strips usernames, paths, hostnames, PIDs, IP addresses, and env vars. Use --minimal or --aggressive for lighter or heavier redaction:

java -jar jhserr.jar redact hs_err_pid12345.log -o redacted.log
java -jar jhserr.jar redact --aggressive hs_err_pid12345.log -o redacted.log
java -jar jhserr.jar redact --scan hs_err_pid12345.log   # scan only, no output

Add extra hints for paths or usernames not auto-detected:

java -jar jhserr.jar redact --username jdoe --sensitive-path /home/jdoe/project \
    hs_err_pid12345.log -o redacted.log
Walk the parsed model with the visitor pattern

Override only the visit methods you need — all default to no-ops:

report.accept(new HsErrVisitor() {
    @Override
    public void visitJavaFrames(FrameList frames) {
        frames.frames().forEach(f -> System.out.println(f.method()));
    }
    @Override
    public void visitEnvironmentVariables(EnvironmentVariables vars) {
        vars.variables().forEach((k, v) -> System.out.println(k + "=" + v));
    }
});
Convert to JSON for tooling
String json = HsErrJson.toJson(report);
HsErrReport restored = HsErrJson.fromJson(json); // round-trip

Or via CLI:

java -jar jhserr.jar json to hs_err.log > report.json
java -jar jhserr.jar json from report.json > restored.log
java -jar jhserr.jar json schema > schema.json

Early prototype — no guarantees regarding functionality or security.

jdklogs

ready Docs GitHub

Interactive browser tool for exploring OpenJDK's -Xlog subsystem. Enter a log config and see exactly which log statements match — with source context, GitHub permalinks, sample output from benchmark runs, and a volume estimate. Use it when you are tuning GC, JIT, or other -Xlog flags and want to understand what output you will get before deploying.

Features

  • Config search with autocomplete over all -Xlog tags and levels
  • Selector wizard — toggle tags on/off without knowing the syntax
  • Firing log sites grouped by file with surrounding source context and GitHub permalinks
  • Real sample output captured from benchmark runs (G1, ZGC, Parallel)
  • Volume estimate (≈ MB/hour) per tag to gauge logging overhead
  • Coverage across JDK versions from LTS to master

Data is regenerated monthly from the OpenJDK source and redeployed to GitHub Pages.

Testing

test-order

experimental v0.1.0 · 2026-07-27 Docs GitHub

Maven and Gradle plugin that reorders your test suite so tests covering recently changed code run first. Uses bytecode instrumentation to track class-to-test coverage, then on the next run promotes affected tests to the front — zero config, no cloud, no annotations.

When to use

  • You want faster feedback in CI by surfacing relevant failures early
  • Your test suite is large enough that waiting for the full run hurts productivity
  • You want zero-config test prioritisation with Maven or Gradle

When not to use

  • You need cross-module coverage tracking (single-module only for now)
  • You are not using Maven or Gradle

Install

<plugin>
  <groupId>me.bechberger</groupId>
  <artifactId>test-order-maven-plugin</artifactId>
  <version>0.1.0</version>
  <extensions>true</extensions>
  <executions>
    <execution>
      <goals><goal>prepare</goal></goals>
    </execution>
  </executions>
</plugin>

Usage

mvn test          # first run: learns coverage
mvn test          # second run: affected tests go first

Find more information at https://parttimenerd.github.io/test-order/

How To

These examples may be incomplete or outdated — see the README / docs for the full reference.
Enable the short mvn test-order: prefix

Maven only resolves goal prefixes for trusted groupIds. Add me.bechberger to ~/.m2/settings.xml once so the short form works from the CLI:

<settings>
  <pluginGroups>
    <pluginGroup>me.bechberger</pluginGroup>
  </pluginGroups>
</settings>

Without this, use the fully-qualified form:

mvn me.bechberger:test-order-maven-plugin:show
Inspect the test ranking and run affected tests only

Maven:

mvn test-order:show       # ranked list of all tests
mvn test-order:affected test  # run only tests affected by recent changes
mvn test-order:dashboard  # full HTML report

Gradle:

./gradlew testOrderShow
./gradlew testOrderAffected
./gradlew testOrderDashboard
Detect flaky and order-dependent tests
mvn test-order:detect-dependencies   # Maven
./gradlew testOrderDetectDependencies  # Gradle

Runs the suite in multiple orders to surface tests that only pass in a specific sequence.

Diagnose why reordering isn't working
mvn test-order:diagnose         # Maven
./gradlew testOrderDiagnose     # Gradle

Checks prerequisites (Java 17+, Maven 3.6+/Gradle 7.6+, Git), coverage data presence, and plugin configuration. Run this first if tests are not being reordered.

JAR Packaging

execjar

experimental v0.1.2 · 2026-08-12 GitHub

Maven plugin and CLI that turns a fat JAR into a single self-executing file on Linux and macOS. Prepends a small POSIX sh launcher that finds Java automatically, checks min/max version requirements, and execs the JAR — no java -jar needed.

When to use

  • You want to ship a CLI tool as a single executable without requiring users to run java -jar
  • You need automatic Java discovery and min/max version checking at startup

When not to use

  • You are on Windows (Linux/macOS only)
  • You are packaging a library JAR (requires a fat/uber JAR with all dependencies)

Install

<plugin>
  <groupId>me.bechberger</groupId>
  <artifactId>execjar</artifactId>
  <version>0.1.2</version>
  <executions>
    <execution>
      <goals><goal>execjar</goal></goals>
    </execution>
  </executions>
</plugin>

Usage

mvn package
./target/your-app   # no java -jar needed

Find more information at https://github.com/parttimenerd/execjar

How To

These examples may be incomplete or outdated — see the README / docs for the full reference.
Set up the two-step Maven build

execjar must run after a fat JAR has been assembled. Add it after your assembly or shade plugin:

<!-- Step 1: assemble fat JAR -->
<plugin>
  <artifactId>maven-assembly-plugin</artifactId>
  <version>3.6.0</version>
  <configuration>
    <descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs>
    <archive><manifest><mainClass>com.example.App</mainClass></manifest></archive>
  </configuration>
  <executions>
    <execution>
      <id>make-assembly</id>
      <phase>package</phase>
      <goals><goal>single</goal></goals>
    </execution>
  </executions>
</plugin>

<!-- Step 2: wrap it -->
<plugin>
  <groupId>me.bechberger</groupId>
  <artifactId>execjar</artifactId>
  <version>VERSION</version>
  <executions>
    <execution>
      <goals><goal>execjar</goal></goals>
    </execution>
  </executions>
</plugin>

After mvn package, run the output directly — the launcher searches $JAVA_HOME, then PATH, then /etc/alternatives/java, then common install locations under /Library/Java and /usr/lib/jvm:

./target/your-app
Enforce a minimum (or maximum) Java version

Add <minJavaVersion> and/or <maxJavaVersion> to the plugin configuration:

<configuration>
  <minJavaVersion>17</minJavaVersion>
  <maxJavaVersion>21</maxJavaVersion>
</configuration>

minJavaVersion defaults to maven.compiler.release or maven.compiler.target if set, so you often don’t need to configure it explicitly. The launcher prints a clear error and exits if the requirement isn’t met.

Embed JVM options and system properties

JVM flags and system properties can be baked into the launcher so users don’t need to set them:

<configuration>
  <jvmOpts>-Xmx512m -XX:+UseG1GC</jvmOpts>
  <javaProperties>
    <app.name>My Application</app.name>
    <app.config>/opt/myapp/config</app.config>
  </javaProperties>
</configuration>

Environment variables, prepended/appended default arguments, and strict-mode (exact version match) are also supported — see the README for the full configuration reference.

Debug Java discovery at runtime

Set EXECJAR_DEBUG=1 before running to print the selected Java executable, detected version, resolved JVM options, and the final exec command:

EXECJAR_DEBUG=1 ./target/your-app

This is useful when the wrong Java is picked up or version checks fail unexpectedly.

Latest release notes
  • Correct ZIP/ZIP64 internal offsets after prepending the launcher script, fixing ZIP64 archives and strict readers (e.g. OpenJDK `java`/`jar`) that follow absolute offsets rather than scanning for signatures ([#1](https://github.com/parttimenerd/execjar/issues/1))
  • `execjar.jar` - Executable JAR file (requires Java 11+)
  • `execjar` - Standalone launcher script (Unix/Linux/macOS)
Agent Development

meta-agent

experimental v0.0.3 · 2025-03-04 GitHub

Java agent that instruments other Java agents — wraps every ClassFileTransformer it finds and records bytecode diffs viewable via a built-in web UI. Shows exactly what Mockito, Dynatrace, async-profiler, or any other agent does to your classes at runtime.

When to use

  • You want to see exactly what bytecode transformations a Java agent (Mockito, Dynatrace, async-profiler) applies
  • You are debugging unexpected behaviour caused by another agent's class instrumentation
  • You want to intercept or block specific ClassFileTransformer registrations at runtime

When not to use

  • You only want to view class bytecode without running another agent — use a decompiler instead

Install

git clone https://github.com/parttimenerd/meta-agent
cd meta-agent
mvn package -DskipTests
# JAR: target/meta-agent.jar

Usage

java -javaagent:target/meta-agent.jar=server -jar your-program.jar
# open http://localhost:7071 to browse transformations

Find more information at https://github.com/parttimenerd/meta-agent

How To

These examples may be incomplete or outdated — see the README / docs for the full reference.
View what an agent does to your classes

Attach meta-agent alongside the agent you want to inspect. Open the built-in web UI to browse decompiled bytecode diffs grouped by transformer or by class:

java -javaagent:target/meta-agent.jar=server -jar your-program.jar
# custom port:
java -javaagent:target/meta-agent.jar=server,port=8080 -jar your-program.jar

Key endpoints at http://localhost:7071:

  • /instrumentators — list all ClassFileTransformers that were registered
  • /full-diff/instrumentator?pattern=.* — decompiled bytecode diff per transformer
  • /classes — list all classes that were transformed
  • /full-diff/class?pattern=.* — full diff across all classes and transformers
  • /all/decompile?pattern=<pattern> — decompile matching classes
Intercept or block transformer registrations with a callback

Implement InstrumentationCallback and pass it via the cb argument to observe or block transformers. Note: CallbackAction.IGNORE suppresses the transformer (not DENY):

public class MyHandler implements InstrumentationCallback {
    @Override
    public CallbackAction onAddTransformer(ClassFileTransformer transformer) {
        System.err.println("New transformer: " + transformer.getClass().getName());
        return CallbackAction.ALLOW; // or IGNORE to suppress it
    }

    @Override
    public void onExistingTransformer(ClassFileTransformer transformer) {
        System.err.println("Existing transformer: " + transformer.getClass().getName());
    }

    @Override
    public CallbackAction onInstrumentation(ClassFileTransformer transformer,
            ClassArtifact before, ClassArtifact after) {
        System.out.println("Instrumenting: " + before.klass().getName());
        return CallbackAction.ALLOW;
    }
}

Pass the fully qualified class name via cb:

java -javaagent:target/meta-agent.jar=server,cb=com.example.MyHandler -jar your-program.jar
Capture native agents too

Build the native component first, then load both agents:

cd native && make
# macOS:
java -agentpath:native/native_agent.dylib \
     -javaagent:target/meta-agent.jar=server \
     -jar your-program.jar
# Linux:
java -agentpath:native/native_agent.so \
     -javaagent:target/meta-agent.jar=server \
     -jar your-program.jar
Add to Maven test runs

Use the Maven plugin to attach meta-agent automatically during mvn test:

<plugin>
  <groupId>me.bechberger</groupId>
  <artifactId>meta-agent-maven-plugin</artifactId>
  <version>VERSION</version>
  <executions>
    <execution>
      <phase>validate</phase>
      <goals><goal>meta-agent</goal></goals>
    </execution>
  </executions>
  <configuration>
    <server>true</server>
    <callbackClasses>
      <callbackClass>com.example.MyHandler</callbackClass>
    </callbackClasses>
  </configuration>
</plugin>

Add the runtime dependency so your callback class can reference the API:

<dependency>
  <groupId>me.bechberger</groupId>
  <artifactId>meta-agent</artifactId>
  <version>VERSION</version>
  <scope>test</scope>
</dependency>
Libraries

jthreaddump

ready v0.5.8 · 2026-01-28 GitHub

Java library for parsing thread dump output from jstack and jcmd into a typed model. Handles all major thread dump formats across JDK versions, including virtual threads, giving you structured access to thread states, stack frames, lock information, CPU times, and deadlock cycles.

When to use

  • You need to programmatically parse and analyse Java thread dumps
  • You are building tooling that processes jstack or jcmd output
  • You want to diff thread dumps or detect recurring blocking patterns
  • You need virtual thread support in thread dump parsing

When not to use

  • You need live thread inspection (use jstall instead)
  • You need profiling data (use the JFR profiling tools instead)

Install

<dependency>
  <groupId>me.bechberger</groupId>
  <artifactId>jthreaddump</artifactId>
  <version>0.5.8</version>
</dependency>

Usage

ThreadDump dump = ThreadDumpParser.parse(dumpText);
for (var thread : dump.threads()) {
    System.out.println(thread.name() + " - " + thread.state());
}
if (dump.deadlockInfos() != null && !dump.deadlockInfos().isEmpty()) {
    System.out.println("Deadlocks detected!");
}

Find more information at https://github.com/parttimenerd/jthreaddump

How To

These examples may be incomplete or outdated — see the README / docs for the full reference.
Parse a thread dump file and inspect threads

Feed any jstack or jcmd Thread.print output to ThreadDumpParser.parse() — you get a typed model with states, stack frames, and deadlock detection:

import me.bechberger.jthreaddump.ThreadDumpParser;
import me.bechberger.jthreaddump.model.ThreadDump;

String raw = Files.readString(Path.of("dump.txt"));
ThreadDump dump = ThreadDumpParser.parse(raw);

System.out.println("Threads: " + dump.threads().size());
dump.threads().forEach(t ->
    System.out.println(t.name() + " — " + t.state()));

if (!dump.deadlockInfos().isEmpty()) {
    System.out.println("Deadlock detected!");
}

Works with output from jstack <pid>, jcmd <pid> Thread.print, and thread dumps in .txt files captured by any means.

Capture a live thread dump and parse it
String raw = JStackUtil.captureThreadDump(pid);        // uses jstack
String raw = JStackUtil.captureThreadDump(pid, true);  // uses jcmd (Thread.print -l)
ThreadDump dump = ThreadDumpParser.parse(raw);

jcmd output includes more detail: CPU time, elapsed time, and JNI reference counts.

Find BLOCKED threads and the locks they are waiting for
ThreadDump dump = ThreadDumpParser.parse(dumpText);
dump.threads().stream()
    .filter(t -> t.state() == Thread.State.BLOCKED)
    .forEach(t -> System.out.println(t.name() + " is blocked"));

Lock information (monitors held/waiting, object addresses) is available on each thread object.

Find threads consuming the most CPU time

CPU time is populated when using jcmd output (JStackUtil.captureThreadDump(pid, true)).

dump.threads().stream()
    .filter(t -> t.cpuTimeSec() != null)
    .sorted((a, b) -> Double.compare(b.cpuTimeSec(), a.cpuTimeSec()))
    .limit(10)
    .forEach(t -> System.out.printf("%s: %.2f sec CPU%n", t.name(), t.cpuTimeSec()));
Diff two thread dumps
ThreadDump before = ThreadDumpParser.parse(Files.readString(Path.of("before.txt")));
ThreadDump after  = ThreadDumpParser.parse(Files.readString(Path.of("after.txt")));
System.out.println("Thread delta: " + (after.threads().size() - before.threads().size()));

Use this pattern to compare dumps taken during a suspected leak or thread explosion.

Use the CLI to pretty-print a dump

The library ships a minimal CLI for quick inspection:

jthreaddump dump.txt           # parse and display summary
jstack <pid> | jthreaddump -   # pipe from jstack
jthreaddump dump.txt -v        # verbose output

Build it with mvn clean package, then run java -jar target/jthreaddump.jar.

Learning Resources

writing-a-profiler

demo GitHub

Companion code for the "Writing a Profiler from Scratch" blog series. A minimal C++/JVMTI sampling profiler that uses AsyncGetCallTrace via signal-based interrupts — showing the core mechanics of how production profilers like async-profiler work under the hood.

When to use

  • You are reading the blog series and want to run the example code
  • You want to study the core mechanics of a signal-based JVMTI sampling profiler

When not to use

  • You need a production profiler — use async-profiler or JFR instead
  • You want a pure-Java profiler — see tiny-profiler instead

Install

No stable release yet — check GitHub for build instructions.

Usage

# macOS:
java -agentpath:cpp/libSmallProfiler.dylib=interval=0.001s -cp samples BasicSample
# Linux:
java -agentpath:cpp/libSmallProfiler.so=interval=0.001s -cp samples BasicSample

Find more information at https://github.com/parttimenerd/writing-a-profiler

Don't set the interval too low or you will crash the JVM. Educational code only — not for production use.