The jni design specs (see section Global and local references) explains that:
- all references passed to a native method as well as all references returned by JNI functions are local references;
- these local references are automatically freed after the native method returns.
However, this principle applies only to the case of a C++ function called by Java: the local references are released when the function returns to Java (see section Implementing local references).
If you create objects or get references from JNI functions in C++ code that is not called from Java, the local references ARE NOT FREED AUTOMATICALLY, causing memory to leak.
So you should better use DeleteLocalRef()
to free java objects created/obtained from C++ but no longer used.
Demonsration:
The following simple Java code allocates big objects without keeping their references, and call some memory inspection:
class BigMemoryConsumer {
private char myTable[];
public BigMemoryConsumer () { // Allocate and use 1Mb
myTable = new char[1048576];
for (int i=0; i<1048576; i++)
myTable[i] = (char) (i % 256);
}
public static long showMem() { // Show memory statistics
int funit = 1024*1024;
String unit = " Mb";
Runtime runtime = Runtime.getRuntime();
System.gc(); // opportunity to run garbage collector (not guaranteed !)
long used = runtime.totalMemory() - runtime.freeMemory();
System.out.println("Used Memory: " + used / funit + unit
+ " ("+ (used*100/runtime.maxMemory())+"%)");
System.out.println("Free Memory: " + runtime.freeMemory() / funit + unit);
System.out.println("Total Memory: " + runtime.totalMemory() / funit + unit);
System.out.println("Max Memory: " + runtime.maxMemory() / funit + unit);
System.out.println("");
return used;
}
public static void main (String[] args) { // test in java
long lastmem = showMem();
for (int i=0; i<256; i++) {
BigMemoryConsumer m = new BigMemoryConsumer();
long mem = showMem();
if (mem<=lastmem) {
System.out.println ("Garbage collector freed some memory");
return;
}
else lastmem = mem;
}
}
}
When you run this class directly from Java (aka main()
) You'll notice that Java will very quicly (second or third iteration on my 64bit system) run the garbage collector: the object m is reinitialised on every iteration, meaning that previously created objects are no longer referenced.
Now I've reproduced the equivalent main()
in C++ code, just after having loaded the JVM and initialized the JNI environment:
jclass cls = env->FindClass("BigMemoryConsumer");
jmethodID ctor = env->GetMethodID(cls, "<init>", "()V"); // find consructor
jmethodID show = env->GetStaticMethodID(cls, "showMem", "()J"); // find memShow()
jlong lastmem = 0;
vector<jobject> v;
for(int i = 0; i < 256; i++) {
jobject myo = env->NewObject(cls, ctor);
v.push_back(myo);
jlong mem = env->CallStaticLongMethod(cls, show);
if(mem <= lastmem) {
cout << "ATTENTION: garbage collector called as consumed java memory didn't increase after "<<i<<" iterations
";
break;
}
else lastmem = mem;
//env->DeleteLocalRef(myo); ///!!!! <======= SEE TEXT
}
If running the code without the DeleteLocalRef()
, you'll notice a continuous increase in consumed memory: no garbage collection takes place, as the JVM is not aware that the reference to the java object requested from C++ is no longe used.
If you comment out the highlighted line, the DeleteLocalRef()
will tell the JVM that the object is no longer needed in the C++ code, and the garbage collector will behave exactly as in the pure java example, rfreeing up the object after a couple of iterations.