NAME
java-profiler - a java profiler and debugger i'm writing from scratch in rust: a native jvmti agent with its own hotspot stack walkers, a crash-safe recording format, a cli, and a react ui with flame graphs and runtime health, plus optional minecraft server integrations
SYNOPSIS
profiler doctor profiler record --duration 30s -- java -jar app.jar profiler record <PID> --duration 30s profiler serve <RECORDING>
DESCRIPTION
Java Profiler is a profiler and debugger for ordinary Java applications, built in Rust. I own the engine: async-profiler, Spark and JProfiler are ruled out as runtime dependencies. A native JVMTI agent loads at JVM startup or attaches to a running process, samples CPU through perf events, wall-clock time through signal-driven thread sweeps, and allocations and monitor contention through JVMTI. It walks stacks with its own HotSpot walkers for Java 8, 11, 17, 21 and 25, which read the VM's own metadata tables to fingerprint its layout instead of trusting a hard-coded one.
The agent never stalls the application. Sampling callbacks don't allocate, lock or do I/O, and every queue is a preallocated ring that drops the newest record when full and writes the loss into the recording as a gap. Samples land in a versioned, chunked binary recording with CRC-checked chunks, per-chunk compression, and recovery that keeps the valid prefix of a file whose daemon was killed. Readers and rewriters keep unknown records and fields byte for byte, so older tools don't destroy newer data.
On top of that sit an analysis engine (weighted call trees, hot methods, time-window queries, GC and runtime health series), an authenticated loopback API, a profiler CLI, and a React UI with flame graphs and correlated timelines. The debugger shares the same agent: breakpoints, stepping and run to cursor, with every stop numbered by generation so stale frame handles are rejected and a disconnect only releases the stops it owns.
Minecraft support is optional. A Java 8 Bukkit bootstrap and a Velocity plugin add a /profiler command, server health reports and plugin ownership worked out from class loaders, and a compatibility lab boots pinned Spigot and Paper servers to prove it. The first release gate passed on native Linux: real JVMs from Java 8 to 25, samples on the right source lines, crash recovery, and wall-sampling overhead measured under its limit. It's still in development. Linux x86-64 comes first, native Windows comes later, and the repo is private for now.
DESIGN
- Lossy by design, never blocking
- Profiling telemetry goes through preallocated, drop-newest rings of plain-copy records. When the daemon falls behind, records are dropped, counted and written into the recording as gaps. In the overload trial the agent dropped close to four million records and every drop was accounted for instead of the target JVM being made to wait.
- Stack walking I own
- vm-hotspot reads HotSpot's own VMStructs tables to fingerprint each VM's layout before walking a frame, with walkers for Java 8, 11, 17, 21 and 25 and AsyncGetCallTrace bindings alongside. Stress tests walk stacks while compiled methods are being unloaded, because that's where walkers crash.
- Recordings that outlive the profiler
- A recording is a header followed by CRC-checked chunks across seven lanes: samples, runtime, spans, metadata, health, gaps and debugger interventions. Chunks are compressed only when that makes them smaller. Recovery keeps the valid prefix of a damaged file, and derived views like call trees and flame graphs are always rebuilt from the raw evidence.
- A debugger in the same agent
- The debugger uses JVMTI breakpoints and single-stepping directly instead of JDWP. Every stop carries a generation number, frame and value handles are tied to it, and they're invalidated on resume. On disconnect the agent releases only the stops it owns, and only an explicit debugger action can suspend application threads.
- Evidence, not claims
- Each task is accepted by a verifier that records source and artifact hashes, runner identity and command logs, and rejects runs with zero tests. The release gate is fed dozens of kinds of deliberately broken evidence to check it refuses them. Overhead is measured with paired trials and bootstrapped confidence intervals.
FEATURES
- Launch a JVM under the agent or attach to a running one on Linux
- CPU sampling via perf events, wall-clock sampling, allocation sampling, and monitor contention capture
- Own HotSpot stack walkers for Java 8, 11, 17, 21 and 25, including virtual threads
- Java, JIT, native and kernel symbol resolution, with C++ and Rust demangling
- Versioned, crash-recoverable binary recordings with compression and retention limits
- Weighted call trees, hot methods, time and thread window queries, and recording comparison
- React UI with flame graphs, correlated timelines and a runtime health page (heap, CPU, threads, GC pauses)
- Debugger with source and method breakpoints, frames and locals, stepping and run to cursor
- profiler doctor explains why attach or perf would fail (ptrace, perf_event_paranoid, seccomp, PID namespaces)
- Optional Bukkit and Velocity plugins with /profiler commands, health reports and plugin ownership
CHALLENGES
- Sampling a running JVM from signal handlers without allocating, locking or doing I/O, so the profiler can lose data but never stall the application
- Walking HotSpot stacks across Java 8 to 25 with walkers that fingerprint each VM's internal layout from its own metadata, including while compiled code is being retired
- Designing a recording format that survives a SIGKILLed daemon and keeps unknown records intact through a rewrite
- Sharing one agent between a lossy profiler and a debugger whose stop and resume commands have to be reliable
- Proving overhead with paired trials and confidence intervals rather than one benchmark run
- Keeping Minecraft support optional, with a Java 8 Bukkit bootstrap and version-specific adapters isolated from the shared engine
STACK
- frontend
- React 19, TypeScript, TanStack Router, TanStack Query, Tailwind CSS, Playwright
- backend
- Rust, JVMTI, Tokio, axum, Java 8 to 25, Bukkit / Velocity
- tools
- perf_event_open, AsyncGetCallTrace, Gradle, Criterion, Python compatibility lab, Self-hosted CI on k3s
STATUS
In development. The repository stays private until it's ready. Role: author.