IpManager内存泄漏案例分析

目录

    • 1 问题背景
    • 2 MAT工具分析内存泄漏
    • 2.1 生成内存镜像文件(hprof)
    • 2.1.1 Android Studio生成内存镜像文件
    • 2.1.2 DDMS生成内存镜像文件
    • 2.2 MAT工具分析hprof文件
    • 3 解决方案
    • 4 LeakCanary工具介绍

1 问题背景

测试案例:
以太网<打开 – dhcp – 关闭 – 打开 – static – 关闭> 循环测试10000次,成功率大于99%。
在Android7.1的项目,随着测试次数的增加,发现系统触发了LMK(Low Memory Killer),导致settings,launcher等重要进程被杀掉。
该案例在Android5.1项目上可以测试通过。
通过top,procrank等命令定位到,system_server进程的内存占用有较大增长,刚开机时其内存占用大约是50MB,跑完压力测试后超过400MB。怀疑system_server进程发生了内存泄漏。
初步分析:
(1) 单独执行dhcp压力和static压力测试,发现dhcp压力测试时system_server有内存增长,static压力测试没有内存增长;
(2) 从Android7.0开始,dhcp分配ip地址采用了一种全新的机制,叫做IpManager,而在此之前使用的是dhcpd本地服务。
初步结论:
内存泄漏是Android7.0新引入的问题,可能发生在IpManager分配ip地址的时候。

2 MAT工具分析内存泄漏

IpManager的代码比较多,直接分析代码很难快速定为到问题点。我们可以借助工具分析。
MAT(Memory Analyzer Tool)是一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。

2.1 生成内存镜像文件(hprof)

2.1.1 Android Studio生成内存镜像文件

(1) 打开(Android Monitor),选择(Monitors)页面,可以看到Memory,CPU,Network,GPU4个监控项,我们关注Memory这一栏:
IpManager内存泄漏案例分析_第1张图片
(2) 选择system_process进程,对这个进程进行监控。system_process进程即system_server进程。

(3)手动GC(点击下图箭头所示按钮):
IpManager内存泄漏案例分析_第2张图片
(4)生成内存镜像文件:
IpManager内存泄漏案例分析_第3张图片
(5)点击(Captures),可看到生成了.hprof文件:
IpManager内存泄漏案例分析_第4张图片
(6)将生成的hprof文件转换成标准hprof文件:
鼠标右键选择导出,保存到指定目录即可。
IpManager内存泄漏案例分析_第5张图片

2.1.2 DDMS生成内存镜像文件

(1) 在DDMS的进程列表中选择system_process,执行下图所示步骤:
IpManager内存泄漏案例分析_第6张图片
(2) 生成内存镜像文件
点击下图所示按钮,Dump HPROF file,选择保存路径,保存:
IpManager内存泄漏案例分析_第7张图片
(3) 将生成的hprof文件转换成标准的hprof文件:
hprof-conv input.hprof out.hprof
IpManager内存泄漏案例分析_第8张图片

2.2 MAT工具分析hprof文件

下载MAT工具:网址 http://www.eclipse.org/mat/downloads.php
可下载eclipse插件或下载独立MAT工具,此处不再赘述。
(1) 导入hprof文件
(file)-(Open File)打开要分析的文件:
IpManager内存泄漏案例分析_第9张图片
(2) 点击第2栏第1行,打开Leak Suspects
IpManager内存泄漏案例分析_第10张图片
可以看到,工具给我们报告了怀疑IpManager有问题。

(3)分析问题
IpManager内存泄漏案例分析_第11张图片
IpManager有255个实例,没有被释放。
继续分析,进行如下图所示操作:
IpManager内存泄漏案例分析_第12张图片
IpManager内存泄漏案例分析_第13张图片
可见,IpManager被NetworkManagerService的mObservers持有。
下面分析具体代码。
frameworks/base/services/core/java/com/android/server/NetworkManagementService.java

    private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
            new RemoteCallbackList<INetworkManagementEventObserver>();

从定义可知,mObservers是一个RemoteCallbackList,应该是IpManager实例被不断地add到这个List,但没有remove,所以这个List的大小不断增加,IpManager不能被GC。
frameworks/base/core/java/android/os/RemoteCallbackList.java

    public boolean register(E callback) {
        return register(callback, null);
    }
    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
        ... ...
                mCallbacks.put(binder, cb); //put
        ... ...
        }
    }

frameworks/base/services/core/java/com/android/server/NetworkManagementService.java

    @Override
    public void registerObserver(INetworkManagementEventObserver observer) {
        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
        mObservers.register(observer);
    }

从以上代码可知,register方法就是向RemoteCallbackList添加元素。

下面看看IpManager有没有调用registerObserver方法:
frameworks/base/services/net/java/android/net/ip/IpManager.java

    private void startStateMachineUpdaters() {
        try {
            mNwService.registerObserver(mNetlinkTracker);
        } catch (RemoteException e) {
            Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString());
        }
        mAvoidBadWifiTracker.start();
    }
    
    public IpManager(Context context, String ifName, Callback callback,
        INetworkManagementService nwService) throws IllegalArgumentException {
        ... ...
        startStateMachineUpdaters();
        ... ...
    }


果然有,而且是在构造方法中被调用!但是,在整个IpManager.java文件中,没有unregisterObserver的动作!
那么以太网dhcp分配IP地址的时候,是不是new了IpManager呢?
frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetNetworkFactory.java

public void onRequestNetwork() {
    ... ...
    mIpManager = new IpManager(mContext, mIface, ipmCallback);
    ... ...
}

分析发现,每次dhcp分配IP地址时,都会new IpManager。根据前面的分析,IpManager实例不会被释放,在这个测试用例中不断增加,造成内存泄漏。当压力测试dhcp到一定次数时,system_server内存泄漏的表现将会非常明显。

3 解决方案

既然是Android原生bug,我们先看看Android新版本有没有解决。
https://www.androidos.net.cn/sourcecode
通过阅读源码,我们发现Android9.0已经解决了这个问题。
以下是解决这个问题的patch:

diff --git a/android/frameworks/base/services/net/java/android/net/ip/IpManager.java b/android/frameworks/base/services/net/java/android/net/ip/IpManager.java
index ea14752..2014aa1 100644
--- a/android/frameworks/base/services/net/java/android/net/ip/IpManager.java
+++ b/android/frameworks/base/services/net/java/android/net/ip/IpManager.java
@@ -372,6 +372,7 @@ public class IpManager extends StateMachine {
     private static final int CMD_SET_MULTICAST_FILTER = 8;
     private static final int EVENT_PROVISIONING_TIMEOUT = 9;
     private static final int EVENT_DHCPACTION_TIMEOUT = 10;
+    private static final int CMD_TERMINATE_AFTER_STOP = 11;
 
     private static final int MAX_LOG_RECORDS = 500;
     private static final int MAX_PACKET_RECORDS = 100;
@@ -530,6 +531,14 @@ public class IpManager extends StateMachine {
         mAvoidBadWifiTracker.start();
     }
 
+    private void stopStateMachineUpdaters() {
+        try {
+            mNwService.unregisterObserver(mNetlinkTracker);
+        } catch (RemoteException e) {
+            Log.e(mTag, "Couldn't unregister NetlinkTracker: " + e.toString());
+        }
+    }
+
     @Override
     protected void onQuitting() {
         mCallback.onQuit();
@@ -539,7 +548,7 @@ public class IpManager extends StateMachine {
     public void shutdown() {
         stop();
         mAvoidBadWifiTracker.shutdown();
-        quit();
+        sendMessage(CMD_TERMINATE_AFTER_STOP);
     }
 
     public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
@@ -1092,6 +1101,11 @@ public class IpManager extends StateMachine {
         @Override
         public boolean processMessage(Message msg) {
             switch (msg.what) {
+                case CMD_TERMINATE_AFTER_STOP:
+                    stopStateMachineUpdaters();
+                    quit();
+                    break;
+
                 case CMD_STOP:
                     break;

4 LeakCanary工具介绍

LeakCanary是查找Android应用内存泄漏的主要工具,由Square公司开发,可以直接在Android设备端查看内存泄露,能够在程序发生内存泄漏的时候在通知栏提示通知。
参考:
https://github.com/square/leakcanary

你可能感兴趣的:(Android)