TL;DR: Java can garbage collect an object while a method on the object is running, if that method doesn’t access the object’s fields/methods. This optimization is normally ok, but can cause problems when using JNI, because the object’s destructor could potentially free memory that the method is using (which the optimizer/compiler can’t see).
Is this still an open JVM issue?
The issue was that Java was making an unsafe optimization (I never bothered to figure out if it is the compiler or the JVM making the mistake, I was satisfied once I had a work-around).
Java was doing something similar to tail-call optimization with regard to garbage collection. You see, if it is waiting for the return value of a method m() of object o, and code in m() that is yet to be executed doesn’t access any other methods or properties of o, then it will go ahead and consider o eligible for garbage collection before m() has finished running.
That is normally a safe optimization to make… except for when a destructor method (finalize()) is defined for the object; the destructor can have side effects, and Java has no way to know whether it is safe for them to happen before m() has finished running.