androidc static对象 对象怎么防止内存泄漏

防止 Android 内存泄漏的 8 种方法 - 安卓 - 伯乐在线
& 防止 Android 内存泄漏的 8 种方法
在上一篇 中,我们讨论了八种容易发生内存泄漏的代码。其中,尤其严重的是泄漏Activity对象,因为它占用了大量系统内存。不管内存泄漏的代码表现形式如何,其核心问题在于:
在Activity生命周期之外仍持有其引用。
幸运的是,一旦泄漏发生且被定位到了,修复方法是相当简单的。
Static Actitivities
private static MainA
void setStaticActivity() {
activity =
private static MainActivity activity;&void setStaticActivity() {&&&&activity = this;}
构造静态变量持有Activity对象很容易造成内存泄漏,因为静态变量是全局存在的,所以当MainActivity生命周期结束时,引用仍被持有。这种写法开发者是有理由来使用的,所以我们需要正确的释放引用让垃圾回收机制在它被销毁的同时将其回收。
Android提供了特殊的Set类
允许开发者控制引用的“强度”。Activity对象泄漏是由于需要被销毁时,仍然被强引用着,只要强引用存在就无法被回收。
可以用弱引用代替强引用。
弱引用不会阻止对象的内存释放,所以即使有弱引用的存在,该对象也可以被回收。
private static WeakReference&MainActivity& activityR
void setStaticActivity() {
activityReference = new WeakReference&MainActivity&(this);
private static WeakReference&MainActivity& activityReference;&&&&&void setStaticActivity() {&&&&&&&&activityReference = new WeakReference&MainActivity&(this);&&&&}
Static Views
静态变量持有View
private static V
void setStaticView() {
view = findViewById(R.id.sv_button);
void setStaticView() {&&&&view = findViewById(R.id.sv_button);}
由于View持有其宿主Activity的引用,导致的问题与Activity一样严重。弱引用是个有效的解决方法,然而还有另一种方法是在生命周期结束时清除引用,Activity#onDestory()方法就很适合把引用置空。
private static V
public void onDestroy() {
super.onDestroy();
if (view != null) {
unsetStaticView();
void unsetStaticView() {
12345678910111213
private static View view;&@Overridepublic void onDestroy() {&&&&super.onDestroy();&&&&if (view != null) {&&&&&&&&unsetStaticView();&&&&}}&void unsetStaticView() {&&&&view = null;}
Inner Class
private static O
void createInnerClass() {
class InnerClass {
inner = new InnerClass();
private static Object inner;&void createInnerClass() {&&&&class InnerClass {&&&&}&&&&inner = new InnerClass();}
于上述两种情况相似,开发者必须注意少用非静态内部类,因为持有外部类的隐式引用,容易导致意料之外的泄漏。然而内部类可以访问外部类的私有变量,只要我们注意引用的生命周期,就可以避免意外的发生。
避免静态变量
这样持有内部类的成员变量是可以的。
void createInnerClass() {
class InnerClass {
inner = new InnerClass();
private Object inner;&void createInnerClass() {&&&&class InnerClass {&&&&}&&&&inner = new InnerClass();}
Anonymous Classes
前面我们看到的都是持有全局生命周期的静态成员变量引起的,直接或间接通过链式引用Activity导致的泄漏。这次我们用AsyncTask
void startAsyncTask() {
new AsyncTask&Void, Void, Void&() {
@Override protected Void doInBackground(Void... params) {
while(true);
}.execute();
void startAsyncTask() {&&&&new AsyncTask&Void, Void, Void&() {&&&&&&&&@Override protected Void doInBackground(Void... params) {&&&&&&&&&&&&while(true);&&&&&&&&}&&&&}.execute();}
void createHandler() {
new Handler() {
@Override public void handleMessage(Message message) {
super.handleMessage(message);
}.postDelayed(new Runnable() {
@Override public void run() {
while(true);
}, Long.MAX_VALUE && 1);
1234567891011
void createHandler() {&&&&new Handler() {&&&&&&&&@Override public void handleMessage(Message message) {&&&&&&&&&&&&super.handleMessage(message);&&&&&&&&}&&&&}.postDelayed(new Runnable() {&&&&&&&&@Override public void run() {&&&&&&&&&&&&while(true);&&&&&&&&}&&&&}, Long.MAX_VALUE && 1);}
void scheduleTimer() {
new Timer().schedule(new TimerTask() {
public void run() {
while(true);
}, Long.MAX_VALUE && 1);
void scheduleTimer() {&&&&new Timer().schedule(new TimerTask() {&&&&&&&&@Override&&&&&&&&public void run() {&&&&&&&&&&&&while(true);&&&&&&&&}&&&&}, Long.MAX_VALUE && 1);}
全部都是因为导致的。匿名类是特殊的内部类——写法更为简洁。当需要一次性的特殊子类时,Java提供的语法糖能让表达式最少化。这种很赞很偷懒的写法容易导致泄漏。正如使用内部类一样,只要不跨越生命周期,内部类是完全没问题的。但是,这些类是用于产生后台线程的,这些Java线程是全局的,而且持有创建者的引用(即匿名类的引用),而匿名类又持有外部类的引用。线程是可能长时间运行的,所以一直持有Activity的引用导致当销毁时无法回收。
这次我们不能通过移除静态成员变量解决,因为线程是于应用生命周期相关的。为了避免泄漏,我们必须舍弃简洁偷懒的写法,把子类声明为静态内部类。
静态内部类不持有外部类的引用,打破了链式引用。
所以对于AsyncTask
private static class NimbleTask extends AsyncTask&Void, Void, Void& {
@Override protected Void doInBackground(Void... params) {
while(true);
void startAsyncTask() {
new NimbleTask().execute();
private static class NimbleTask extends AsyncTask&Void, Void, Void& {&&&&@Override protected Void doInBackground(Void... params) {&&&&&&&&while(true);&&&&}}&void startAsyncTask() {&&&&new NimbleTask().execute();}
private static class NimbleHandler extends Handler {
@Override public void handleMessage(Message message) {
super.handleMessage(message);
private static class NimbleRunnable implements Runnable {
@Override public void run() {
while(true);
void createHandler() {
new NimbleHandler().postDelayed(new NimbleRunnable(), Long.MAX_VALUE && 1);
123456789101112131415
private static class NimbleHandler extends Handler {&&&&@Override public void handleMessage(Message message) {&&&&&&&&super.handleMessage(message);&&&&}}&private static class NimbleRunnable implements Runnable {&&&&@Override public void run() {&&&&&&&&while(true);&&&&}}&void createHandler() {&&&&new NimbleHandler().postDelayed(new NimbleRunnable(), Long.MAX_VALUE && 1);}
private static class NimbleTimerTask extends TimerTask {
@Override public void run() {
while(true);
void scheduleTimer() {
new Timer().schedule(new NimbleTimerTask(), Long.MAX_VALUE && 1);
private static class NimbleTimerTask extends TimerTask {&&&&@Override public void run() {&&&&&&&&while(true);&&&&}}&void scheduleTimer() {&&&&new Timer().schedule(new NimbleTimerTask(), Long.MAX_VALUE && 1);}
但是,如果你坚持使用匿名类,只要在生命周期结束时中断线程就可以。
public void onDestroy() {
super.onDestroy();
if (thread != null) {
thread.interrupt();
void spawnThread() {
thread = new Thread() {
@Override public void run() {
while (!isInterrupted()) {
thread.start();
12345678910111213141516171819
private Thread thread;&@Overridepublic void onDestroy() {&&&&super.onDestroy();&&&&if (thread != null) {&&&&&&&&thread.interrupt();&&&&}}&void spawnThread() {&&&&thread = new Thread() {&&&&&&&&@Override public void run() {&&&&&&&&&&&&while (!isInterrupted()) {&&&&&&&&&&&&}&&&&&&&&}&&&&}&&&&thread.start();}
Sensor Manager
void registerListener() {
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
void registerListener() {&&&&SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);&&&&Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);&&&&sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);}
使用Android系统服务不当容易导致泄漏,为了Activity与服务交互,我们把Activity作为监听器,引用链在传递事件和回调中形成了。只要Activity维持注册监听状态,引用就会一直持有,内存就不会被释放。
在Activity结束时注销监听器
private SensorManager sensorM
public void onDestroy() {
super.onDestroy();
if (sensor != null) {
unregisterListener();
void unregisterListener() {
sensorManager.unregisterListener(this, sensor);
1234567891011121314
private SensorManager sensorManager;private Sensor sensor;&@Overridepublic void onDestroy() {&&&&super.onDestroy();&&&&if (sensor != null) {&&&&&&&&unregisterListener();&&&&}}&void unregisterListener() {&&&&sensorManager.unregisterListener(this, sensor);}
Activity泄漏的案例我们已经都走过一遍了,其他都大同小异。建议日后遇到类似的情况时,就使用相应的解决方法。内存泄漏只要发生过一次,通过详细的检查,很容易解决并防范于未然。
是时候做最佳实践者了!为了账号安全,请及时绑定邮箱和手机
点击这里,将文章分享到自己的动态
Android内存泄漏的8种可能
Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同时防止未释放的内存把堆栈(heap)挤爆的可能,所以写出来的代码更为安全。不幸的是,在Java中仍存在很多容易导致内存泄漏的逻辑可能(logical leak)。如果不小心,你的Android应用很容易浪费掉未释放的内存,最终导致内存用光的错误抛出(out-of-memory,OOM)。一般内存泄漏(traditional memory leak)的原因是:当该对象的所有引用都已经释放了,对象仍未被释放。(译者注:Cursor忘记关闭等)逻辑内存泄漏(logical memory leak)的原因是:当应用不再需要这个对象,当仍未释放该对象的所有引用。如果持有对象的强引用,垃圾回收器是无法在内存中回收这个对象。在Android开发中,最容易引发的内存泄漏问题的是。比如的Context,就包含大量的内存引用,例如View Hierarchies和其他资源。一旦泄漏了Context,也意味泄漏它指向的所有对象。Android机器内存有限,太多的内存泄漏容易导致OOM。检测逻辑内存泄漏需要主观判断,特别是对象的生命周期并不清晰。幸运的是,Activity有着明确的,很容易发现泄漏的原因。被视为Activity生命的结束,程序上来看,它应该被销毁了,或者Android系统需要回收这些内存(译者注:当内存不够时,Android会回收看不见的Activity)。如果这个方法执行完,在堆栈中仍存在持有该Activity的强引用,垃圾回收器就无法把它标记成已回收的内存,而我们本来目的就是要回收它!结果就是Activity存活在它的生命周期之外。Activity是重量级对象,应该让Android系统来处理它。然而,逻辑内存泄漏总是在不经意间发生。(译者注:曾经试过一个Activity导致20M内存泄漏)。在Android中,导致潜在内存泄漏的陷阱不外乎两种:全局进程(process-global)的static变量。这个无视应用的状态,持有Activity的强引用的怪物。活在Activity生命周期之外的线程。没有清空对Activity的强引用。检查一下你有没有遇到下列的情况。Static Activities在类中定义了静态Activity变量,把当前运行的Activity实例赋值于这个静态变量。如果这个静态变量在Activity生命周期结束后没有清空,就导致内存泄漏。因为static变量是贯穿这个应用的生命周期的,所以被泄漏的Activity就会一直存在于应用的进程中,不会被垃圾回收器回收。&&&&&static&Activity&&&&&void&setStaticActivity()&{
&&&&&&activity&=&
&&&&View&saButton&=&findViewById(R.id.sa_button);
&&&&saButton.setOnClickListener(new&View.OnClickListener()&{&&&&&&@Override&public&void&onClick(View&v)&{
&&&&&&&&setStaticActivity();
&&&&&&&&nextActivity();
&&&&});Memory Leak 1 – Static ActivityStatic Views类似的情况会发生在单例模式中,如果Activity经常被用到,那么在内存中保存一个实例是很实用的。正如之前所述,强制延长Activity的生命周期是相当危险而且不必要的,无论如何都不能这样做。特殊情况:如果一个View初始化耗费大量资源,而且在一个Activity生命周期内保持不变,那可以把它变成static,加载到视图树上(View Hierachy),,当Activity被销毁时,应当释放资源。(译者注:示例代码中并没有释放内存,把这个static view置null即可,但是还是不建议用这个static view的方法)&&&&static&&&&&void&setStaticView()&{
&&&&&&view&=&findViewById(R.id.sv_button);
&&&&View&svButton&=&findViewById(R.id.sv_button);
&&&&svButton.setOnClickListener(new&View.OnClickListener()&{&&&&&&@Override&public&void&onClick(View&v)&{
&&&&&&&&setStaticView();
&&&&&&&&nextActivity();
&&&&});Memory Leak 2 – Static ViewInner Classes继续,假设Activity中有个,这样做可以提高可读性和封装性。将如我们创建一个内部类,而且持有一个静态变量的引用,恭喜,内存泄漏就离你不远了(译者注:销毁的时候置空,嗯)。&&&&&&&private&static&Object&&&&&&&&void&createInnerClass()&{&&&&&&&&class&InnerClass&{
&&&&&&&&inner&=&new&InnerClass();
&&&&View&icButton&=&findViewById(R.id.ic_button);
&&&&icButton.setOnClickListener(new&View.OnClickListener()&{&&&&&&&&@Override&public&void&onClick(View&v)&{
&&&&&&&&&&&&createInnerClass();
&&&&&&&&&&&&nextActivity();
&&&&});Memory Leak 3 – Inner Class内部类的优势之一就是可以访问外部类,不幸的是,导致内存泄漏的原因,就是内部类持有外部类实例的强引用。Anonymous Classes相似地,匿名类也维护了外部类的引用。所以内存泄漏很容易发生,当你在Activity中定义了匿名的AsyncTsk。当异步任务在后台执行耗时任务期间,Activity不幸被销毁了(译者注:用户退出,系统回收),这个被AsyncTask持有的Activity实例就不会被垃圾回收器回收,直到异步任务结束。&&&&void&startAsyncTask()&{&&&&&&&&new&AsyncTask&Void,&Void,&Void&()&{&&&&&&&&&&&&@Override&protected&Void&doInBackground(Void...&params)&{&&&&&&&&&&&&&&&&while(true);
&&&&&&&&&&&&}
&&&&&&&&}.execute();
&&&&}&&&&super.onCreate(savedInstanceState);
&&&&setContentView(R.layout.activity_main);
&&&&View&aicButton&=&findViewById(R.id.at_button);
&&&&aicButton.setOnClickListener(new&View.OnClickListener()&{&&&&&&&&@Override&public&void&onClick(View&v)&{
&&&&&&&&&&&&startAsyncTask();
&&&&&&&&&&&&nextActivity();
&&&&});Memory Leak 4 – AsyncTaskHandler同样道理,定义匿名的Runnable,用匿名类Handler执行。Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列MessageQueue中,在Message消息没有被处理之前,Activity实例不会被销毁了,于是导致内存泄漏。&&&&void&createHandler()&{&&&&&&&&new&Handler()&{&&&&&&&&&&&&@Override&public&void&handleMessage(Message&message)&{&&&&&&&&&&&&&&&&super.handleMessage(message);
&&&&&&&&&&&&}
&&&&&&&&}.postDelayed(new&Runnable()&{&&&&&&&&&&&&@Override&public&void&run()&{&&&&&&&&&&&&&&&&while(true);
&&&&&&&&&&&&}
&&&&&&&&},&Long.MAX_VALUE&&&&1);
&&&&View&hButton&=&findViewById(R.id.h_button);
&&&&hButton.setOnClickListener(new&View.OnClickListener()&{&&&&&&&&@Override&public&void&onClick(View&v)&{
&&&&&&&&&&&&createHandler();
&&&&&&&&&&&&nextActivity();
&&&&});Memory Leak 5 – HandlerThreads我们再次通过和来展现内存泄漏。&&&&void&spawnThread()&{&&&&&&&&new&Thread()&{&&&&&&&&&&&&@Override&public&void&run()&{&&&&&&&&&&&&&&&&while(true);
&&&&&&&&&&&&}
&&&&&&&&}.start();
&&&&View&tButton&=&findViewById(R.id.t_button);
&&&&tButton.setOnClickListener(new&View.OnClickListener()&{&&&&&&@Override&public&void&onClick(View&v)&{
&&&&&&&&&&spawnThread();
&&&&&&&&&&nextActivity();
&&&&});Memory Leak 6 – ThreadTimerTask只要是匿名类的实例,不管是不是在工作线程,都会持有Activity的引用,导致内存泄漏。&&&&void&scheduleTimer()&{&&&&&&&&new&Timer().schedule(new&TimerTask()&{&&&&&&&&&&&&@Override
&&&&&&&&&&&&public&void&run()&{&&&&&&&&&&&&&&&&while(true);
&&&&&&&&&&&&}
&&&&&&&&},&Long.MAX_VALUE&&&&1);
&&&&View&ttButton&=&findViewById(R.id.tt_button);
&&&&ttButton.setOnClickListener(new&View.OnClickListener()&{&&&&&&&&@Override&public&void&onClick(View&v)&{
&&&&&&&&&&&&scheduleTimer();
&&&&&&&&&&&&nextActivity();
&&&&});Memory Leak 7 – TimerTaskSensor Manager最后,通过可以获取系统服务。这些服务工作在各自的进程中,帮助应用处理后台任务,处理硬件交互。如果需要使用这些服务,可以注册,这会导致服务持有了Context的引用,如果在Activity销毁的时候没有注销这些监听器,会导致内存泄漏。&&&&&&&&void&registerListener()&{
&&&&&&&&&&&&&&&SensorManager&sensorManager&=&(SensorManager)&getSystemService(SENSOR_SERVICE);
&&&&&&&&&&&&&&&Sensor&sensor&=&sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
&&&&&&&&&&&&&&&sensorManager.registerListener(this,&sensor,&SensorManager.SENSOR_DELAY_FASTEST);
&&&&&&&&View&smButton&=&findViewById(R.id.sm_button);
&&&&&&&&smButton.setOnClickListener(new&View.OnClickListener()&{&&&&&&&&&&&&@Override&public&void&onClick(View&v)&{
&&&&&&&&&&&&&&&&registerListener();
&&&&&&&&&&&&&&&&nextActivity();
&&&&&&&&&&&&}
&&&&&&&&});Memory Leak 8 – Sensor Manager总结看过那么多会导致内存泄漏的例子,容易导致吃光手机的内存使垃圾回收处理更为频发,甚至最坏的情况会导致OOM。垃圾回收的操作是很昂贵的开销,会导致肉眼可见的卡顿。所以,实例化的时候注意持有的引用链,并经常进行内存泄漏检查。祝好运。
若觉得本文不错,就分享一下吧!
评论加载中...
相关文章推荐
正在加载中
作者相关文章开发者干货 | Android内存泄漏分析
对于Android App内存泄漏问题开发者会比较困扰,并且类似问题难以复现,比较难定位,一但发生很可能是灾难性的,有千里之堤毁于蚁穴之势。App开发者对内存泄漏问题能够在测试阶段发现并且解决是非常重要的。
什么是内存泄漏?简单来讲是在开发应用时,没有释放掉不需要的内存资源。比如对象不需要了,但是由于没有释放对它的引用, GC(Garbage Collection) 无法回收相应的内存资源,这部分内存就无法被利用了。这种情况就是所谓的“内存泄漏”。
Android内存问题涉及的知识点还是非常多的,下面从开发者角度把内存泄漏问题分三个部分来介绍一下,目的是能够对于内存泄漏问题有一个清晰的了解和分析:
Android内存管理
内存分析工具使用
内存泄漏demo
Android内存管理
Android每个App默认是运行在一个独立进程中, 这个进程运行在一个独立的VM(Virtual Machine)空间,可以参考下面JVM架构图。Android是怎么管理这些App的内存的呢?
Android 4.4之前一直使用的是Dalvik虚拟机作为App的运行的VM, Android 4.4中引入了ART(Android Runtime)作为备选VM,Android5.0起正式将ART作为默认VM。
ART相比Dalvik有以下优点:
1)Ahead-of-time(AOT) compilation instead of Just-in-time (JIT)
Dalvik中采用的是JIT来做动态翻译的,将dex或odex中并排的Dalvik code运行态翻译成native code去执行。JIT的引入使得Dalvik提升了3~6倍的性能,而在ART中完全抛弃了Dalvik的JIT,使用了AOT,直接在安装时用dex2oat将其完全翻译成native code。这一技术的引入,使得VM执行指令的速度又一重大提升。
2)Improvedgarbage collection
Dalvik GC的过程
ART GC的过程
1、当GC被触发时候,其会去查找所有活动的对象,这个时候整个程序与虚拟机内部的所有线程就会挂起,这样目的是在较少的堆栈里找到所引用的对象。需要注意的是这个回收动作是和应用程序同时执行(非并发)。2、GC对符合条件的对象进行标记3、GC对标记的对象进行回收4、恢复所有线程的执行现场继续运行
1、GC将会锁住java堆,扫描并进行标记2、标记完毕释放掉java堆的锁,并且挂起所有线程3、GC对标记的对象进行回收4、恢复所有线程的执行现场继续运行5、重复2-4直到结束
ART主要的改善点在将其非并发过程改变成了部分并发。另外对内存重新分配管理,使得执行时间缩短,据官方测试数据,GC效率提高了l2倍。
3)Improvedmemory usage and reduce fragmentation
Dalvik的内存管理特点是内存碎片化严重,当然这也是Mark and Sweep算法带来的弊端。该算法分为两个阶段:标记(mark)和清除(sweep)。
在标记阶段,collector从mutator根对象开始进行遍历,对从根对象可以访问到的对象都打上一个标识,一般是在对象的header中,将其记录为可达对象。
在清除阶段,collector对堆内存(heap memory)从头到尾进行线性的遍历,如果发现某个对象没有标记为可达对象-通过读取对象的header信息,则就将其回收。
Mark and Sweep算法
ART的解决方案:它将java分了一块空间命名为Large-Object-Space,这块内存空间的引入用来专门存放large object。同时ART又引入了moving collector的技术,即将不连续的物理内存块进行对齐。对齐了后内存碎片化就得到了很好的解决。Large-Object-Space的引入能够有效提高内存的利用率,根官方统计,ART的内存利用率提高了10倍左右。
Dalvik和ART都是使用paging和memory-mapping(mmapping)来管理内存的。这就意味着, 任何被分配的内存都会持续存在, 唯一释放这块内存方式就是释放对象引用(让对象GC Root不可达), 故而让GC程序来回收内存。关于如何管理应用的进程与内存分配,Android官网有比较详细说明。
内存分析工具使用
工欲善其事,必先利其器。接下来会介绍三个内存分析工具:Android Studio自带的Memory Monitor、MAT工具和插件工具Leakcanary
工具1:MemoryMonitor
Memory Monitor是 Android Studio内置的, 官方的内存监测工具,它是图形化的展示当前应用的内存状态, 包括已分配内存, 空闲内存, 内存实时动态等。
Memory Monitor
图中标注1:GC按钮, 点击执行一次GC操作。
图中标注2:Dump Java Heap按钮, 点击会在该调试工程的captures目录下生成一个类似这样"com.talkingdata.demo__13.35.hprof"命名的hprof文件。针对文件的分析可以参考Google官网描述:HPROF Analyzer。
图中标注3:Allocation Traking按钮, 点击一次开始, 再次点击结束,同样会在captrures目录生成一个文件, 类似"com.talkingdata.demo__13.35.alloc"命名的alloc文件,针对文件的分析可以参考Google官网描述:Allocation Tracker。
工具2:MAT
Eclipse MAT(MemoryAnalyzer)是一个快速且功能丰富的Java Heap分析工具, 可以帮助我们寻找内存泄露, 减少内存消耗.
MAT可以分析程序(成千上万的对象产生过程中)生成的Heap dumps文件, 它会快速计算出对象的Retained Size, 来展示是哪些对象没有被GC, 自动生成内存泄露疑点的报告。详细的使用方法可以查阅官方文档。下面简单介绍常用的方式:
获取heap dumps
可以使用 Android Studio 获取 heap dump。点击 Monitors 中的 Dump Java Heap 按钮后,会得到一个 .hprof 文件。生成的 .hprof 文件默认在项目的根目录的 captures 目录下。
heap dumps
因为MAT是用来分析Java程序的hprof文件的,和Android导出的hprof文件的格式有一定区别,所以需要转换为标准格式。
有两种方式可供选择:Android SDK中给我们提供了转换的工具,即platform-tools/hprof-conv ,使用如下命令即可转换我们的hprof文件格式:hprof-conv[源hprof文件的路径] [输出的hprof文件路径]或者选择Captures选项卡,右键相应的.hprof 文件,并选择 Export to standard .hprof。
Exportto standard .hprof
使用MAT分析
打开MAT工具,并加载之前导出的hprof文件,会进入Overview界面。可以从界面中看到Retained Size最大的几个对象。
MATOverview
它列出了按类别分组的对象,MAT可以非常快速地计算各类别的大小和个数,并显示在列表中,这是深入分析的重要指标。它有多种分组方式:Group by class/Group by superclass/Group by classloader/Group by package。甚至还可以按线程分组,不过这需要打开thread_overview。
Dominator Tree
DominatorTree列出了最大的对象。下一级别会显示那些被立即阻止垃圾回收的对象。右键单击可以查看传出和传入的引用或查看Path to GC Roots,以查看保留对象的引用链。
DominatorTree
Path to GC Roots
GC Roots的路径显示了阻止对象被垃圾回收的引用链。有黄点的对象是GC Roots,即被假定为活着的对象。通常GC Roots是当前在线程或系统类的调用堆栈上的对象。用这个方法可以快速找到对象没有被回收的原因。
Path to GC Roots
工具3:Leakcanary
LeakCanary是square出的一款开源的用来做内存泄露检测的工具。被测试App集成LeakCanary之后, 工具检测到潜在的内存泄露后, 会弹出Toast提示,并在测试手机桌面生成一个Leaks的icon:
点击该icon进入Leaks界面, 可以比较清晰的看到内存泄露疑点
对于源码感兴趣的同学可以参考下面:
源码文件结构说明
AbstractAnalysisResultService.java
ActivityRefWatcher.java -- Activity监控者,监控其生命周期
AndroidDebuggerControl.java --Android Debug控制开关,就是判断Debug.isDebuggerConnected()
AndroidExcludedRefs.java -- 内存泄漏基类
AndroidHeapDumper.java --生成.hrpof的类
AndroidWatchExecutor.java -- Android监控线程,延迟5s执行
DisplayLeakService.java -- 显示通知栏的内存泄漏,实现了AbstractAnalysisResultService.java
LeakCanary.java --对外类,提供install(this)方法
ServiceHeapDumpListener.java
internal --这个文件夹用于显示内存泄漏的情况(界面相关)
DisplayLeakActivity.java --内存泄漏展示的Activity
DisplayLeakAdapter.java --内存泄漏展示ListView适配器
DisplayLeakConnectorView.java --内存泄漏展示连接器
FutureResult.java
HeapAnalyzerService.java 在另一个进程启动的Service,用于接收数据并发送数据到界面
LeakCanaryInternals.java
LeakCanaryUi.java
MoreDetailsView.java
RefWatcher创建
watch()方法使用
1)RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。
2)然后在后台线程检查引用是否被清除,如果没有,调用GC。
3)如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。
4)在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。
5)得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。
6)HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。
7)引用链传递到 APP 进程中的DisplayLeakService, 并以通知的形式展示出来。
检测 Activity
1)在Application onCreate()中调用 LeakCanary.install(this)
Github示例:
publicclassExampleApplication extendsApplication {
publicvoidonCreate() {
super.onCreate();
if(LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
LeakCanary.install(this);
// Normal app init code...
2)LeakCanary.install()会返回一个 RefWatcher
publicstaticRefWatcher install(Application application) {
returnrefWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
3)buildAndInstall()同时也会启用一个 ActivityRefWatcher,用于自动监控调用Activity.onDestroy()之后泄露的 activity。
publicRefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if(refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
returnrefW
publicvoidwatchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
//注册 LifecycleCallbacks,用于观察activity是否被回收
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
voidonActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
注:registerActivityLifecycleCallbacks 时API 14引入的监控方式,如要兼容 API 14 以下版本,请重写ActivityonDestroy()在其中调用refWatcher.watch(activity)
检测Fragment
publicabstractclassBaseFragment extendsFragment {
publicvoidonDestroy() {
super.onDestroy();
RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
refWatcher.watch(this);
检测其他对象
RefWatcher refWatcher = ExampleApplication.getRefWatcher(this);
refWatcher.watch(Object);
xxx:leakcanary D/LeakCanary: In com.talkingdata.demo:1.0:1.
xxx:leakcanary D/LeakCanary: * com.talkingdata.demo.app.AppBaseFunction has leaked:
xxx:leakcanary D/LeakCanary: * GC ROOT android.location.LocationManager$ListenerTransport.mListener
xxx:leakcanary D/LeakCanary: * references com.talkingdata.demo.BaseActivity$LocationTracker.mContext
xxx:leakcanary D/LeakCanary: * leaks com.talkingdata.demo.app.AppBaseFunction instance
xxx:leakcanary D/LeakCanary: * Retaining: 1.2 kB.
xxx:leakcanary D/LeakCanary: * Reference Key: c-44f5-831d-38fa21bb0595
xxx:leakcanary D/LeakCanary: * Device: Huawei google Nexus 6P angler
xxx:leakcanary D/LeakCanary: * Android Version: 8.0.0 API: 26 LeakCanary: 1.6-SNAPSHOT
xxx:leakcanary D/LeakCanary: * Durations: watch=5016ms, gc=168ms, heap dump=1248ms, analysis=89686ms
xxx:leakcanary D/LeakCanary: * Details:
内存泄漏Demo
假设有一个单例的ListenerManager,可以add/remove Listener,有一个Activity,实现了该Listener,且这个Activity中持有大对象BigObject,BigObject中包含一个大的字符串数组和一个Bitmap List。
代码片段如下:
ListenerManager
publicclassListenerManager {
privatestaticListenerManager sI
privateListenerManager() {}
privateList&SampleListener& listeners = newArrayList&&();
publicstaticListenerManager getInstance() {
if(sInstance == null) {
sInstance = newListenerManager();
publicvoidaddListener(SampleListener listener) {
listeners.add(listener);
publicvoidremoveListener(SampleListener listener) {
listeners.remove(listener);
MemoryLeakActivity
publicclassMemoryLeakActivity extendsAppCompatActivity implementsSampleListener {
privateBigObject mBigObject = newBigObject();
protectedvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_leak);
ListenerManager.getInstance().addListener(this);
publicvoiddoSomething() {
1)使用Memory Monitor 分析启动我们要检测的Activity(MemoryLeakActivity), 然后退出, 在monitor中查看内存变化。操作步骤和结果如下:
步骤1:点击"Analyzer Tasks"视图中的启动按钮, 启动分析。
步骤2:查看"Analysis Result"中的分析结果,点击"Leaked Activityes"中的具体实例, 该实例的引用关系将会展示在"Reference Tree"视图中。
步骤3:根据"Reference Tree"视图中的引用关系,查找leak的activity, 也就是谁Dominate这个activity对象。
可以看到是ListenerManager的静态单例sInstance最终支配了MemoryLeakActivity.sIntance连接到GC Roots, 故而导致MemoryLeakActivityGC Roots可达, 导致activity无法被回收。Heap Viewer查看内存消耗上述步骤,可以让我们快速定位可能的内存泄露。除了内存泄露, 还有内存消耗过大。我们可以在Heap Viewer中查看分析内存的消耗点, 如下:2) 使用MAT工具分析相对与Android Studio的Memory Monitor, HPROF工具来说, MAT的使用显得更加生涩、难以理解些,但是MAT功能很全面。Android Studio导出的hprof文件需要转换下才可以在MAT中使用,转换命令如下:$ hprof-conv com.anly.samples__15.07.hprof mat.hprofHistogram定位内存消耗MAT中很多视图的第一行, 都可以输入正则, 来匹配我们关注的对象实例。Dominate Tree查看支配关系使用OQL查询相关对象对于Android App开发来说,大部分的内存问题都跟四大组件, 尤其是Activity相关, 故而我们会想查出所有Activity实例的内存占用情况, 可以使用OQL来查询:GC路径定位问题上面几个视图都可以让我们很快速的找到内存的消耗点,接下来我们要分析的就是为何这些个大对象没有被回收。对象没有被回收是因为他有到GC Roots的可达路径。那么我们就来分析下这条路径(Path toGC Roots), 看看是谁在这条路中"搭桥"。如下, 进入该对象的"path2gc"视图:会发现与HPROF Analyzer异曲同工,找出了是ListenerManager的静态实例导致了MemoryLeakActivity无法回收。3) 使用Leakcanary工具分析步骤1:加入LeakCanaryapp的build.gradle中加入:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
Application中加入:
publicclassSampleApplication extendsApplication {
publicvoidonCreate() {
super.onCreate();
LeakCanary.install(this);
步骤2:操作要检测的界面, 查看结果当发生可疑内存泄露时, 会在桌面生成一个"Leaks"的图标,点击进去可以看到内存泄露的疑点报告:可以看到内存泄漏的分析结果和之前两个工具结果一致。内存问题的分析, 无外乎分析对象的内存占用(Retained Size), 找出Retained Size大的对象, 找到其直接支配(Immediate Dominator), 跟踪其GC可达路径(Path to GC Roots), 从而找到是谁让这个大对象活着。对于上面三种工具可以混合使用,一般情况下Android Studio自带的工具结合LeakCanary就能分析内存问题,MAT有更专业的一些功能,比如Heap比较等等值得探索。为了更好的分享开发经验每周四我们都会准备一篇技术干货与大家分享!专治各种疑难杂症希望你会喜欢!
责任编辑:
声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。
全面覆盖金融、地产、快消、零售、出行、政府等行业领军企业,你还在犹豫什么?
平均月活跃用户6.5亿,超过12万款移动应用,旨为提供最具影响力的数据报告与榜单
今日搜狐热点

我要回帖

更多关于 java static对象 的文章

 

随机推荐