react-native
react-native copied to clipboard
feat: Trigger Java GC on reload
Summary:
While we (Margelo) were developing a new C++ 3D library for react-native, we noticed that Java often keeps a lot of dead instances in memory, making it hard to debug memory allocations (or actually de-allocations), especially since we use jsi::HostObject and jni::HybridClass in conjunction. Having two garbage-collected languages retain an object is a bit tricky, and making sure that we aren't doing anything wrong with our allocations and references was not easy - but manually calling System.gc() on app reloads helped us see that much better.
Before this, we needed to wait multiple minutes until some Java objects are actually freed from the GC. Our use-case was a facebook::jni::HybridClass, which was held strong in a facebook::jsi::HostObject (so again, two GC'd languages).
There should be no change in behaviour with this PR, just two things to note:
- Memory might be free'd more eagerly in full reloads (dev builds) - makes sense for library developers, especially when working with C++ modules.
System.gc()only suggests garbage collection, it does not force it. But when it runs, it might impact performance, although we haven't noticed any impact of that at all. The garbage collector runs anyways - better during a reload than later when exceuting the app normally.
Changelog:
[ANDROID] [ADDED] - Trigger Java GC on app reload
Test Plan:
Open an app, create a Java module that holds a few objects, add finalize() methods to those objects and log their deletion.
Reload the app to see the logs, compare before vs after.
| Platform | Engine | Arch | Size (bytes) | Diff |
|---|---|---|---|---|
| android | hermes | arm64-v8a | 18,910,437 | -4,132 |
| android | hermes | armeabi-v7a | n/a | -- |
| android | hermes | x86 | n/a | -- |
| android | hermes | x86_64 | n/a | -- |
| android | jsc | arm64-v8a | 22,279,102 | -4,132 |
| android | jsc | armeabi-v7a | n/a | -- |
| android | jsc | x86 | n/a | -- |
| android | jsc | x86_64 | n/a | -- |
Base commit: ce811a009d358021c11d911f653c77631a206cf4 Branch: main
Seems reasonable, and should only affect development. I'm not 100% confident that the existing runtime will have been torn down by the time this method is called. Perhaps onJSBundleLoadedFromServer?
Can you expand the comment in-code with some of the information from this PR?
@javache what do you think of putting it inside ReactInstanceManager::tearDownReactContext?
Or I guess at the end of ReactInstanceManager::recreateReactContextInBackground - after that ran, this.mCurrentReactContext is also null, and it definitely looks like this is a better place for a "GC when reloading the app", as opposed to "run GC when tearing down the context", which sounds a bit more hacky. 😅
Okay I moved it to recreateReactContextInBackground and added a comment 👍
let me know if that makes sense
EDIT: hmm actually I don't think this makes sense - for bridge mode this doesn't get called afaik. I'll try to find a better place
Yes, this will only get called in bridge mode, you'll need an equivalent point in ReactHostImpl for this.
I just moved it into DevSupportManagerBase.java::onReactInstanceDestroyed(...), what do you think about that?
For me this always hits after refreshing in debug. In release, this isn't used afaik
sorry for the bump here @javache - have you had a chance to look at this?