I. Introduction to memory leak analysis tools

Profiler

  • Anticipate which classes are causing memory leaks

    The Activity/Fragment Leaks option has been added to Android Studio 3.6 to quickly locate which classes are leaking memory. This option was not available in previous versions. You can also view the classes by selecting Arrange by Package. A class that anticipates a memory leak (for example, if the B Activity page is down but you can still see that it’s taking up memory).

  • Get the hprof file

    Manually triggering Java heap Dump to get the hprof file.

MAT

  • Find the leak chain

The Hprof file exported by Android Studio needs to go through a hprof-conv command to be recognized by MAT and imported to MAT to find the GcRoots chain of the leaked class, exclude soft references, weak references and virtual references, and the rest is the leak chain we need.

LeakCanary

LeakCanary has a nice icon, a canary inside the yellow box. It’s easy to access and check the leak chain, but you may not be able to tolerate its short pauses during dump. The frequent pauses make testers think it’s a big bug. It could also be a bug that detects a possible memory leak.

Use of memory leak analysis tools

Profiler works with MAT to detect memory leaks.

Build a memory leak first

Public class SingleTonXX {// Just for memory leak, please do not write public static Context mContext; privateSingleTonXX() {
    }
    private static volatile SingleTonXX sSingleTonXX;
    public static SingleTonXX getInstance(Context context) {
        if (sSingleTonXX == null) {
            synchronized (SingleTonXX.class) {
                if (sSingleTonXX == null) {
                    mContext=context;
                    returnnew SingleTonXX(); }}}returnsSingleTonXX; } } public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState)  { super.onCreate(savedInstanceState);setContentView(R.layout.activity_second); SingleTonXX.getInstance(this); }}Copy the code

Dump Profile first

Or just look at the leaked classes

See Lifecycle parsing earlier

A few key values:

Shallow Size: The Size of the memory occupied by the object itself, excluding the object referenced by it.

Retained Size: the Retained Size of the object + the Shallow Size of the object that can be directly or indirectly referenced. Retained Size is the sum of the Retained memory that the object can be retrieved after being Gc.

Heap Size: indicates the Heap Size.

Allocated: Allocated heap size that is the actual memory occupied by the App.

Export the hprof file and convert it to aA.hprof.

- vm/Library/Java/JavaVirtualMachines jdk1.8.0 _191. JDK/Contents/Home/bin/JavaCopy the code

Open the aa.hprof file you just opened

Click histogram and enter “SecondActivity “in the input box

Choose the shortest chain of GcRoots and exclude weak virtual references.

The leak chain is out, and the member variable mContext of the singleton SingleTonXX object holds the SecondActivity reference.

Q: What is the Gc Roots chain and why are you looking for it?

How to determine an object is garbage when GC, using the root search algorithm.

The object of GC Roots is taken as the starting point to search for the corresponding reference nodes. After finding these reference nodes, continue to search for their reference nodes from these nodes and form a reference chain.

Reachable objects: An object has a reference chain attached to an object in GC Roots.

Unreachable object: An object that is not connected by a reference chain to GC Roots’ object is the object we want to reclaim.

Target for GC Roots: The primary area of GC management is the Java heap, and garbage collection is typically done only for the heap. Objects referenced by GC Roots are not collected by GC, The objects that can be GCRoots are objects referenced in the Java virtual machine stack, objects referenced by JNI in the local method stack, objects referenced by the method area runtime constant pool, objects referenced by static properties in the method area, running threads (think of memory leaks), objects loaded by the bootloader, Objects controlled by GC.

Use LeakCanary memory detection

Principle of LeakCanary (Version 1.6.3)

LeakCanary Checks whether there is a memory leak. If there is a memory leak, the current memory snapshot is dumped and the dump file is parsed.

The logic to determine whether there is a memory leak is simple. The core part uses Java APIS, the method provided by the Android system for dump operation, and the Haha library for parsing dump files. The difficulty lies in parsing dump files. But I’m not going to talk about parsing, see for yourself, it’s a little complicated.

  • Determine whether there is a memory leak

Let’s take a look at the Java API

Obj =new Object(); ReferenceQueue queue = new ReferenceQueue(); Class WeakReference<Object> objectWeakReference = new WeakReference<>(obj,queue); // Disconnect Gc Roots chain obj = null; Runtime.getRuntime().gc(); System.runFinalization(); Log.e("test"."onCreate: queue----"+queue.poll());
Copy the code

When obJ is reclaimed, its weak reference object is placed in the queue (obJ’s memory state changes from Pending to EnQueue). Normally, the value of queue.poll() is objectWeakReference; If null, there may be a memory leak.

How does Leakcanary know?

In view of the activity: application. RegisterActivityLifecycleCallbacks (newActivityLifecycleCallbacksAdapter() { @Override public void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); }}); In view of the Fragment: To get the corresponding activity of FragmentManager supportFragmentManager. RegisterFragmentLifecycleCallbacks (new FragmentManager.FragmentLifecycleCallbacks() {
    @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
        View view = fragment.getView();
        if (view != null) {
            refWatcher.watch(view);
        }
    }
    @Override public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
        refWatcher.watch(fragment);
    }
}, true);
Copy the code
public class RefWatcher {
    private static final String HPROF_SUFFIX = ".hprof";
    private static final String PENDING_HEAPDUMP_SUFFIX = "_pending" + HPROF_SUFFIX;
   public static volatile RefWatcher sInstance;
    public static  RefWatcher  getInstance() {if(sInstance==null){
            synchronized (RefWatcher.class){
                if(sInstance==null){
                    returnnew RefWatcher(new CopyOnWriteArraySet<String>(),new ReferenceQueue<>()); }}}return sInstance;
    }
    private final Handler mBackgroundHander;
    private final Handler mMainHandler;

    private  RefWatcher(Set<String> retainedKeys, ReferenceQueue<Object> queue) {
        this.retainedKeys = retainedKeys;
        this.queue = queue;
        HandlerThread handlerThread=new HandlerThread("watch_leak");
        handlerThread.start();
        mBackgroundHander = new Handler(handlerThread.getLooper());
        mMainHandler = new Handler(Looper.getMainLooper());

    }
    private final Set<String> retainedKeys;
    private final ReferenceQueue<Object> queue;
    public void watch(Object object) {
        if(object==null) {
            return; } String key = UUID.randomUUID().toString(); retainedKeys.add(key); // Bind key to object, so create KeyedWeakReference; KeyedWeakReference weakReference=new KeyedWeakReference(object,key,queue); ensureGoneAsync(weakReference); } private void ensureGoneAsync(Final KeyedWeakReference weakReference) {//leakCanary To detect leaks mBackgroundHander. PostDelayed (newRunnable() {
            @Override
            public void run() {
                mMainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
                            @Override public boolean queueIdle() {
                                ensureGone(weakReference);
                                return false; }}); }}); }}, 5000); } public void ensureGone(KeyedWeakReference weakReference){ removeWeaklyReachableReferences(); // Indicates that the object is properly reclaimedif(! retainedKeys.contains(weakReference.key)){return; } // GcTrigger.default.rungc (); / / to clean up operations removeWeaklyReachableReferences ();if(retainedKeys.contains(weakReference.key)){
            Log.e("leak1"."catch a leak point "+weakReference ); // Reference is not in the ReferenceQueue. Memory leaks may be happened the File File = new File (MyApplication. GetInstance (). GetExternalFilesDir ("dump"),UUID.randomUUID().toString()+PENDING_HEAPDUMP_SUFFIX); try { Debug.dumpHprofData(file.getAbsolutePath()); / / leakcanary start a service to parse the profile file Intent Intent = new Intent (MyApplication. GetInstance (), HeapAnalyzerService. Class); intent.putExtra(HEAPDUMP_EXTRA, file.getAbsolutePath()); intent.putExtra(REFERENCE_KEY_EXTRA, weakReference.key); ContextCompat.startForegroundService(MyApplication.getInstance(), intent); } catch (IOException e) { e.printStackTrace(); } } } private voidremoveWeaklyReachableReferences() {
        // WeakReferences are enqueued as soon as the object to whichthey point to becomes weakly // reachable. This is before finalization or garbage collection has actually happened. // Under normal circumstances if an object is unreachable, then the weak reference of the object will be added to the specified ReferenceQueue (we can query the object's memory state based on this);while((ref = (KeyedWeakReference) queue.poll()) ! = null) {// Empty the key of a normal reference object. retainedKeys.remove(ref.key); }}}Copy the code

The above code has been reduced to equivalent rewrite. By listening to the callback of the onDestroy event of the Activity and Fragment, attach WeakReference to the Act or Fragment that is in the destroying state and specify a ReferenceQueue to collect it. Remember, If the poll() collected is null, it is considered to be a memory leak, which is not accurate. OnActivityDestroyed and onFragmentDestroyed are called in the super. OnDestory method. Act and Fragment destroyed may be strongly referenced and destoryed. That is enough of destroyed, and if the system GC does not fire, manually fire a runtime GC so that the detection is correct.

  • Get the memory snapshot hprof file

Debug.dumpHprofData(file.getAbsolutePath());
Copy the code

It’s that simple.

  • Parse the HeapDump file

Start an IntentService (HeapAnalyzerService) to parse dump files. This service can handle android O and other applications without killing them. The final display can be displayed in another Activity(DisplayLeakActivity). This act is different from the others, and appears in a different icon in the launcher, which makes it easier to view. (Componentization is not for reference.)

Ps: How do YOU make an app with two ICONS on the launcher?

<activity
    android:theme="@style/leak_canary_LeakCanary.Base"
    android:name=".internal.DisplayLeakActivity"
    android:process=":leakcanary"
    android:enabled="false"
    android:label="@string/leak_canary_display_activity_label"
    android:icon="@mipmap/leak_canary_icon"
    android:taskAffinity="com.squareup.leakcanary.${applicationId}"
    >
  <intent-filter>
    <action android:name="android.intent.action.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </intent-filter> <intent-filter> <action android:name="android.intent.action.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> The taskAffinity value is a new value, which is different from the default value (the default is the package name), indicating that a new stack of tasks is opened when DisplayLeakActivity is started. Instead of showing the main app's interface, clicking the extra icon will show this DisplayLeakActivity.enableSet tofalseSo how do you enable it, the code rewrite is equal totrue.Copy the code

Android :enabled=”false” this can be left out. I found that the designer has added this flag to LeakCanary when declaring Activity or Service in the manifest file. When starting the Activity or Service, Change the value of flag to true before starting. Do you know? Please let me know.

public static PendingIntent createPendingIntent(Context context, String referenceKey) {
setEnabledBlocking(context, DisplayLeakActivity.class, true);
Intent intent = new Intent(context, DisplayLeakActivity.class);
intent.putExtra(SHOW_LEAK_EXTRA, referenceKey);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
return PendingIntent.getActivity(context, 1, intent, FLAG_UPDATE_CURRENT);
}

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);Copy the code

LeakCanaryDemo lite is posted

Poking me LeakCanaryDemo