From 510d50fc17157f186a593c2c02b3fcf610889c77 Mon Sep 17 00:00:00 2001 From: Alexander Blom Date: Mon, 7 Dec 2015 08:43:24 -0800 Subject: [PATCH] Trigger GC and drop compiled code on low memory Reviewed By: astreet Differential Revision: D2658693 fb-gh-sync-id: 8cba49b67ac45a2dbf8b4c9c404d6fb9c97693f6 --- .../facebook/react/MemoryPressureRouter.java | 93 +++++++++++++++++++ .../react/ReactInstanceManagerImpl.java | 5 + .../react/bridge/CatalystInstance.java | 4 +- .../react/bridge/CatalystInstanceImpl.java | 5 + .../facebook/react/bridge/MemoryPressure.java | 8 ++ .../facebook/react/bridge/ReactBridge.java | 15 +++ ReactAndroid/src/main/jni/react/Bridge.cpp | 16 ++++ ReactAndroid/src/main/jni/react/Bridge.h | 2 + ReactAndroid/src/main/jni/react/Executor.h | 4 + .../src/main/jni/react/JSCExecutor.cpp | 16 ++++ ReactAndroid/src/main/jni/react/JSCExecutor.h | 4 +- .../src/main/jni/react/jni/OnLoad.cpp | 13 +++ 12 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressure.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java b/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java new file mode 100644 index 000000000..58f283311 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/MemoryPressureRouter.java @@ -0,0 +1,93 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react; + +import javax.annotation.Nullable; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.ComponentCallbacks2; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Build; + +import com.facebook.react.bridge.CatalystInstance; +import com.facebook.react.bridge.MemoryPressure; +import com.facebook.react.bridge.ReactContext; + +import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; +import static android.content.ComponentCallbacks2.TRIM_MEMORY_MODERATE; +import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; + +/** + * Translates and routes memory pressure events to the current catalyst instance. + */ +public class MemoryPressureRouter { + // Trigger this by sending an intent to your activity with adb shell: + // am start -a "com.facebook.catalyst.ACTION_TRIM_MEMORY" --activity-single-top -n + private static final String ACTION_TRIM_MEMORY ="com.facebook.catalyst.ACTION_TRIM_MEMORY"; + + private @Nullable CatalystInstance mCatalystInstance; + private final ComponentCallbacks2 mCallbacks = new ComponentCallbacks2() { + @Override + public void onTrimMemory(int level) { + trimMemory(level); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + } + + @Override + public void onLowMemory() { + } + }; + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public static boolean handleDebugIntent(Activity activity, String action) { + switch (action) { + case ACTION_TRIM_MEMORY: + simulateTrimMemory(activity, TRIM_MEMORY_MODERATE); + break; + default: + return false; + } + + return true; + } + + MemoryPressureRouter(Context context) { + context.getApplicationContext().registerComponentCallbacks(mCallbacks); + } + + public void onNewReactContextCreated(ReactContext reactContext) { + mCatalystInstance = reactContext.getCatalystInstance(); + } + + public void onReactInstanceDestroyed() { + mCatalystInstance = null; + } + + public void destroy(Context context) { + context.getApplicationContext().unregisterComponentCallbacks(mCallbacks); + } + + private void trimMemory(int level) { + if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { + dispatchMemoryPressure(MemoryPressure.CRITICAL); + } else if (level >= TRIM_MEMORY_BACKGROUND || level == TRIM_MEMORY_RUNNING_CRITICAL) { + dispatchMemoryPressure(MemoryPressure.MODERATE); + } + } + + private void dispatchMemoryPressure(MemoryPressure level) { + if (mCatalystInstance != null) { + mCatalystInstance.handleMemoryPressure(level); + } + } + + private static void simulateTrimMemory(Activity activity, int level) { + activity.getApplication().onTrimMemory(level); + activity.onTrimMemory(level); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java index 1eecb4032..1169034ee 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerImpl.java @@ -100,6 +100,7 @@ import com.facebook.systrace.Systrace; new ConcurrentLinkedQueue<>(); private volatile boolean mHasStartedCreatingInitialContext = false; private final UIImplementationProvider mUIImplementationProvider; + private final MemoryPressureRouter mMemoryPressureRouter; private final ReactInstanceDevCommandsHandler mDevInterface = new ReactInstanceDevCommandsHandler() { @@ -215,6 +216,7 @@ import com.facebook.systrace.Systrace; mBridgeIdleDebugListener = bridgeIdleDebugListener; mLifecycleState = initialLifecycleState; mUIImplementationProvider = uiImplementationProvider; + mMemoryPressureRouter = new MemoryPressureRouter(applicationContext); } @Override @@ -400,6 +402,7 @@ import com.facebook.systrace.Systrace; public void onDestroy() { UiThreadUtil.assertOnUiThread(); + mMemoryPressureRouter.destroy(mApplicationContext); if (mUseDeveloperSupport) { mDevSupportManager.setDevSupportEnabled(false); } @@ -539,6 +542,7 @@ import com.facebook.systrace.Systrace; catalystInstance.initialize(); mDevSupportManager.onNewReactContextCreated(reactContext); + mMemoryPressureRouter.onNewReactContextCreated(reactContext); moveReactContextToCurrentLifecycleState(reactContext); for (ReactRootView rootView : mAttachedRootViews) { @@ -591,6 +595,7 @@ import com.facebook.systrace.Systrace; } reactContext.onDestroy(); mDevSupportManager.onReactInstanceDestroyed(reactContext); + mMemoryPressureRouter.onReactInstanceDestroyed(); } /** diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java index 7e5afc3a4..e9240eb88 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java @@ -9,8 +9,6 @@ package com.facebook.react.bridge; -import javax.annotation.Nullable; - import java.util.Collection; import com.facebook.react.bridge.queue.CatalystQueueConfiguration; @@ -49,6 +47,8 @@ public interface CatalystInstance { T getNativeModule(Class nativeModuleInterface); Collection getNativeModules(); + void handleMemoryPressure(MemoryPressure level); + /** * Adds a idle listener for this Catalyst instance. The listener will receive notifications * whenever the bridge transitions from idle to busy and vice-versa, where the busy state is diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index 6929ea3d7..06d53a85e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -314,6 +314,11 @@ public class CatalystInstanceImpl implements CatalystInstance { return mJavaRegistry.getAllModules(); } + @Override + public void handleMemoryPressure(MemoryPressure level) { + Assertions.assertNotNull(mBridge).handleMemoryPressure(level); + } + /** * Adds a idle listener for this Catalyst instance. The listener will receive notifications * whenever the bridge transitions from idle to busy and vice-versa, where the busy state is diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressure.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressure.java new file mode 100644 index 000000000..c947782ef --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/MemoryPressure.java @@ -0,0 +1,8 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react.bridge; + +public enum MemoryPressure { + MODERATE, + CRITICAL +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java index d84c7b844..b809e680b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java @@ -56,6 +56,19 @@ public class ReactBridge extends Countable { super.dispose(); } + public void handleMemoryPressure(MemoryPressure level) { + switch (level) { + case MODERATE: + handleMemoryPressureModerate(); + break; + case CRITICAL: + handleMemoryPressureCritical(); + break; + default: + throw new IllegalArgumentException("Unknown level: " + level); + } + } + private native void initialize( JavaScriptExecutor jsExecutor, ReactCallback callback, @@ -72,4 +85,6 @@ public class ReactBridge extends Countable { public native boolean supportsProfiling(); public native void startProfiler(String title); public native void stopProfiler(String title, String filename); + private native void handleMemoryPressureModerate(); + private native void handleMemoryPressureCritical(); } diff --git a/ReactAndroid/src/main/jni/react/Bridge.cpp b/ReactAndroid/src/main/jni/react/Bridge.cpp index e3643e6cf..84fb525b0 100644 --- a/ReactAndroid/src/main/jni/react/Bridge.cpp +++ b/ReactAndroid/src/main/jni/react/Bridge.cpp @@ -51,6 +51,14 @@ public: m_jsExecutor->stopProfiler(title, filename); } + void handleMemoryPressureModerate() { + m_jsExecutor->handleMemoryPressureModerate(); + } + + void handleMemoryPressureCritical() { + m_jsExecutor->handleMemoryPressureCritical(); + } + private: std::unique_ptr m_jsExecutor; Bridge::Callback m_callback; @@ -109,4 +117,12 @@ void Bridge::stopProfiler(const std::string& title, const std::string& filename) m_threadState->stopProfiler(title, filename); } +void Bridge::handleMemoryPressureModerate() { + m_threadState->handleMemoryPressureModerate(); +} + +void Bridge::handleMemoryPressureCritical() { + m_threadState->handleMemoryPressureCritical(); +} + } } diff --git a/ReactAndroid/src/main/jni/react/Bridge.h b/ReactAndroid/src/main/jni/react/Bridge.h index 9f81f529d..23843de42 100644 --- a/ReactAndroid/src/main/jni/react/Bridge.h +++ b/ReactAndroid/src/main/jni/react/Bridge.h @@ -38,6 +38,8 @@ public: bool supportsProfiling(); void startProfiler(const std::string& title); void stopProfiler(const std::string& title, const std::string& filename); + void handleMemoryPressureModerate(); + void handleMemoryPressureCritical(); private: Callback m_callback; std::unique_ptr m_threadState; diff --git a/ReactAndroid/src/main/jni/react/Executor.h b/ReactAndroid/src/main/jni/react/Executor.h index 1f4f54cc5..6a59bff42 100644 --- a/ReactAndroid/src/main/jni/react/Executor.h +++ b/ReactAndroid/src/main/jni/react/Executor.h @@ -43,6 +43,10 @@ public: }; virtual void startProfiler(const std::string &titleString) {}; virtual void stopProfiler(const std::string &titleString, const std::string &filename) {}; + virtual void handleMemoryPressureModerate() {}; + virtual void handleMemoryPressureCritical() { + handleMemoryPressureModerate(); + }; virtual ~JSExecutor() {}; }; diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index 5e6caae26..93bb17fba 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -18,6 +18,10 @@ #include #endif +#ifdef WITH_JSC_MEMORY_PRESSURE +#include +#endif + #ifdef WITH_FBSYSTRACE #include using fbsystrace::FbSystraceSection; @@ -182,6 +186,18 @@ void JSCExecutor::stopProfiler(const std::string &titleString, const std::string #endif } +void JSCExecutor::handleMemoryPressureModerate() { + #ifdef WITH_JSC_MEMORY_PRESSURE + JSHandleMemoryPressure(this, m_context, JSMemoryPressure::MODERATE); + #endif +} + +void JSCExecutor::handleMemoryPressureCritical() { + #ifdef WITH_JSC_MEMORY_PRESSURE + JSHandleMemoryPressure(this, m_context, JSMemoryPressure::CRITICAL); + #endif +} + void JSCExecutor::flushQueueImmediate(std::string queueJSON) { m_flushImmediateCallback(queueJSON); } diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.h b/ReactAndroid/src/main/jni/react/JSCExecutor.h index 849148bfa..a319a9036 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.h +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.h @@ -31,7 +31,9 @@ public: virtual bool supportsProfiling() override; virtual void startProfiler(const std::string &titleString) override; virtual void stopProfiler(const std::string &titleString, const std::string &filename) override; - + virtual void handleMemoryPressureModerate() override; + virtual void handleMemoryPressureCritical() override; + void flushQueueImmediate(std::string queueJSON); void installNativeHook(const char *name, JSObjectCallAsFunctionCallback callback); diff --git a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp index 6afa306da..610cd160d 100644 --- a/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp +++ b/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp @@ -729,6 +729,16 @@ static void stopProfiler(JNIEnv* env, jobject obj, jstring title, jstring filena bridge->stopProfiler(fromJString(env, title), fromJString(env, filename)); } +static void handleMemoryPressureModerate(JNIEnv* env, jobject obj) { + auto bridge = extractRefPtr(env, obj); + bridge->handleMemoryPressureModerate(); +} + +static void handleMemoryPressureCritical(JNIEnv* env, jobject obj) { + auto bridge = extractRefPtr(env, obj); + bridge->handleMemoryPressureCritical(); +} + } // namespace bridge namespace executors { @@ -840,6 +850,9 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { makeNativeMethod("supportsProfiling", bridge::supportsProfiling), makeNativeMethod("startProfiler", bridge::startProfiler), makeNativeMethod("stopProfiler", bridge::stopProfiler), + makeNativeMethod("handleMemoryPressureModerate", bridge::handleMemoryPressureModerate), + makeNativeMethod("handleMemoryPressureCritical", bridge::handleMemoryPressureCritical), + }); jclass nativeRunnableClass = env->FindClass("com/facebook/react/bridge/queue/NativeRunnable");