Skip to content

Smart References

Eugene Gershnik edited this page Oct 9, 2021 · 3 revisions

In raw JNI references to Java objects come in 4 flavors

  • Automatic. These are local references (see next type below) that are owned by the currently active reference frame. For this to work someone needs to establish such frame. This is always done for you by JNI prior to calling a native method implementation, so anything called from there will operate on this same frame unless you create your own ones in-between. You do not not need to manage automatic references yourself (hence the name) but relying on the current frame is extremely dangerous. The size of the frame is fixed and can be pretty small. Since you share the frame with all the callers above you it is very easy to run out of space there, especially if you do something in a loop. The main advantage of automatic references is that they are deallocated in-bulk offering potential speed savings. Each non-automatic local reference needs to be deallocated via its own JNI call. (For those familiar with old pre-ARC Objective C, automatic references and reference frame behave very similarly to autorelease pool and share the same gotchas)
  • Local. This is the type of references to any new object created by JNI. They are local to current call and cannot be used outside of it. In other words you can pass them to functions you call and they do the same in turn but you can not store them for later use elsewhere. If there is a reference frame active when a local reference is created it will track the reference and dispose of it automatically. In other words such local reference will be automatic. However, due to the issues with automatic references mentioned above you should manually dispose of them.
  • Global. You can convert any local reference to a global one. Global references can be stored for later use. However, most JNI implementations have some kind of limit to how many global references there can be in total. The limit is usually much larger than automatic reference frame one but not infinite. Trying to "keep" too many Java objects in C++ code via global reference can run out of this limit. For this reason it is better to limit use of global reference to a few key objects, if any.
  • Weak. A kind of global reference that does not prevent garbage collection of the referent.

SimpleJNI follows the philosophy of being safe by default and so it avoid the use of automatic references by default.

References in SimpleJNI are represented by "smart reference" objects. These are RAII wrappers over the raw JNI references. These are

auto_java_ref<T>
local_java_ref<T> 
global_java_ref<T>
weak_java_ref<T>

local_java_ref is by far the most common type you will encounter. Any SimpleJNI method that returns Java objects will produce local_java_ref<jSomething> instead of raw jSomething. When this object goes out of scope it will manually delete the local reference. Yes, it is potentially "slower" than relying on a frame but it is also much safer. You can still get the frame semantics if you want, but you will need to manually indicate so. In other words safe behavior - default; unsafe, but potentially faster behavior - manually invoked. The following example illustrates various usage patterns

jSomething foo(JNIEnv * env)
{
   //Create new object. You can use auto for return type
   java_local_ref<jSomething> obj = java_classes::get<ClassOfSomething>().ctor(env);
   //Pass obj to a method that expects raw jSomething
   bar(obj.c_ptr());

   //Return obj as raw jSomething
   return obj.release();
}

jSomething foo_advanced(JNIEnv * env)
{
   //Create new object. We want to rely on automatic frame here
   jSomething obj = java_classes::get<ClassOfSomething>().ctor(env).release();
   //Pass obj to a method that expects raw jSomething
   bar(obj);

   //Return obj
   return obj;
}

Note that java_local_ref instances must not outlive the scope in which they were created. It is almost always an error to see them as class members (unless the class they are member of also lives only within the scope it is created) or global variables.

Accessing raw pointer

There are 2 methods on java_local_ref that return raw pointer c_ptr() and release(). The first one produces "raw" C pointer as the name indicates. It should be used to pass the object to any method you call that expects a raw pointer. The release() method relinquishes ownership of the pointer. It should be used to return the object as a raw pointer.

Other pointer operations

Just like any other smart pointer java_local_ref can be assigned (to another java_local_ref or different smart reference types) and checked for being not nullptr via

if (obj)
{}

//and
if (!obj)
{}

Global and weak references

The java_global_ref and java_weak_ref behave identically to java_local_ref but manage global and weak JNI references respectively. They are never produced by any SimpleJNI call - you need to manually create them. Unlike java_local_ref these can be class instance variables. The java_weak_ref behaves somewhat differently to other smart reference classes. Despite the fact that it's template argument is T when you extract raw pointer from it you will get a jweak object. When you convert it to bool you will check whether jweak stored inside is nullptr or not. In order to actually access the Java object you will need to convert it to one of the strong pointer classes and check that the result is not null.

Conversions

All 4 smart reference classes support automatic conversions to each other. Conversions always work when the template argument T is the same. When the argument is not the same the conversions will work if java conversion between two types is defined as described in Declaring Java Types#conversions

Purpose of auto_java_ref class

On occasion you might want to write a method that accepts either local or global smart reference class without caring what it is. In such situation you could of course fall back on raw jSomething but doing so would prevent you from using automatic conversions facility described above. Instead you can use auto_java_ref. This smart reference class participates in all the conversions but does not perform any reference management of its own. Instead it assumes that someone else, above in the calling hierarchy owns the object and will dispose of it as needed. Consider

DEFINE_JAVA_TYPE(jHashMap,    "java.util.HashMap");
DEFINE_JAVA_TYPE(jMap,        "java.util.Map"); 
DEFINE_JAVA_CONVERSION(jHashMap, jMap);

void bar(const java_auto_ref<jMap> & map)
{
    ...use map...
}

void foo()
{
    local_java_ref<jHashMap> local = java_classes::get<ClassOfHashMap>().ctor();
    bar(local);
    global_java_ref<jHashMap> global = local;
    bar(global);
}

Note that there are no explicit cast calls anywhere in the code above.

Obtaining smart references from raw references

In order to obtain smart references from raw ones you can use the following calls

template<typename T>
auto_java_ref<T> jauto(T ptr) noexcept;

//Creates a new local reference.    
template<typename T>
local_java_ref<T> jref(JNIEnv * env, T ptr) noexcept;

//Attaches an object to an existing local reference.
template<typename T>
local_java_ref<T> jattach(JNIEnv * env, T ptr) noexcept;

//Creates a new global reference.    
template<typename T>
global_java_ref<T> jglobal_ref(T ptr);

//Attaches an object to an existing global reference.
template<typename T>
global_java_ref<T> jglobal_attach(T ptr);

//Creates a new weak reference.        
template<typename T>
weak_java_ref<T> jweak_ref(T ptr);

//Attaches an object to an existing weak reference.
template<typename T>
weak_java_ref<T> jweak_attach(T ptr);

Creating local frames

If you do want use local reference frames SimpleJNI provides a RAII wrapper to manage them.

void foo(JNIEnv * env)
{
    //Let's use a simple frame that will wipe everything when destroyed
    java_frame frame(env, 64); //size of the frame

    ...use up to 64 automatic references here...
}

jSomething foo(JNIEnv * env)
{
    //Let's use a frame that will return an object at the end
    java_frame frame(env, 64); //size of the frame

    ...use up to 64 automatic references here...
    jSomething obj = ....;
    
    //Clear the frame but retain one object
    return frame.pop(obj);
}