Android内存泄露分析(MemoryAnalyzer工具)

news/2024/7/7 19:10:28

前提条件:

1,电脑安装了java 运行环境  

2,手机端开启了 USB 调试开关 

3,获取 root 权限

基本步骤:

1,使用eclipse 自带的 DDMS 工具分析各线程的内存使用情况,如下图所示



Heap视图界面会定时刷新,在对应用的不断的操作过程中就可以看到内存使用的变化。

怎样判断当前进程是否有内存泄漏呢?

这里需要注意一个值:VM Heap页面中部有一个data object选项,即数据对象,也就是我们的程序中大量存在的类类型的对象。

在data object一行中有一列是“Total Size”,其值就是当前进程中所有Java数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏。如上图中选中行所示。

可以据此判断内存有泄漏:
1) 不断的操作当前应用,或者重复某一动作,注意观察data object的Total Size值。

2) 正常情况下Total Size值都会稳定在一个有限的范围内,也就是说如果程序中的的代码逻辑良好,

没有创建的对象不被GC机制正常回收的情况,即便 我们不断的操作生成很多对象,而在虚拟机不断的进行垃圾回收的过程中,这些对象都被正常回收了,内存使用量会保持在一个比较稳定的水平。

3) 如果代码中存在对象引用没有释放的情况,则data object的Total Size值在每次GC后不会有明显的回落,随着操作次数的增多Total Size的值会越来越大。
正常情况下,一个虚拟机的进程的内存在64M, 如果内存泄漏会发现 Heap Size 在不断的逼近 64M, 一旦达到这个值时,就会出现退出应用等情况。


发生内存泄露,Total Size的值越来越大时,按下“Dump HPROF file”按钮,这个时候会提示设置hprof文件的保存路径。保存后,可以对比log来分析是哪些操作造成了内存泄漏。

2,点击 按钮,导出 hprof 文件,使用MAT 工具进行分析。具体分析步骤和过程详见下面链接

http://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html

3,打开 MAT 工具,File–>Open Heap Dump… 选择你刚刚保存的 hprof 文件打开

此时,会弹出一个错误,如下图所示:


提示:  Unknown HPROF Version (JAVA PROFILE 1.0.3) (java.io.IOException)

哦,不要以为是 MAT 工具版本不对,其实是 android 的 hprof 文件在这里需要进行转换一下格式才可以使用 MAT 打开,不知道 谷歌在这里

捣了什么鬼,难道是优化?

使用 android sdk 目录下的 tools 中一个工具进行转化一下

4,使用AndrodiSDK/tools/hprof-conv转化hprof文件, 

首先,要通过控制台进入到你的 android sdk tools 目录下

例如 hprof-conv input.hprof     out.hprof

再使用MAT工具打开转换后的 hprof 文件,就能看到完整的内存使用分析报告了。

如下所示是 MAT 分析内存使用的主界面:


点击上图中的 Reports –>Leak Suspects 则可以进一步看到更详细的内存泄漏疑点。


在其中怀疑的地方,点击 Details 就可以看到具体的内存使用情况了。

tip1:

有一种比较好的方法是,在内存泄漏开始时抓取一个 hprof 文件,在内存泄漏很厉害时,app 濒临崩溃时再抓取一个hprof 文件。

对比看这两个图,就很容易看出来上面的饼图中哪一块存在内存泄漏。

有的时候能直接看出来多了一块。那么我们就从那一块入手进行分析。比较快能得到结果。

tip2:

看 dominator_tree,可以从列表中 data_object 最多的几项数据入手分析,如下文件所示(136,80对应的两项)



我这边曾经就因为在 onStart 中添加了一个 PhoneStateListener 的监听,而在 onStop 中未设置为空,导致内存泄漏。

这里引用一点别人总结的实例:

原因1:

          BraodcastReceiver,ContentObserver,FileObserver,Cursor在Activity onDeatory或者某类声明周期结束之后一定要unregister或者close掉,否则这个Activity类会被system强引用,不会被内存回收。

原因2:

        不要直接对Activity进行直接引用作为成员变量,如果不得不这么做,请用private WeakReference mActivity来做,相同的,对于Service等其他有自己声明周期的对象来说,直接引用都需要谨慎考虑是否会存在内存泄露的可能。()

[java] view plain copy print ?

  1. private static class MyHandler extends Handler {  
  2.         private WeakReference<GeneralSettings> mStatus;  
  3.   
  4.         public MyHandler(GeneralSettings activity) {  
  5.             mStatus = new WeakReference<GeneralSettings>(activity);  
  6.         }  
  7.   
  8.         @Override  
  9.         public void handleMessage(Message msg) {  
  10.             GeneralSettings status = mStatus.get();  
  11.             if (status == null) {  
  12.                 return;  
  13.             }  
  14.   
  15.             switch (msg.what) {  
  16.                 case EVENT_UPDATE_STATS:  
  17.                     status.updateTimes();  
  18.                     sendEmptyMessageDelayed(EVENT_UPDATE_STATS, 1000);  
  19.                     break;  
  20.             }  
  21.         }  
  22. }  

[java] view plain copy
print ?
  1. private static class MyHandler extends Handler {  
  2.         private WeakReference<GeneralSettings> mStatus;  
  3.   
  4.         public MyHandler(GeneralSettings activity) {  
  5.             mStatus = new WeakReference<GeneralSettings>(activity);  
  6.         }  
  7.   
  8.         @Override  
  9.         public void handleMessage(Message msg) {  
  10.             GeneralSettings status = mStatus.get();  
  11.             if (status == null) {  
  12.                 return;  
  13.             }  
  14.   
  15.             switch (msg.what) {  
  16.                 case EVENT_UPDATE_STATS:  
  17.                     status.updateTimes();  
  18.                     sendEmptyMessageDelayed(EVENT_UPDATE_STATS, 1000);  
  19.                     break;  
  20.             }  
  21.         }  
  22. }  
private static class MyHandler extends Handler {
        private WeakReference<GeneralSettings> mStatus;

        public MyHandler(GeneralSettings activity) {
            mStatus = new WeakReference<GeneralSettings>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            GeneralSettings status = mStatus.get();
            if (status == null) {
                return;
            }

            switch (msg.what) {
                case EVENT_UPDATE_STATS:
                    status.updateTimes();
                    sendEmptyMessageDelayed(EVENT_UPDATE_STATS, 1000);
                    break;
            }
        }
}

原因3:

对 Context 保持了一个长生命周期的引用。

[java] view plain copy print ?

  1. private static Drawable sBackground;  
  2. @Override  
  3. protected void onCreate(Bundle state) {  
  4.   super.onCreate(state);  
  5.   TextView label = new TextView(this);  
  6.   label.setText(“Leaks are bad”);  
  7.   if (sBackground == null) {  
  8.     sBackground = getDrawable(R.drawable.large_bitmap);  
  9.   }  
  10.   label.setBackgroundDrawable(sBackground);  
  11.   setContentView(label);  
  12. }  


[java] view plain copy
print ?
  1. private static Drawable sBackground;  
  2. @Override  
  3. protected void onCreate(Bundle state) {  
  4.   super.onCreate(state);  
  5.   TextView label = new TextView(this);  
  6.   label.setText(”Leaks are bad”);  
  7.   if (sBackground == null) {  
  8.     sBackground = getDrawable(R.drawable.large_bitmap);  
  9.   }  
  10.   label.setBackgroundDrawable(sBackground);  
  11.   setContentView(label);  
  12. }  
private static Drawable sBackground; 
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}

sBackground的生命周期比Activity要长,label引用到context,sBackground又把label设为内部成员变量,所以sBackground引用到了context,导致activity结束的时候context还是不能释放,从而引发内存泄露。(不甚理解,还要仔细研究一下)


最后,作者给了一点总结(有些地方还是不太懂……)

总结:

1.      对activity的引用应该控制在activity的生命周期之内;

2.      如果不能就考虑使用getApplicationContext或者getApplication;

3.      尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context),即使要使用,也要考虑适时把外部成员变量置空(如上例可以通过把sBackground的callback置空来解决内存泄露的问题);也可以在内部类中使用弱引用来引用外部类的变量;

4.      做到在onDestroy中释放资源,如清空对图片等资源有直接引用或者间接引用的数组(使用array.clear();array = null);
转自:https://blog.csdn.net/pugongying1988/article/details/9122699


http://www.niftyadmin.cn/n/664877.html

相关文章

如何使用URL跳转解决Web服务80端口被封问题?

如何使用URL跳转解决Web服务80端口被封问题&#xff1f; 2007-07-15 14:55 随着宽带的普及&#xff0c;很多用户都喜欢使用ADSL路由器几个人一起共享上网。因为这样不仅上网的费用降低了&#xff0c;还可以和别人分享自己电脑上的各种资源。但是&#xff0c;许多宽带用户的ADSL…

计算机辅助设计与制造考试题,《计算机辅助设计与制造》复习题汇总.pdf

《计算机辅助设计与制造》复习题汇总.pdf (11页)本资源提供全文预览&#xff0c;点击全文预览即可全文预览,如果喜欢文档就下载吧&#xff0c;查找使用更方便哦&#xff01;19.90 积分一、填空题 1&#xff0e;CAD/CAM 集成系统主要是指 、 、 的集成。 2&#xff0e;CAPP 专家…

Piktochart(制作信息图表)

插件介绍&#xff1a; 信息图表提高了可视化的数据&#xff0c;不但设计优美、效果显著&#xff0c;同时帮助我们更准确、更高效地了解信息&#xff0c;这信息图表应该怎么制作呢&#xff1f;今天就给大家推荐一个简单的制作信息图片的插件。Piktochart是一个来自马来西亚的非专…

指针应用时的注意事项

1. 对指针进行取值之前&#xff0c;一定要保证指针不为空 空指针不可取其值或者继续访问 #define BinNodePosi(T) BinNode<T>*template <typename T> struct BinNode{T data;BinNodePosi(T) lChild;BinNodePosi(T) rChild; };#define IsLChild(x) (x).parent->…

计算机xiuli教程,电脑上不去网维修教程

电脑上不去网维修教程高张大叔作为本次行动的主角&#xff0c;笔记本维修是多年行走于维修前和维修后都未消失。在笔记本电脑可以在安全等级、性能、操作过程等方面都进行了性能要求不可过高、车身结构很重要、电源插座不易损坏等错误设计。由于蓝屏&#xff0c;蓝牙功能已经失…

佛家经典语录 - 佛的告诫

一、人之所以痛苦&#xff0c;在于追求错误的东西。 二、与其说是别人让你痛苦&#xff0c;不如说自己的修养不够。 三、如果你不给自己烦恼&#xff0c;别人也永远不可能给你烦恼。因为你自己的内心&#xff0c;你放不下。 四、好好的管教你自己&#xff0c;不要管别人。 …

《大道至简》第一章读后感

这周我们上了第一节java语言课&#xff0c;也了解到了《大道至简》这本书。本书第一章以愚公移山的故事引入&#xff0c;恰当而又新奇。讲述了编程的精义。曾经我以为语言类非常重要&#xff0c;如C语言等。如今才发现语言不过是一种工具&#xff0c;真正重要的是编程的思路&am…

【JSP】简单登陆界面

学生登陆查询系统 1 程序的主要功能及特点 实现一个登录界面的基本功能&#xff0c;具体要求&#xff1a; 登录界面login.jsp含有表单&#xff0c;用户能够输入用户名和密码&#xff0c;并提交表单给verify.jsp。Verify.jsp读取表单信息&#xff0c;并验证接收的用户名和密码是…