When invoking static native
methods from objects that override
finalize
,
the finalizer may run before the native method finishes execution.
Consider the following example:
public class GameRunner {
// Pointer to some native resource, stored as a long
private long nativeResourcePtr;
// Allocates the native resource that this object "owns"
private static native long doNativeInit();
// Releases the native resource
private static native void cleanUpNativeResources(long nativeResourcePtr);
public GameRunner() {
nativeResourcePtr = doNativeInit();
}
@Override
protected void finalize() {
cleanUpNativeResources(nativeResourcePtr);
nativeResourcePtr = 0;
super.finalize();
}
public void run() {
GameLibrary.playGame(nativeResourcePtr); // Bug!
}
}
public class GameLibrary {
// Plays the game using the native resource
public static native void playGame(long nativeResourcePtr);
}
During the execution of GameRunner.run
, the call to playGame
may not hold
the this
reference live, and its finalizer may run, cleaning up the native
resources while the native code is still executing.
You can fix this by making the static native
method not static
, or by
changing the static native
method so that it is passed the enclosing instance
and not the nativeResourcePtr
value directly.
If you can use Java 9, the new method
Reference.reachabilityFence
will keep the reference passed in live. Be sure to call it in a finally block:
public void run() {
try {
playAwesomeGame(nativeResourcePtr);
} finally {
Reference.reachabilityFence(this);
}
}
}
Note: This check doesn’t currently detect this problem when passing fields from objects other than “this”. That is equally unsafe. Consider passing the Java object instead.
Suppress false positives by adding the suppression annotation @SuppressWarnings("UnsafeFinalization")
to the enclosing element.