常见的几种内存泄漏场景
1.静态实例
我们使用静态实例的时候需要警惕它的生命周期,因为它跟应用程序的生命周期一样长,比如在Activity中使用静态实例,就算Activity已经被回收,静态实例还会存在。
public class SecondActivity extends AppCompatActivity {
private static Object sInner = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
MyParcel parcel = getIntent().getParcelableExtra("parcel");
Log.d(TAG, "parcel " + parcel);
sInner = new InnerClass();
InnerClass inner = new InnerClass();
}
class InnerClass {
String name;
int value;
}
}
2.异步任务
在异步线程中执行耗时操作,也可能造成内存泄漏,比如下面的例子,在AsyncTask中写一个while循环,就算Activity已经销毁,这里还是会持有一个Activity的引用。
public class SecondActivity extends AppCompatActivity {
private AsyncTask asyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
MyParcel parcel = getIntent().getParcelableExtra("parcel");
Log.d(TAG, "parcel " + parcel);
asyncTask = new AsyncTask() {
@Override
protected Object doInBackground(Object[] objects) {
while (true);
}
};
asyncTask.execute();
}
}
内存泄漏的分析工具
1. Profile
这是Android Studio自带的工具,不仅是内存,同时可以监控CPU,网络等资源。调试定位问题确实是方便快捷,但是有个问题,经常会连接不上,总是要把adb重启一下或者是重启android studio。
在windows平台上,可以通过tasklist命令查看所有进程,tskill [PID] 杀死adb进程。
通过netstat命令查看端口和连接情况。
下图可以看到,profile分成5个区域:
第一行是event事件,包括activity的启动销毁等;
第二行是CPU资源;
第三行是内存资源;
第四行是网络资源;
第五行是电量使用情况。
我们看下内存资源,profile将不同内存区域标识出来,stack是虚拟机栈内存,Java是堆内存,code应该是方法区,graphics是图形缓存区,native是c/c++分配的内存,allocated是分配的对象数量,others是其他。
4.PNG
以上面第一个内存泄漏的例子为例,我们看下怎么定位内存泄漏问题,选中一段区域之后,会自动显示这段时间分配的对象信息,直接查找InnerClass类,右边是它的所有实例,其他普通实例都能够在onDestroy之后被回收,只有那个静态实例,也就是第二个instance,一直没有被回收。
1.PNG
可以看到,直到整个应用退出了,这个实例依然存在。
2.PNG
更精确的信息也可以请看dumphead,图里有个垃圾桶按钮和一个下载按钮,分别代表强制gc和抓取dumpheap。
5.PNG
2. Memory Analyzer(MAT)
实际上也可以通过简单的adb命令先定位问题,比如meminfo
Applications Memory Usage (in Kilobytes):
Uptime: 32331378 Realtime: 175875568
** MEMINFO in pid 29973 [com.one.powerexception] **
Pss Private Private SwapPss Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 7871 7788 0 4 22528 12161 10366
Dalvik Heap 1387 1324 0 0 2351 1764 587
Dalvik Other 857 852 0 0
Stack 60 60 0 0
Ashmem 2 0 0 0
Gfx dev 788 788 0 0
Other dev 20 0 20 0
.so mmap 2226 76 128 0
.jar mmap 1051 0 8 0
.apk mmap 241 0 0 0
.ttf mmap 97 0 0 0
.dex mmap 2200 2200 0 0
.oat mmap 69 0 0 0
.art mmap 6077 5784 64 0
Other mmap 311 132 0 0
GL mtrack 5880 5880 0 0
Unknown 828 808 0 0
TOTAL 29969 25692 220 4 24879 13925 10953
App Summary
Pss(KB)
------
Java Heap: 7172
Native Heap: 7788
Code: 2412
Stack: 60
Graphics: 6668
Private Other: 1812
System: 4057
TOTAL: 29969 TOTAL SWAP PSS: 4
Objects
Views: 16 ViewRootImpl: 0
AppContexts: 6 Activities: 1
Assets: 15 AssetManagers: 0
Local Binders: 14 Proxy Binders: 31
Parcel memory: 4 Parcel count: 18
Death Recipients: 1 OpenSSL Sockets: 0
WebViews: 0
SQL
MEMORY_USED: 0
PAGECACHE_OVERFLOW: 0 MALLOC_SIZE: 0
我们可以通过adb的dumpheap命令抓取prof信息,这个命令会在/data/local/tmp/目录下生成一份Prof文件,然后通过android studio自带的hprof-conv工具转换成hprof,在MAT工具中分析。
adb shell dumpsys meminfo com.one.powerexception
adb shell am dumpheap -g com.one.powerexception
hprof-conv heapdump.prof hf.hprof
2.PNG
3.PNG
主要是几个区域:
- Histogram:对象的个数和大小
- Dominator Tree:最大的对象以及其依赖存活的Object
- Shallow size:对象本身占用内存的大小,不包含其引用的对象
- Retained Heap:如果一个对象被释放掉,那会因为该对象的释放而减少引用,进而被释放的所有的对象(包括被递归释放的)所占用的heap大小
参考:
使用 Memory Profiler 查看 Java 堆和内存分配
Android Profile Tools 入门
关于使用Eclipse Memory Analyzer的10点小技巧
Android Studio 的dump java heap以及mat调试
网友评论