Memory leak
Memory leak caused by single case
public class SingletonActivityContext { private static SingletonActivityContext instance; private Context context; private SingletonActivityContext(Context context) { this.context = context; } public static SingletonActivityContext getInstance(Context context) { if (instance == null) { instance = new SingletonActivityContext(context); } return instance; } }
-
Problem: if the context of the Activity is passed in, the memory will not be recycled when the Activity exits, because the singleton object holds the reference of the Activity.
-
Solution: pass in the context of the Application to make the life cycle of the single instance as long as that of the Application.
Memory leak caused by creating static instance of non static inner class
public class StaticLeakActivity extends Activity { private static noneStaticClass mResource = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (mResource == null) { mResource = new noneStaticClass(); } } private class noneStaticClass { } }
-
Non static internal classes hold external class references. The life cycle of static instance mResource is as long as that of the application. As a result, static instances always hold references to StaticLeakActivity, which makes StaticLeakActivity unable to be recycled by memory.
-
Solution: change the non static inner class noneStaticClass into a static inner class (with static modification).
Memory leak caused by Handler
public class HandlerLeakActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mLeakyHandler.postDelayed(new Runnable() { @Override public void run() { } }, 1000 * 60 * 10); finish(); } /* * terms of settlement: * 1.Declare Handler static * 2.Introduce Activity through weak reference * * */ }
-
Message Queue -> message -> Handler -> Activity.
-
solve:
- Declare the Handler as static, so the life cycle of the Handler has nothing to do with the Activity.
- Introduce Activity through weak reference.
perhaps
- Handler is called when onDestroy removeCallbacksAndMessages();
Memory leak caused by Webview
public class WebviewLeakActivity extends AppCompatActivity { private WebView mWebView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mWebView = (WebView) findViewById(R.id.wv_show); mWebView.loadUrl("http://www.baidu.com"); } @Override protected void onDestroy() { destroyWebView(); android.os.Process.killProcess(android.os.Process.myPid()); super.onDestroy(); } private void destroyWebView() { if (mWebView != null) { mWebView.pauseTimers(); mWebView.removeAllViews(); mWebView.destroy(); mWebView = null; } } }
Solution: separate process
<activity android:name=".WebviewActivity" android:process="webview"/>
ReferenceQueue
-
Soft reference weak reference
-
Object is garbage collected, and the Java virtual machine will add this reference to the reference queue associated with it.
Source code analysis
Looking for memory leaks
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Fresco.initialize(this); (1)Follow in LeakCanary.install(this); } }
public static RefWatcher install(Application application) { return refWatcher(application).listenerServiceClass(DisplayLeakService.class) .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) (1)Follow in .buildAndInstall(); }
public RefWatcher buildAndInstall() { (1)establish RefWatcher object RefWatcher refWatcher = build(); if (refWatcher != DISABLED) { (2)To open the memory leak pop-up window Activity. LeakCanary.enableDisplayLeakActivity(context); (3)adopt ActivityRefWatcher monitor Activity Memory leak. (4)Follow in ActivityRefWatcher.install((Application) context, refWatcher); } return refWatcher; } (2)To open the memory leak pop-up window Activity. The underlying call. public static void setEnabledBlocking(Context appContext, Class<?> componentClass, boolean enabled) { ComponentName component = new ComponentName(appContext, componentClass); PackageManager packageManager = appContext.getPackageManager(); int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED; // Blocks on IPC. packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP); }
public final class ActivityRefWatcher { /** @deprecated Use {@link #install(Application, RefWatcher)}. */ @Deprecated public static void installOnIcsPlus(Application application, RefWatcher refWatcher) { install(application, refWatcher); } public static void install(Application application, RefWatcher refWatcher) { new ActivityRefWatcher(application, refWatcher).watchActivities(); } (1)activity Lifecycle Callback private final Application.ActivityLifecycleCallbacks lifecycleCallbacks = new Application.ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { } @Override public void onActivityStarted(Activity activity) { } @Override public void onActivityResumed(Activity activity) { } @Override public void onActivityPaused(Activity activity) { } @Override public void onActivityStopped(Activity activity) { } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override public void onActivityDestroyed(Activity activity) { (4)important ActivityRefWatcher.this.onActivityDestroyed(activity); } }; private final Application application; private final RefWatcher refWatcher; /** * Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking * after they have been destroyed. */ public ActivityRefWatcher(Application application, RefWatcher refWatcher) { this.application = checkNotNull(application, "application"); this.refWatcher = checkNotNull(refWatcher, "refWatcher"); } void onActivityDestroyed(Activity activity) { (6)Follow in refWatcher.watch(activity); } (2)Register listening public void watchActivities() { // Make sure you don't get installed twice. stopWatchingActivities(); application.registerActivityLifecycleCallbacks(lifecycleCallbacks); } (3)Deregistration public void stopWatchingActivities() { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks); } }
public final class RefWatcher { public static final RefWatcher DISABLED = new RefWatcherBuilder<>().build(); (1)To perform memory leak detection private final WatchExecutor watchExecutor; (2)Query whether it is in debug During debugging, memory leak detection will not be performed during re debugging private final DebuggerControl debuggerControl; (3)trigger GC garbage collection private final GcTrigger gcTrigger; (4)dump Heap file in memory private final HeapDumper heapDumper; (5)Holding objects that are to be detected and have memory leaks key private final Set<String> retainedKeys; (6)establish WeakReference The reference queue passed in during is mainly used to judge whether the objects held by weak references are deleted gc Recycling, gc After recycling, weak references are added to the queue private final ReferenceQueue<Object> queue; (7)Monitor generation heap File callback private final HeapDump.Listener heapdumpListener; (8) Some system memory leaks that need to be eliminated private final ExcludedRefs excludedRefs; }
public void watch(Object watchedReference) { watch(watchedReference, ""); } /** * Watches the provided references and checks if it can be GCed. This method is non blocking, * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed * with. * * @param referenceName An logical identifier for the watched object. */ public void watch(Object watchedReference, String referenceName) { if (this == DISABLED) { return; } checkNotNull(watchedReference, "watchedReference"); checkNotNull(referenceName, "referenceName"); final long watchStartNanoTime = System.nanoTime(); (1)Add unique key value String key = UUID.randomUUID().toString(); retainedKeys.add(key); (2)Create weak reference object final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue); (3)Check whether the object is recycled ensureGoneAsync(watchStartNanoTime, reference); }
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) { (1)look down watchExecutor realization watchExecutor.execute(new Retryable() { @Override public Retryable.Result run() { (2)Follow in return ensureGone(reference, watchStartNanoTime); } }); } @Override public void execute(Retryable retryable) { (3)adopt idleHandler Perform detection if (Looper.getMainLooper().getThread() == Thread.currentThread()) { waitForIdle(retryable, 0); } else { postWaitForIdle(retryable, 0); } } void postWaitForIdle(final Retryable retryable, final int failedAttempts) { mainHandler.post(new Runnable() { @Override public void run() { waitForIdle(retryable, failedAttempts); } }); } void waitForIdle(final Retryable retryable, final int failedAttempts) { // This needs to be called from the main thread. Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { postToBackgroundWithDelay(retryable, failedAttempts); return false; } }); }
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); (1)Delete weak references that have been recycled removeWeaklyReachableReferences(); (2)debug Return during debugging if (debuggerControl.isDebuggerAttached()) { // The debugger can create false leaks. return RETRY; } (3)Return after recycling if (gone(reference)) { return DONE; } (4)Active trigger GC gcTrigger.runGc(); (5)Delete weak references that have been recycled removeWeaklyReachableReferences(); (6)Analysis started before it was recycled if (!gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); (7)obtain dump snapshot File heapDumpFile = heapDumper.dumpHeap(); if (heapDumpFile == RETRY_LATER) { // Could not dump the heap. return RETRY; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); (8)Start analysis heapdumpListener.analyze( new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)); } return DONE; }
Summary:
-
Through application Activitylifecyclecallbacks listens to the onDestroy method of an Activity.
-
When the Activity calls the onDestroy method, a weak reference is created to associate with the Activity. And assign a unique Key.
-
When the Activity is GC, the weak reference will be added to ReferenceQueue.
-
If it is not added to the queue, trigger GC manually and check whether it is added to the queue again.
-
If not, a memory leak occurs.
Analyze memory leaks
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) { long analysisStartNanoTime = System.nanoTime(); if (!heapDumpFile.exists()) { Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile); return failure(exception, since(analysisStartNanoTime)); } try { HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile); HprofParser parser = new HprofParser(buffer); (1)Continue Hprof Convert file to Snapshot snapshot Snapshot snapshot = parser.parse(); (2)Remove duplicate memory leaks deduplicateGcRoots(snapshot); (3)according to key Query whether the parsing result has the object we need Instance leakingRef = findLeakingReference(referenceKey, snapshot); // False alarm, weak reference was cleared in between key check and heap dump. if (leakingRef == null) { (4)stay heap dump References are recycled in the process return noLeak(since(analysisStartNanoTime)); } (5)Leak path return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef); } catch (Throwable e) { return failure(e, since(analysisStartNanoTime)); } }
private Instance findLeakingReference(String key, Snapshot snapshot) { (1)Find the first leaked weak reference object from the memory snapshot ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName()); List<String> keysFound = new ArrayList<>(); (2)Traverse all instances of this object for (Instance instance : refClass.getInstancesList()) { List<ClassInstance.FieldValue> values = classInstanceValues(instance); String keyCandidate = asString(fieldValue(values, "key")); (3) If key Value and first defined key If the value is the same, the leaked object is returned if (keyCandidate.equals(key)) { return fieldValue(values, "referent"); } keysFound.add(keyCandidate); } throw new IllegalStateException( "Could not find weak reference with key " + key + " in " + keysFound); }
private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot, Instance leakingRef) { ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs); ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef); // False alarm, no strong reference path to GC Roots. if (result.leakingNode == null) { return noLeak(since(analysisStartNanoTime)); } LeakTrace leakTrace = buildLeakTrace(result.leakingNode); String className = leakingRef.getClassObj().getClassName(); // Side effect: computes retained size. snapshot.computeDominators(); Instance leakingInstance = result.leakingNode.instance; long retainedSize = leakingInstance.getTotalRetainedSize(); // TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer if (SDK_INT <= N_MR1) { retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance); } return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize, since(analysisStartNanoTime)); }
Summary:
-
Weak reference found in Snapshot
-
Traverse all instances of the KeyedWeakReference class
-
If the key value is the same as the key value defined at the beginning, the leaked object is returned