测试案例:
以太网<打开 – 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地址的时候。
IpManager的代码比较多,直接分析代码很难快速定为到问题点。我们可以借助工具分析。
MAT(Memory Analyzer Tool)是一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。
(1) 打开(Android Monitor),选择(Monitors)页面,可以看到Memory,CPU,Network,GPU4个监控项,我们关注Memory这一栏:
(2) 选择system_process进程,对这个进程进行监控。system_process进程即system_server进程。
(3)手动GC(点击下图箭头所示按钮):
(4)生成内存镜像文件:
(5)点击(Captures),可看到生成了.hprof文件:
(6)将生成的hprof文件转换成标准hprof文件:
鼠标右键选择导出,保存到指定目录即可。
(1) 在DDMS的进程列表中选择system_process,执行下图所示步骤:
(2) 生成内存镜像文件
点击下图所示按钮,Dump HPROF file,选择保存路径,保存:
(3) 将生成的hprof文件转换成标准的hprof文件:
hprof-conv input.hprof out.hprof
下载MAT工具:网址 http://www.eclipse.org/mat/downloads.php
可下载eclipse插件或下载独立MAT工具,此处不再赘述。
(1) 导入hprof文件
(file)-(Open File)打开要分析的文件:
(2) 点击第2栏第1行,打开Leak Suspects
可以看到,工具给我们报告了怀疑IpManager有问题。
(3)分析问题
IpManager有255个实例,没有被释放。
继续分析,进行如下图所示操作:
可见,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内存泄漏的表现将会非常明显。
既然是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;
LeakCanary是查找Android应用内存泄漏的主要工具,由Square公司开发,可以直接在Android设备端查看内存泄露,能够在程序发生内存泄漏的时候在通知栏提示通知。
参考:
https://github.com/square/leakcanary