Expand description
§jvmti
Complete JNI and JVMTI bindings for Rust with zero third-party crate dependencies.
This crate provides everything you need to build JVM agents in Rust:
- Low-level FFI bindings to JNI and JVMTI
- High-level wrappers with ergonomic Rust APIs
- The
Agenttrait andexport_agent!macro for easy agent creation
§Version 3 Migration Notice
Version 3.0 is intentionally source-breaking from 2.x. Version 2.4.0 was not published because the planned ABI, callback, ownership, and lifecycle corrections required a major-version release under semantic versioning. Existing agents must migrate their callback implementations and review all affected safety and ownership contracts rather than changing only the Cargo dependency version. See the complete 2.x to 3.0 migration guide before upgrading a production agent.
§Features
- Complete Coverage: Complete JDK 28 JNI and JVM TI function tables
- Zero Third-Party Crates: Including optional features, tests, tools, and benchmarks
- Ergonomic API: High-level wrappers handle strings, arrays, references
- Type-Safe: Proper Rust types,
Resultreturns, RAII guards - Release-Aware: source-ABI verified against pinned OpenJDK 8-28 revisions and live callback-tested through JDK 28 preview
§Quick Start
Create a minimal agent in 4 steps:
1. Create a new library crate:
cargo new --lib my_agent2. Configure Cargo.toml:
[lib]
crate-type = ["cdylib"]
[dependencies]
jvmti-bindings = "3"3. Implement your agent (src/lib.rs):
use jvmti_bindings::prelude::*;
#[derive(Default)]
struct MyAgent;
impl Agent for MyAgent {
fn on_load(&self, context: AgentLoadContext<'_>) -> jni::jint {
println!("[MyAgent] Loaded with options: {:?}", context.options_lossy());
let Ok(jvmti) = context.vm().jvmti() else {
return jni::JNI_ERR;
};
if jvmti.set_default_agent_callbacks().is_err()
|| jvmti.enable_vm_lifecycle_events().is_err()
{
return jni::JNI_ERR;
}
jni::JNI_OK
}
fn vm_init(&self, _context: CallbackContext<'_>, _event: ThreadEvent) {
println!("[MyAgent] VM initialized!");
}
fn vm_death(&self, _context: CallbackContext<'_>) {
println!("[MyAgent] VM shutting down");
}
}
export_agent!(MyAgent);4. Build and run:
cargo build --release
java -agentpath:./target/release/libmy_agent.so=myoptions MyApp§Architecture
The crate is organized in layers:
┌─────────────────────────────────────────────────────────┐
│ Your Agent Code │
│ impl Agent for MyAgent { ... } │
├─────────────────────────────────────────────────────────┤
│ Agent Trait + Macros │
│ Agent, export_agent!, get_default_callbacks() │
├─────────────────────────────────────────────────────────┤
│ High-Level Wrappers (env module) │
│ env::Jvmti - JVMTI operations with Result returns │
│ env::JniEnv - JNI operations with string helpers │
│ env::LocalRef, GlobalRef, WeakGlobalRef - RAII guards │
├─────────────────────────────────────────────────────────┤
│ Raw FFI Bindings (sys module) │
│ sys::jni - JNI types, JDK 28 vtable │
│ sys::jvmti - JVMTI types, vtable (156 functions) │
└─────────────────────────────────────────────────────────┘§Modules
| Module | Purpose |
|---|---|
sys::jni | Raw JNI types and vtable (for FFI) |
sys::jvmti | Raw JVMTI types, vtable, capabilities, events |
env | High-level wrappers - start here for ergonomic APIs |
env::Jvmti | High-level JVM TI operations with Result returns |
env::JniEnv | Complete fixed-signature JNI operations (A invocation) |
classfile | Typed JVMS-standard attributes through Java 28; opaque unknown attributes |
mutf8 | Java Modified UTF-8 and exact UTF-16 conversions |
prelude | Recommended imports for agents |
embed | Optional JVM embedding helpers (embed feature) |
advanced | Feature-gated advanced helpers (heap graph utilities) |
§Enabling JVMTI Events
To receive JVMTI events, you must request capabilities, register callbacks,
and enable notifications. export_agent! does not do those steps.
Helpers such as env::Jvmti::configure_class_file_load_hook_agent perform
all three for common workflows:
use jvmti_bindings::prelude::*;
#[derive(Default)]
struct ClassMonitor;
impl Agent for ClassMonitor {
fn on_load(&self, context: AgentLoadContext<'_>) -> jni::jint {
let Ok(jvmti_env) = context.vm().jvmti() else {
return jni::JNI_ERR;
};
// Capabilities, default callbacks, and ClassFileLoadHook enablement.
if jvmti_env.configure_class_file_load_hook_agent().is_err() {
return jni::JNI_ERR;
}
jni::JNI_OK
}
fn class_file_load_hook(
&self,
_context: CallbackContext<'_>,
_event: ClassFileLoadHookEvent<'_>,
) {
// Called for every class load!
}
}
export_agent!(ClassMonitor);§Working with JNI
Use env::JniEnv for ergonomic JNI operations:
use jvmti_bindings::prelude::*;
fn print_message(jni: &JniEnv) {
// Find a class
let Some(system_class) = jni.find_class("java/lang/System") else {
return;
};
// Get a static field
let Some(out_field) = (unsafe {
jni.get_static_field_id(system_class, "out", "Ljava/io/PrintStream;")
}) else {
return;
};
let out = unsafe { jni.get_static_object_field(system_class, out_field) };
// Create a Java string
let Some(message) = jni.new_string_utf("Hello from Rust!") else {
return;
};
// Call a method
let Some(print_class) = jni.find_class("java/io/PrintStream") else {
return;
};
let Some(println_method) = (unsafe {
jni.get_method_id(print_class, "println", "(Ljava/lang/String;)V")
}) else {
return;
};
unsafe { jni.call_void_method(out, println_method, &[jni::jvalue { l: message }]) };
// Check for exceptions
if jni.exception_check() {
jni.exception_describe();
jni.exception_clear();
}
}§Version Compatibility
| JDK Version | JNI Functions | JVMTI Functions | Notes |
|---|---|---|---|
| 8 | 233 | 155 | Supported baseline |
| 9 | 234 | 155 | +GetModule, +module functions |
| 11 | 234 | 156 | +SetHeapSamplingInterval |
| 21 | 235 | 156 | +IsVirtualThread, virtual threads final |
| 24 | 236 | 156 | +GetStringUTFLengthAsLong |
| 25 | 236 | 156 | +ClearAllFramePops (slot 67) |
| 28 preview | 237 | 156 | +HasIdentity, value-object semantics |
Re-exports§
pub use crate::sys::jni;
Modules§
- advanced
- Advanced helpers for JVMTI power users.
- agent
- Callback-scoped agent lifecycle inputs.
- callbacks
- Callback-scoped contexts and complete JVMTI event payloads.
- classfile
- Class file parser for Java 8 through 28.
- embed
- Helpers for embedding a JVM inside a Rust process.
- env
- High-level environment wrappers for JVMTI and JNI.
- mutf8
- Java Modified UTF-8 encoding and decoding.
- prelude
- Common imports for building JVMTI agents.
- sys
- version
- Release-aware compatibility data for JNI and JVM TI evolution.
Macros§
- export_
agent - Exports your agent type as a loadable JVMTI agent library.
- jni_
call - Helper to call JNI functions through the vtable. env_ptr: *mut JNIEnv = *mut *const JNINativeInterface_ *env_ptr: *const JNINativeInterface_ (vtable pointer) **env_ptr: JNINativeInterface_ (vtable itself) Usage: jni_call!(env, FindClass, b“java/lang/String\0“.as_ptr() as *const c_char)
- jvm_
call - Helper to call JavaVM functions through the vtable. vm_ptr: *mut JavaVM = *mut *const JNIInvokeInterface_ *vm_ptr: *const JNIInvokeInterface_ (vtable pointer) **vm_ptr: JNIInvokeInterface_ (vtable itself)
Structs§
- Global
Agent Already Set - The process-global agent has already been initialized.
Statics§
Traits§
- Agent
- The core trait for implementing a JVMTI agent.
Functions§
- describe_
jni_ result - Return a display-ready JNI result string, e.g.
JNI_EDETACHED (-2). - get_
default_ callbacks - Returns a pre-configured
jvmtiEventCallbacksstruct with all event trampolines wired up. - set_
global_ agent - Helper to initialize the global agent (called by the macro)