Feature: support deleting GlobalRef from Env.classCache to avoid memory leak #83
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
✨ Types of changes
💡 Motivation and Context
My application was recently experiencing OOM after running for a couple days. The architecture of the app is Java running on top, calling into JNI using
jnigi
then calling into Go. Java can make many calls to the JNI layer with different types of parameters, includingString
.This was a very interesting/hard leak to find, because the memory stats from the Main app running in Java didn't indicate any abnormal growth, but the RSS values definitely had a very linear curve. So we started isolating the components and verified that the leak was coming from the JNI. I then wrote a stress test app and used
jemalloc
to trace the memory on the stack.jemalloc
gave me this chart:That showed me that there is a large % of memory used by
jni_NewGlobalRef
that wasn't getting freed. For some reason the map symbols were not working on the memory boxes above the global ref. I then started tracing wherejnigi
callsjni_NewGlobalRef
and that was how I got to the problem.🐛 Problem
Here is how my JNI layer looks with the leak:
That looked sort of harmless in the beginning, until we had to run the app for days.
The issue lives inside of the
env.classCache
. Whenenv
gets out of scope, all of theenv.classCache
will also get destroyed. The catch is that those objects inside ofenv.classCache
were created usingNewGlobalRef
bycallFindClass
and are not deleted whenenv
gets out of scope, creating dangling references.🎯 Solution
This PR is introducing a new method called
env.DeleteGlobalRefCache()
, this method should be called after you want to dispose an instance of*jnigi.Env
. It'll calldeleteGlobalRef
in all of theclassCache
and clear the map as well.