Skip to main content

Crate jvmti_bindings

Crate jvmti_bindings 

Source
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 Agent trait and export_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, Result returns, 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_agent

2. 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

ModulePurpose
sys::jniRaw JNI types and vtable (for FFI)
sys::jvmtiRaw JVMTI types, vtable, capabilities, events
envHigh-level wrappers - start here for ergonomic APIs
env::JvmtiHigh-level JVM TI operations with Result returns
env::JniEnvComplete fixed-signature JNI operations (A invocation)
classfileTyped JVMS-standard attributes through Java 28; opaque unknown attributes
mutf8Java Modified UTF-8 and exact UTF-16 conversions
preludeRecommended imports for agents
embedOptional JVM embedding helpers (embed feature)
advancedFeature-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 VersionJNI FunctionsJVMTI FunctionsNotes
8233155Supported baseline
9234155+GetModule, +module functions
11234156+SetHeapSamplingInterval
21235156+IsVirtualThread, virtual threads final
24236156+GetStringUTFLengthAsLong
25236156+ClearAllFramePops (slot 67)
28 preview237156+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§

GlobalAgentAlreadySet
The process-global agent has already been initialized.

Statics§

GLOBAL_AGENT

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 jvmtiEventCallbacks struct with all event trampolines wired up.
set_global_agent
Helper to initialize the global agent (called by the macro)