JNI Attach/Detach thread memory management
I figured the problem. It was dangling local references in the JNI code I didn't destroy. Every callback would create a new local reference, thus resulting in a memory leak. When I converted the local reference to global, so I could reuse it, the problem disappeared.
Several points about calling back into Java from native code:
- AttachCurrentThread should only be called if jvm->GetEnv() returns JNI_EDETACHED. It's usually a no-op if the thread is already attached, but you can save some overhead.
- DetachCurrentThread should only be called if you called AttachCurrentThread.
- Avoid the detach if you expect to be called on the same thread in the future.
Depending on your native code's threading behavior, you may want to avoid the detach and instead store references to all native threads for disposal on termination (if you even need to do that; you may be able to rely on application shutdown to clean up).
If you continually attach and detach native threads, the VM must continually associate (often the same) threads with Java objects. Some VMs may re-use threads, or temporarily cache mappings to improve performance, but you'll get better and more predictable behavior if you don't rely on the VM to do it for you.