#[repr(transparent)]pub struct JavaVM(_);
Expand description
The Java VM, providing Invocation API support.
The JavaVM can be obtained either via JNIEnv#get_java_vm
in an already attached
thread, or it can be launched from Rust via JavaVM#new
.
Attaching Native Threads
A native thread must «attach» itself to be able to call Java methods outside of a native Java method. This library provides two modes of attachment, each ensuring the thread is promptly detached:
- A scoped attachment with
attach_current_thread
. The thread will automatically detach itself once the returned guard is dropped. - A permanent attachment with
attach_current_thread_permanently
orattach_current_thread_as_daemon
. The thread will automatically detach itself before it terminates.
As attachment and detachment of a thread is an expensive operation, the scoped attachment
shall be used if happens infrequently. If you have an undefined scope where you need
to use JNIEnv
and cannot keep the AttachGuard
, consider attaching the thread
permanently.
Local Reference Management
Remember that the native thread attached to the VM must manage the local references
properly, i.e., do not allocate an excessive number of references and release them promptly
when they are no longer needed to enable the GC to collect them. A common approach is to use
an appropriately-sized local frame for larger code fragments
(see with_local_frame
and Executor)
and auto locals in loops.
See also the JNI specification for details on referencing Java objects.
Executor
Jni-rs provides an Executor
— a helper struct that allows to
execute a closure with JNIEnv
. It combines the performance benefits of permanent attaches
and automatic local reference management. Prefer it to manual permanent attaches if
they happen in various parts of the code to reduce the burden of local reference management.
Launching JVM from Rust
To launch a JVM from a native process, enable the invocation
feature in the Cargo.toml:
jni = { version = "0.21.1", features = ["invocation"] }
The application will be able to use JavaVM::new
which will dynamically
load a jvm
library (which is distributed with the JVM) at runtime:
// Build the VM properties
let jvm_args = InitArgsBuilder::new()
// Pass the JNI API version (default is 8)
.version(JNIVersion::V8)
// You can additionally pass any JVM options (standard, like a system property,
// or VM-specific).
// Here we enable some extra JNI checks useful during development
.option("-Xcheck:jni")
.build()
.unwrap();
// Create a new VM
let jvm = JavaVM::new(jvm_args)?;
// Attach the current thread to call into Java — see extra options in
// "Attaching Native Threads" section.
//
// This method returns the guard that will detach the current thread when dropped,
// also freeing any local references created in it
let mut env = jvm.attach_current_thread()?;
// Call Java Math#abs(-10)
let x = JValue::from(-10);
let val: jint = env.call_static_method("java/lang/Math", "abs", "(I)I", &[x])?
.i()?;
assert_eq!(val, 10);
At runtime, the JVM installation path is determined via the java-locator crate:
- By the
JAVA_HOME
environment variable, if it is set. - Otherwise — from
java
output.
It is recommended to set JAVA_HOME
For the operating system to correctly load the jvm
library it may also be
necessary to update the path that the OS uses to find dependencies of the
jvm
library.
- On Windows, append the path to
$JAVA_HOME/bin
to thePATH
environment variable. - On MacOS, append the path to
libjvm.dylib
toLD_LIBRARY_PATH
environment variable. - On Linux, append the path to
libjvm.so
toLD_LIBRARY_PATH
environment variable.
The exact relative path to jvm
library is version-specific.
Implementations§
source§impl JavaVM
impl JavaVM
sourcepub fn new(args: InitArgs<'_>) -> StartJvmResult<Self>
pub fn new(args: InitArgs<'_>) -> StartJvmResult<Self>
Launch a new JavaVM using the provided init args.
Unlike original JNI API, the main thread (the thread from which this method is called) will
not be attached to JVM. You must explicitly use attach_current_thread…
methods (refer
to Attaching Native Threads section).
This API requires the “invocation” feature to be enabled, see “Launching JVM from Rust”.
This will attempt to locate a JVM using
[java-locator], if the JVM has not already been loaded. Use the
with_libjvm
method to give an explicit location for the JVM shared
library (jvm.dll
, libjvm.so
, or libjvm.dylib
, depending on the platform).
sourcepub fn with_libjvm<P: AsRef<OsStr>>(
args: InitArgs<'_>,
libjvm_path: impl FnOnce() -> StartJvmResult<P>
) -> StartJvmResult<Self>
pub fn with_libjvm<P: AsRef<OsStr>>( args: InitArgs<'_>, libjvm_path: impl FnOnce() -> StartJvmResult<P> ) -> StartJvmResult<Self>
Launch a new JavaVM using the provided init args, loading it from the given shared library file if it’s not already loaded.
Unlike original JNI API, the main thread (the thread from which this method is called) will
not be attached to JVM. You must explicitly use attach_current_thread…
methods (refer
to Attaching Native Threads section).
This API requires the “invocation” feature to be enabled, see “Launching JVM from Rust”.
The libjvm_path
parameter takes a closure which returns the path to the JVM shared
library. The closure is only called if the JVM is not already loaded. Any work that needs
to be done to locate the JVM shared library should be done inside that closure.
sourcepub unsafe fn from_raw(ptr: *mut JavaVM) -> Result<Self>
pub unsafe fn from_raw(ptr: *mut JavaVM) -> Result<Self>
Create a JavaVM from a raw pointer.
Safety
Expects a valid pointer retrieved from the JNI_CreateJavaVM
JNI function. Only does null check.
sourcepub fn get_java_vm_pointer(&self) -> *mut JavaVM
pub fn get_java_vm_pointer(&self) -> *mut JavaVM
Returns underlying sys::JavaVM
interface.
sourcepub fn attach_current_thread_permanently(&self) -> Result<JNIEnv<'_>>
pub fn attach_current_thread_permanently(&self) -> Result<JNIEnv<'_>>
Attaches the current thread to the JVM. Calling this for a thread that is already attached is a no-op.
The thread will detach itself automatically when it exits.
Attached threads block JVM exit. If it is not desirable — consider using
attach_current_thread_as_daemon
.
sourcepub fn attach_current_thread(&self) -> Result<AttachGuard<'_>>
pub fn attach_current_thread(&self) -> Result<AttachGuard<'_>>
Attaches the current thread to the Java VM. The returned AttachGuard
can be dereferenced to a JNIEnv
and automatically detaches the thread
when dropped. Calling this in a thread that is already attached is a no-op, and
will neither change its daemon status nor prematurely detach it.
Attached threads block JVM exit.
Attaching and detaching a thread is an expensive operation. If you use it frequently
in the same threads, consider either attaching them permanently,
or, if the scope where you need the JNIEnv
is well-defined, keeping the returned guard.
sourcepub unsafe fn detach_current_thread(&self)
pub unsafe fn detach_current_thread(&self)
Explicitly detaches the current thread from the JVM.
Note: This operation is rarely appropriate to use, because the attachment methods ensure that the thread is automatically detached.
Detaching a non-attached thread is a no-op.
To support the use of JavaVM::destroy()
it may be necessary to use this API to
explicitly detach daemon threads before JavaVM::destroy()
is called because
JavaVM::destroy()
does not synchronize and wait for daemon threads.
Any daemon thread that is still “attached” after JavaVM::destroy()
returns would
cause undefined behaviour if it then tries to make any JNI calls or tries
to detach itself.
Normally jni-rs
will automatically detach threads from the JavaVM
by storing
a guard in thread-local-storage that will detach on Drop
but this will cause
undefined behaviour if JavaVM::destroy()
has been called.
Calling this will clear the thread-local-storage guard and detach the thread early to avoid any attempt to automatically detach when the thread exits.
Safety
Any existing JNIEnv
s and AttachGuard
s created in the calling thread
will be invalidated after this method completes. It is the caller’s responsibility
to ensure that no JNI calls are subsequently performed on these objects.
Failure to do so will result in unspecified errors, possibly, the process crash.
Given some care is exercised, this method can be used to detach permanently attached
threads before they exit (when automatic detachment occurs). However, it is
never appropriate to use it with the scoped attachment (attach_current_thread
).
sourcepub fn attach_current_thread_as_daemon(&self) -> Result<JNIEnv<'_>>
pub fn attach_current_thread_as_daemon(&self) -> Result<JNIEnv<'_>>
Attaches the current thread to the Java VM as a daemon. Calling this in a thread that is already attached is a no-op, and will not change its status to a daemon thread.
The thread will detach itself automatically when it exits.
sourcepub fn threads_attached(&self) -> usize
pub fn threads_attached(&self) -> usize
Returns the current number of threads attached to the JVM.
This method is provided mostly for diagnostic purposes.
sourcepub fn get_env(&self) -> Result<JNIEnv<'_>>
pub fn get_env(&self) -> Result<JNIEnv<'_>>
Get the JNIEnv
associated with the current thread, or
ErrorKind::Detached
if the current thread is not attached to the java VM.
sourcepub unsafe fn destroy(&self) -> Result<()>
pub unsafe fn destroy(&self) -> Result<()>
Unloads the JavaVM and frees all it’s associated resources
Firstly if this thread is not already attached to the JavaVM
then
it will be attached.
This thread will then wait until there are no other non-daemon threads
attached to the JavaVM
before unloading it (including threads spawned
by Java and those that are attached via JNI)
Safety
IF YOU ARE USING DAEMON THREADS THIS MAY BE DIFFICULT TO USE SAFELY!
Daemon thread rules
Since the JNI spec makes it clear that DestroyJavaVM
will not wait for
attached deamon threads to exit, this also means that if you do have any
attached daemon threads it is your responsibility to ensure that they
don’t try and use JNI after the JavaVM
is destroyed and you won’t be able
to detach them after the JavaVM
has been destroyed.
This creates a very unsafe hazard in jni-rs
because it normally automatically
ensures that any thread that gets attached will be detached before it exits.
Normally jni-rs
will automatically detach threads from the JavaVM
by storing
a guard in thread-local-storage that will detach on Drop
but this will cause
undefined behaviour if JavaVM::destroy()
has been called before the thread
exits.
To clear this thread-local-storage guard from daemon threads you can call
JavaVM::detach_current_thread()
within each daemon thread, before calling
this API.
Calling this will clear the thread-local-storage guard and detach the thread early to avoid any attempt to automatically detach when the thread exits.
Don’t call from a Java native function
There must be no Java methods on the call stack when JavaVM::destroy()
is called.
Drop all JNI state, including auto-release types before calling JavaVM::destroy()
There is currently no 'vm
lifetime associated with a JavaVM
that
would allow the borrow checker to enforce that all jni
resources
associated with the JavaVM
have been released.
Since these JNI resources could lead to undefined behaviour through any
use after the JavaVM
has been destroyed then it is your responsibility
to release these resources.
In particular, there are numerous auto-release types in the jni
API
that will automatically make JNI calls within their Drop
implementation. All such types must be dropped before destroy()
is
called to avoid undefined bahaviour.
Here is an non-exhaustive list of auto-release types to consider:
AttachGuard
AutoElements
AutoElementsCritical
AutoLocal
GlobalRef
JavaStr
JMap
WeakRef
Invalid JavaVM
on return
After destroy()
returns then the JavaVM
will be in an undefined state
and must be dropped (e.g. via std::mem::drop()
) to avoid undefined behaviour.
This method doesn’t take ownership of the JavaVM
before it is
destroyed because the JavaVM
may have been shared (E.g. via an Arc
)
between all the threads that have not yet necessarily exited before this
is called.
So although the JavaVM
won’t necessarily be solely owned by this
thread when destroy()
is first called it will conceptually own the
JavaVM
before destroy()
returns.