Struct jni::JavaVM

source ·
#[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:

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:

  1. By the JAVA_HOME environment variable, if it is set.
  2. 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 the PATH environment variable.
  • On MacOS, append the path to libjvm.dylib to LD_LIBRARY_PATH environment variable.
  • On Linux, append the path to libjvm.so to LD_LIBRARY_PATH environment variable.

The exact relative path to jvm library is version-specific.

Implementations§

source§

impl JavaVM

source

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

source

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.

source

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.

source

pub fn get_java_vm_pointer(&self) -> *mut JavaVM

Returns underlying sys::JavaVM interface.

source

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.

source

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.

source

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 JNIEnvs and AttachGuards 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).

source

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.

source

pub fn threads_attached(&self) -> usize

Returns the current number of threads attached to the JVM.

This method is provided mostly for diagnostic purposes.

source

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.

source

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.

Trait Implementations§

source§

impl Debug for JavaVM

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Send for JavaVM

source§

impl Sync for JavaVM

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere T: ?Sized,

const: unstable · source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere T: ?Sized,

const: unstable · source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

const: unstable · source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for Twhere U: From<T>,

const: unstable · source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T, U> TryFrom<U> for Twhere U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
const: unstable · source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
const: unstable · source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.