Project experience, if necessary, please specify the author: Yuloran (t.cn/EGU6c76)

preface

Memory leaks are both simple and complicated. Simple because we have many tools, such as Android Studio Profiler, MAT, etc., to locate memory leaks. It is complicated because we need to know a lot of other knowledge, such as the automatic garbage collection mechanism of The Android VIRTUAL machine (Dalvik or ART), the memory usage analysis of the Android platform application, the Android Context principle, etc., in order to understand the data provided by these tools. Otherwise, the next step of memory leak analysis cannot be carried out, or the cause of the leak is only known.

Therefore, it is highly recommended to read my first three articles before reading this one:

  • Java Virtual Machine Memory Model and GC Algorithm in Detail
  • Android Virtual Machine Vs Java Virtual Machine
  • Analyzing and Optimizing The Memory Footprint of Android Apps

If you are already familiar with or familiar with the above, you can also read this article directly.

As for the Android Context principle, I will be on Android 9.0 (Pie, API28) source code for a brief analysis.

If there are any mistakes, please correct them.

Memory leak concept

We know that in C, memory is managed by developers themselves, using malloc() to allocate memory and free() to free it. If free() is not called, memory leaks will result. The Java virtual machine implements automatic memory management, freeing developers from manual management. So why is there a “memory leak”? In fact, “memory leak” refers to a variable that lives longer than its lifetime, which is roughly “not dead at the damn time.” On mobile devices with limited physical memory, each Java process has a memory usage threshold that the VIRTUAL machine will throw OutOfMemoryError if it exceeds this threshold. Persistent memory leaks are likely to cause OutofMemoryErrors, which is why we need to fix memory leaks.

Memory upper limit of a single application heap

Check the maximum Java heap memory usage for a single application on an Android device:

Java code:

ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
Logger.debug("JavaHeap"."dalvik.vm.heapgrowthlimit: %dM", am.getMemoryClass());
Logger.debug("JavaHeap"."dalvik.vm.heapsize: %dM", am.getLargeMemoryClass());
Copy the code

Logger is a Logger printing utility class wrapped by the author: Logger.java

Shell command:

adb shell getprop dalvik.vm.heapgrowthlimit
adb shell getprop dalvik.vm.heapsize
Copy the code

Corresponding to:

  • Ordinary application Java heap usage limit: corresponding/system/build. The prop of “dalvik. Vm. Heapgrowthlimit”
  • Android :largeHeap=”true” in the Manifest Application tag, corresponding to “dalvik.vm.heapsize” in /system/build.prop

Examples (Mi 6, Android 8.0, MIUI 10 8.12.13) :

The 2018-12-31 17:13:39. 335, 4142-4142 /? D/WanAndroid: JavaHeap: dalvik vm. Heapgrowthlimit: 256 m 2018-12-31 17:13:39. 335, 4142-4142 /? D/WanAndroid: JavaHeap: dalvik.vm.heapsize: 512MCopy the code

Viewing Memory Information

As mentioned in the previous article, we can use:

adb shell dumpsys meminfo [-s|-d|-a] <package_name|pid>
Copy the code

Note: [] indicates command options, and <> indicates command parameters.

options role
-s Output summary memory information
-d Output detailed memory information
-a Output all memory information

Example (Mi 6, Android 8.0, MIUI 10 8.12.13)

D:\Android\projects\wanandroid_java>adb shell dumpsys meminfo -s com.yuloran.wanandroid_java
Applications Memory Usage (in Kilobytes) :Uptime: 563701889 Realtime: 1391868153 * *MEMINFO in pid 5897 [com.yuloran.wanandroid_java] * *App Summary
                       Pss(KB) -- -- -- -- -- -Java Heap:     9384
         Native Heap:    15636
                Code:    28680
               Stack:      120
            Graphics:     5688
       Private Other:     8020
              System:     3788

               TOTAL:    71316       TOTAL SWAP PSS44:Objects
               Views:        0         ViewRootImpl:        0
         AppContexts:        2           Activities:        0
              Assets18:AssetManagers:        2
       Local Binders:       24        Proxy Binders:       21
       Parcel memory8:Parcel count:       34
    Death Recipients:        3      OpenSSL Sockets:        0
            WebViews:        0
Copy the code

This shows PSS data in kilobytes. The application comes from my JetPack practice project wanAndroid_java.

Here we focus on Activities, which are an entry point for locating memory leaks in your application. Because an Activity acts as an interface between the application and the user, most memory leaks will eventually be reflected on the Activity. Normally, open an Activity, Activity count +1, exit an Activity, Activity count -1, exit the application, Activity count =0. If not, your application has a memory leak. Obviously, as shown in the figure above, Activities is 0 and there is no memory leak.

AppContexts, which stands for Context objects that are still alive in processes, is not as important as Activities. Application, Activity, and Service are Context objects, so the number of these objects will be included in the AppContexts count. Context of ContentProvider and BroadcastReceiver are passed in externally, so it will not affect the count of AppContexts. Generally, the number of Application contexts is 1, unless the Application is running multiple processes (one Java process for one VIRTUAL machine, so it could be a new Application). Why is AppContexts number 2? Use the Android Studio Profiler to debug:

The Instance View window can see internal references to the object, and the Reference window can see which external objects the object is referenced by. Let’s see which Context objects are being referenced by:

As shown in the figure above, this object is referenced by the MyApplication member variable mBase without problem.

As shown in the figure above, this object is referred to by the ActivityThread member variable mSystemContext without problem.

Context source code

The Context subclasses Application, Activity, Service, etc. Because the Application generally creates the Activity along with the creation, so directly from the Activity creation source code, source code based on SDK Android 9.0 (Pie, API28).

Activity create source code

-> ActivityThread::performLaunchActivity()

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // Create a ContextImpl object for the Activity, where the variable name is activityContext
        ContextImpl extends Context, so it is a subclass of Context
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            Reflection creates an Activity object
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
        } catch (Exception e) {
        }

        try {
            // Create an Application object
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
            if(activity ! =null) {
                / / to assign the activity to ContextImpl: : mOuterContext object, it can be seen that mOuterContext represents the actual component object,
                // For example, the Application, Activity, and Service objects, in this case the Activity object
                appContext.setOuterContext(activity);
                // Assign the ContextImpl appContext object to ContextWrapper::mBase, and most of the time, it does the actual work
                // This is the same object
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
                // Set the theme
                int theme = r.activityInfo.getThemeResource();
                if(theme ! =0) {
                    activity.setTheme(theme);
                }
                // Call onCreate() indirectly via the Instrumentation mInstrumentation object
                // Note the mInstrumentation object, which is an application of the proxy pattern, used to host directly created system components or calls
                // System component functions to monitor and assist in running Android applications.
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else{ mInstrumentation.callActivityOnCreate(activity, r.state); }}}catch (SuperNotCalledException e) {
        } catch (Exception e) {
        }
        return activity;
    }
Copy the code

-> LoadedApk::makeApplication()

    public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
        Application app = null;
        try {
            java.lang.ClassLoader cl = getClassLoader();
            // Create a ContextImpl object applicable to the Application
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            // Create an Application object
            app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
            / / to assign app ContextImpl: : mOuterContext object, it can be seen that mOuterContext represents the actual component object,
            // For example, the Application, Activity, and Service objects are Application objects
            appContext.setOuterContext(app);
        } catch (Exception e) {
        }
        return app;
    }
Copy the code

-> Instrumentation::newApplication()

    public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Application app = getFactory(context.getPackageName()).instantiateApplication(cl, className);
        // Assigns a ContextImpl context object to ContextWrapper::mBase, and most of the time, it does the actual work
        // This is the same object
        app.attach(context);
        return app;
    }
Copy the code

Service create

-> ActivityThread::handleCreateService()

    private void handleCreateService(CreateServiceData data) {
        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);
        Service service = null;
        try {
            // Create a Service object
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            service = packageInfo.getAppFactory().instantiateService(cl, data.info.name, data.intent);
        } catch (Exception e) {
        }

        try {
            // Create a ContextImpl object applicable to the Application
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            / / to assign service ContextImpl: : mOuterContext object, it can be seen that mOuterContext represents the actual component object,
            // For example, the Application, Activity, and Service objects. In this case, Service objects
            context.setOuterContext(service);
            // Check that the Application object is created only once
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            // Assigns a ContextImpl context object to ContextWrapper::mBase, and most of the time, it does the actual work
            // This is the same object
            service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService());
            / / calls onCreate ()
            service.onCreate();
        } catch (Exception e) {
        }
    }
Copy the code

The above code has been simplified by the author, and only the parts related to the topic of this article are retained. The ContextWrapper::mBase object is of type ContextImpl and does the actual work. Context is a variable pocket like an environment variable or a robocat that can be used to retrieve various system resources. Most memory leaks in Android are also Context leaks. For example, View and Drawable objects keep references to their source activities, so keeping a View or Drawable object can cause an Activity to leak.

Memory Leak Cases

Originally, I wanted to find a large factory App to test whether there was a memory leak, because the author had integrated their SDk before, and there was a memory leak at that time. Just tried, uh, after exiting even the process is not 😂… I can’t help but think of when I was in college, at that time Android applications were online, and some didn’t even do confusion, such as QQ were changed beyond recognition, all kinds of killing matt skin. The author also decomcompiled and modified the Settings center of Miui, and removed the auto-management and virus scanning functions. And today, all kinds of confusion encryption reinforcement, can’t run amok 😐. And this kind of low-level problem of memory leakage, big factory’s App naturally also rarely appears again.

Let’s write a memory leak:

    @Override
    protected void onStart(a)
    {
        super.onStart();

        Single.fromCallable(new Callable<SectionResp>()
        {
            @Override
            public SectionResp call(a) throws Exception
            {
                Thread.sleep(TimeUnit.SECONDS.toMillis(20));
                return new SectionResp();
            }
        }).subscribeOn(Schedulers.newThread()).subscribe(new Consumer<SectionResp>()
        {
            @Override
            public void accept(SectionResp sectionResp) throws Exception
            {
                Logger.debug("RxThread"."thread stopped."); }}); }Copy the code

The code above, which uses RxJava to simulate a slow response to a weak network request, is not a serious memory leak because we generally set a timeout for the request, at which point the thread will stop and the leak will disappear. But it doesn’t matter, because the location is the same regardless of the form of leak.

First, exit the app, then dumpsys meminfo to quickly check for Activity leaks:

D:\Android\projects\wanandroid_java>adb shell dumpsys meminfo -s com.yuloran.wanandroid_java
Applications Memory Usage (in Kilobytes) :Uptime: 578084002 Realtime: 1406250267 * *MEMINFO in pid 30243 [com.yuloran.wanandroid_java] * *App Summary
                       Pss(KB) -- -- -- -- -- -Java Heap:    10692
         Native Heap:    27740
                Code:    31264
               Stack:      120
            Graphics:     5816
       Private Other:     8792
              System:    12896

               TOTAL:    97320       TOTAL SWAP PSS28:Objects
               Views:      227         ViewRootImpl: 1.AppContexts:        3           Activities: 1.Assets18:AssetManagers:        3
       Local Binders26:Proxy Binders27:Parcel memory:       10         Parcel count:       42
    Death Recipients:        3      OpenSSL Sockets:        0
            WebViews:        0
Copy the code

Obviously, there is an Activity leak, because other references are often held within an Activity, resulting in a large number of resources that cannot be released in a timely manner.

Next, you need to analyze the specific cause of memory leaks, and there are two methods: Android Studio Profiler and MAT.

Analysis using Android Studio Profiler

Apparently, the anonymous inner class new Callable(){} in the onStart() method causes MainActivity to leak.

Analysis using MAT

To be honest, the performance tuning tools for Android Studio have evolved from DeviceMonitor to Profiler in just a few years. In the past, when the built-in tools were not available, the memory was dumped to xxx.hprof first, and then converted to MAT readable format for analysis.

Download and Install

Download address

Download MAT independent version, no installation, decompression use.

Export to *. Hprof

Export the applied Java heap memory as *.hprof (heap profile) :

You can also use adb shell am dumpheap to export:

adb shell am dumpheap com.yuloran.wanandroid_java /data/local/tmp/temp.hprof
adb pull /data/local/tmp/temp.hprof temp.hprof
Copy the code

Format conversion

Use hprof-conv.exe to convert the above exported files into mat-readable files with the same suffix:

hprof-conv.exe .\memory-20181231T225508.hprof .\memory-20181231T225508_mat.hprof
Copy the code

Hprof-conv. exe is in platform-tools:

For ease of use, I added platform-tools to the system environment variables, so you can use this program directly in the Windows Bat command above.

MAT analysis

Open the converted file memory-20181231T225508_mat.hprof:

Click on the histogram:

Enter.*Activity.* to filter:

Right-click and select With Outgoing References:

Right click and select exclude all virtual/weak/soft references:

MainActivity leak cause:

Apparently, the anonymous inner class new Callable(){} in the onStart() method causes MainActivity to leak.

conclusion

Writing on a public platform is a different experience than using it in private, and requires a more rigorous attitude and argument. Therefore, most of the author’s articles come from official documents or source code analysis, but after all, the level is limited, if there is a mistake, but also hope to give advice 🤝.

The attached

Memory tuning for Google Android Developers

  • Overview of memory management: includes garbage collection, shared memory, memory allocation and reclamation, memory limitation, and fast switching mechanism
  • Investigating Your RAM Usage: GC log analysis, memory allocation tracing, Hprof analysis, Dumpsys Meminfo data parsing, how to trigger memory leaks
  • Manage Your App’s Memory: Recommendations for memory optimization