Skip to content

Preventing/detecting memory leaks on Android #833

Open
@lukmccall

Description

@lukmccall

Introduction

In Expo, we recently discovered a couple of memory leaks. We identify two patterns that can lead to leaks affecting overall performance.

This problem may seem quite niche, as it initially appears to affect only a small group. However, it can significantly impact the DX and brownfield projects. In greenfield projects, this issue arises only when the OTA reloads the application.

It’s impossible to prevent users from writing code that leaks their own objects, which is fine. However, I think it would be beneficial to check if objects from React Native are not leaking. For instance, we discovered this problem in Expo Go because we observed multiple BridgeReactContext objects in the heap dump after reloading the app.

Details

Pattern 1 - Holding the jni::global_ref to the Java part of the HybridClass in the C++ code.

In fbjni, the Java part of the HybridClass is responsible for clearing the memory (when the GC removes it during de-initialization, it clears the C++ part). Generally speaking, this is safe and follows users’ expectations. However, when you need to call a Java method from C++ (for instance, when someone calls a JS method that needs to invoke Android-specific APIs), developers must create a global_ref to the Java object. This often results in a Java object that stores a C++ counterpart, which holds a global_ref to the (self) Java object. The JVM can handle circular references, but the jni::global_ref is treated as a static field; it is always accessible from the GC root and cannot be removed. Therefore, the user must manually remove one end of the references.

image

Let’s look at an example:

I don’t have a clear answer on how to fix this, but it would be helpful to have a mechanism for detecting if objects like the ReactApplicationContext are leaking.

Pattern 2 - not removing the listeners

It’s unclear to people if listeners should be removed or not. React Native has many different listeners, and many people are using them but forget to unsubscribe during de-initialization. Yes, in most cases, they will be cleared automatically. Although it isn’t hard to write a listener that captures much more than you think, causing weird leaks. I think it would be better to add some kind of warning to inform people if there is a dangling listener forcing developers to remove listeners in the clean-up phase

Discussion points

Because of C++, memory leaks are even more challenging to track and fix. We lack tools to detect bad code, and the connection between C++ and Java via JNI is often tricky to get right. We can't change how it works, but maybe we can structure the React Native code to mitigate some negative aspects, improving the overall experience.

  • Should we add some mechanism to detect if objects from React Native are leaking?
    • if so, how should it work?
  • Should we force people to unregister listeners?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions