This directory contains small, compilable programs that each demonstrate one
JVMTI, JNI, class-file, or embedding concern. They use only public
jvmti-bindings APIs and intentionally do not depend on external
instrumentation projects.
Build one agent as a shared library:
cargo build --release --example thread_lifecycleOn Linux, load it at JVM startup:
java -agentpath:./target/release/examples/libthread_lifecycle.so MyAppmacOS uses libthread_lifecycle.dylib; Windows uses
thread_lifecycle.dll. Agent options follow the library path after =:
java -agentpath:./target/release/examples/libfield_watch.so=class=Lcom/example/State\;,field=value MyAppShells treat ; specially, so quote or escape option strings when needed.
For a real agent project, copy the relevant source into a library crate with:
[lib]
crate-type = ["cdylib"]
[dependencies]
jvmti-bindings = "3"| Example | Purpose |
|---|---|
minimal |
Smallest load, VM event, and unload agent |
vm_lifecycle |
Distinguish VM start, initialization, and death |
attach_logger |
Handle Agent_OnAttach options |
class_logger |
Observe class bytes and names |
method_counter |
Configure method entry/exit and count callbacks |
profiler |
Minimal method-entry profiler skeleton |
tracer |
Minimal class-load tracer skeleton |
| Example | Purpose | Notes |
|---|---|---|
thread_lifecycle |
Count platform-thread starts and ends | Allocation-free hot callbacks |
virtual_thread_lifecycle |
Count virtual-thread starts and ends | Requires a runtime exposing JVMTI virtual-thread support |
thread_inventory |
Count threads at VM initialization | Releases returned JNI local references |
monitor_contention |
Count waits, timeouts, and contended monitor acquisition | Requests monitor-event capability |
| Example | Purpose | Notes |
|---|---|---|
exception_tracker |
Count thrown and caught exceptions | High-volume event family |
method_exit_values |
Separate normal returns from exception pops | Does not guess the type of raw jvalue |
field_watch |
Install read/write watchpoints on one field | Options: class=..., field=... |
breakpoint |
Break at the first location of one method | Options: class=..., method=... |
Watchpoints and breakpoints normally resolve their target during
ClassPrepare, where name lookup is safe and bounded. An attach-time design
must also inventory classes that were loaded earlier, but only when the live
JVM still advertises every required capability; HotSpot keeps several debugging
capabilities OnLoad-only.
| Example | Purpose | Notes |
|---|---|---|
heap_sampler |
Sample allocations at a configured byte interval | Lower overhead than every-allocation events |
allocation_tracker |
Count every reported VM allocation and byte size | Intentionally expensive |
gc_observer |
Count GC starts and finishes | No JNI calls from GC callbacks |
resource_exhaustion |
Preserve count and flags for later reporting | No allocation or I/O in the exhaustion callback |
data_dump_request |
Receive JVM data-dump requests | Real agents should hand work to an agent-owned worker |
| Example | Purpose | Notes |
|---|---|---|
native_method_bind |
Observe native binding decisions | Does not replace function addresses |
jit_code_events |
Count compiled loads, unloads, generated blocks, and bytes | Preserves typed JIT callback payloads |
event_abi_smoke |
Exercise callbacks around reserved table slots | Release-gate support example |
| Example | Purpose | Notes |
|---|---|---|
jni_string_roundtrip |
Use callback-scoped JNI and an RAII local frame | Includes NUL and Unicode MUTF-8 content |
modified_utf8_roundtrip |
Encode/decode Java Modified UTF-8 without a JVM | Run with cargo run --example modified_utf8_roundtrip |
classfile_inspector |
Parse selected class bytes without transforming them | Option: prefix=com/example/ |
class_inventory |
Count loaded classes at VM initialization | Releases returned JNI local references |
version_support |
Print the verified JDK range and release profile | Run with cargo run --example version_support |
embed |
Start a JVM and call Java from Rust | Run with cargo run --example embed --features embed |
These examples are deliberately standalone and mapping-agnostic. They do not contain or derive from any private instrumentor or RASP implementation.
| Example | Purpose | Options |
|---|---|---|
minecraft_class_activity |
Count class files and bytes under a package prefix | prefix=net/minecraft/ |
minecraft_tick_breakpoint |
Count calls to a chosen server tick method | class=L...;,method=... |
minecraft_bullet_time |
Hold F8 and scroll down to slow a chosen client tick or up to restore speed | tick_class=..., scroll_class=..., keyboard_class=..., activation_key=297, step_ms=10, max_delay_ms=250 |
Minecraft class and method names vary by release, loader, and mapping set. Pass the names for the exact runtime being observed. Breakpoints are useful for a small diagnostic probe; production profiling usually needs sampling or narrow instrumentation to avoid event overhead.
minecraft_bullet_time observes Java-side keyboard and scroll callbacks
supplied by the game/LWJGL and is therefore independent of the host window
system. By default, only F8 (GLFW_KEY_F8, value 297) arms the mouse wheel.
Armed wheel input is consumed before normal hotbar handling. Its default
Mojang-mapped names are illustrative. The configured callback signatures and
local slots must match the exact game version and mapping set. It deliberately
sleeps on the selected tick thread, making it a toy and diagnostic demonstration
rather than a production agent. Options are parsed strictly: unknown,
duplicate, malformed, or negative-slot values reject agent initialization
instead of silently selecting defaults.
Build it and load it when the client JVM starts:
cargo build --release --example minecraft_bullet_time
java -agentpath:/absolute/path/to/libminecraft_bullet_time.so [launcher options]The agent enables VMInit and inventories already-prepared classes there, then
keeps ClassPrepare enabled for later Minecraft classes. It also implements an
attach path for JVMs that advertise breakpoint and local-variable capabilities
in the live phase. OpenJDK HotSpot 8 and 28 do not advertise either capability
then, so dynamic attach fails fast with an instruction to use startup
-agentpath; it does not pretend that a loaded-class scan can overcome the
capability boundary.
The example keeps its breakpoints for the lifetime of the process. JVM TI marks
ClearBreakpoint as live-phase-only and not callback-safe, so VMDeath is not
a valid place to retrofit teardown. A reusable agent would expose an earlier
agent-owned shutdown command, disable events, clear each breakpoint on that
control path, and only then relinquish capabilities.
Use libminecraft_bullet_time.dylib on macOS and
minecraft_bullet_time.dll on Windows. Supply comma-separated agent options
after = when the runtime uses different mappings or local-variable slots:
-agentpath:/path/to/agent=keyboard_method=keyPress,activation_key=297,step_ms=5,max_delay_ms=200
At runtime, hold F8 while moving the wheel. Scrolling down increases the delay; scrolling up decreases it. An unarmed wheel event is not modified. If any configured method or local-variable slot does not match, installation logs name the target and discovery path. The VM-death summary reports read and consumption totals split into invalid-slot, type-mismatch, opaque-frame, and other local access failures.
On Linux or macOS, the repository's deterministic Java fixture exercises those
semantics, the already-loaded VMInit scan, and the HotSpot dynamic-attach
capability boundary end to end:
scripts/prove-minecraft-bullet-time-live.shWith no arguments, the proof uses a valid JAVA_HOME or derives the home of
the active java runtime. Pass one or more JDK homes to exercise an explicit
matrix; every explicitly requested home must contain both bin/java and
bin/javac or the proof fails.
| Example | Purpose |
|---|---|
callback_idle_bench |
Agent-loaded baseline without enabled hot callbacks |
callback_noop_bench |
No-op callback-dispatch cost |
callback_counter_bench |
Relaxed-atomic counter callback cost |
callback_allocation_audit |
Prove the selected callback path does not allocate |
Use the repository benchmark and release scripts rather than interpreting one ad hoc JVM run as a stable performance result.
- Treat every raw JNI/JVMTI handle as callback- and VM-scoped unless the JVM specification explicitly grants a longer lifetime.
- Do not retain a callback
JNIEnv; it belongs to the current JVM thread. - Request capabilities before enabling dependent events.
- Keep high-frequency callbacks bounded and preferably allocation-free.
- Do not decode a
jvalueunion without the corresponding Java descriptor. - Avoid JNI and non-GC-safe JVMTI operations in GC/object-free callbacks.
- Match every owned JVMTI allocation, JNI reference, monitor, and embedded VM attachment with its documented release operation.