您的位置:首页 > 移动开发 > Android开发

[置顶] Android 内存泄漏---新能优化专题(MAT的使用)

2017-07-11 19:34 453 查看
1.问题:内存泄漏是什么?

内存溢出就不是内存泄漏,内存泄漏过多积累下来就会造成内存溢出。

内存不在G掌控之内了。


GC垃圾回收机制漏掉的垃圾对象–无法回收。(垃圾对象持有的内存一直存在累加)

(1)什么是垃圾回收机制?

吃完饭餐具不用管,服务员自动回收。
服务员就是GC,什么时候去回收它呢?
某个对象不再有任何的引用的时候才会进行回收。(不能往上追溯到GC Root的对象)
例如在C中持有了D的引用:D d=new D();,这时D能回收么?回收不了可以追溯到GC节点。
例如在F中持有了G的引用:但是追溯不到GC ROOT,那么这个就可以被回收。




可以作为GC Root 的引用的点事:这些都会追溯到GC Root引用点。
JavaStack中的引用的对象。(堆栈中)
方法区中静态引用指向的对象。
方法区中静态常量引用指向的对象。
Native方法中JNI引用的对象。
Thread--活着的线程()


JVM内存模型



垃圾对象:1.GC可以直接回收的:2.GC会受不了,但是我们程序员又忘记回收的。


3.确定是否存在内存泄漏—–确定泄漏的大致范围。

首先我们从容易内存泄漏又很危险的Activity,View等系统重量级的组件查找。
首先我们用AndroidStudIo自带的Android Monitors来查看我们内存情况(相信大家都用过吧):如下图所示:




这里是我的一个小小项目用来测试用的:
首先我们先运行这个小项目由于这个Demon自己写的所以我在MainPinlun这个Activity里面故   意写了一个内存泄漏的案例代码:




8.63MB
我们同样的再次访问,如果这个Activity有内存泄漏那么内存占用会增大。我们只需要点击app再次启动然后同样步骤退出。




8.91MB同样可以多来几次。
为了更加清楚了解详情。我们可以点击Memory Usage来查看。如下图操作之后:
第一次和第二次数据对比:
1.首先我们运行As:
2.点击进入评论Activity里面
3.返回键退出应用。
4.如下操作:




5.直接点击应用打开APP到Activity里面。




前后对比两个所占内存逐渐增加。
我们可以前后2个的Memory Usage里面对比看:








第一次进入退出之后有一个Activity被引用没有被回收。Activities: 1
第二次进入退出之后有2个Activity被引用没有被回收。Activities:  2
这里我们可以分析出这个Activity可能里面导致内存泄漏。

上面我们只能大概的推测到这个Activity存在内存泄漏。但是具体是那部分代码我们依然不能够知晓。
这时候我们用到Android Monitor的快照功能:




如图所示:



这里我们可以点击每个Activity类然后查看每个实例(instance),以及最下面的引用树。
左边出现的是当前进程中的所有类,右边是每个类里面的实例。最下面是引用树。




我们可以通过上图来分析,int数组在内存中的实例被引用树中的TypedArray所引用,而Resources引用了TypeArray一次类推,最后WeakReference知道这是弱引用,这种我们不需要关心,因为是弱引用,随时可以被系统回收。那我们如何找内存泄漏的类呢?这么多的类。我们可以通过Package Tree
View搜索我们项目包名来缩小我们查询的范围。上面我们可以将所想要测验的Activity都运行之后点击,然后点击Android Monitor里面的快照,然后进行点击左表项目里面的类,如果右边没有实例说明这个类没有泄露,如果有实例引用,但是弱引用那么也可以排除掉。但是在引用树里面也好多引用,这么多引用,我们很难判断。这时候我们就需要更精细的查找泄漏的位置。


4.更精细地查找泄漏的位置


MAT(MemoryAnalyzer)工具的使用:
前后可以进行对比:泄漏前后对比。
首先我们先获取一次运行的快照并在androidStudio工具的左边栏的Captures中导图.hprof保存到电脑中。然后我们在运行一次上次运行的点击同样的几个想要测试的Activity然后快照,同样导出.hpro文件。然后安装Memory Analyzer工具,然后分别打开两个文件。我们记住第一次和第二次2次对应的文件。然后我们 分别获取Histogram 然后在第二次快照里面点击与另一个比较堆内存,如下图比较过程:
点击




获取第一个内存分析:




点击




获取第二个内存分析:




这里可以根据包来查看可以缩小范围:




然后我们在第二个中点击:




按钮
进行两个内存比较分析:
得出比较结果分析图表:
这是我们输入我们项目的包名:如下图com.exa....




我们可以看出MainPinglun这个Activity和MIlstView相比要多与原来的。可以大概知道发生泄漏,一般我们先看重量级的Activty,一般Activty引用导致下面的变量被引用情况,所以我们先分析Activty。
我们返回到第二个文件中点击分析按钮输入下面包名com.exa.....找到刚才分析出来的泄露内存的Activity然后右键ListObjects->with  Outging references出现引用对象列表:




我们点击这个引用之后展开又有好多引用,这时候很难判断,我们可以进行最后一步操作了。去除弱引用,软引用等不必关心系统会自动处理回收的:




得到最终定位:




我们可以知道在MainPinglun里面有一个Message持有这个Activity引用。我们找到程序:


private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
tv_tops.setText(msg.what+"ff");
}
};
private void loadHandler() {
Message message = Message.obtain();
message.what = 1001;
mHandler.sendMessageDelayed(message, 1000 *60*10);
}


这里我们分析代码:这里我们分析指导mHandle是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,而当这个Acvitity退出时候,消息队列中未处理或者正在处理消息,呢么消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,


我们如何解决呢?

我们如何解决呢?
我们都知道静态内部类不会依赖外部类,这样我们可以避免Activity被Handler持有引用,但是如果我们需要在MyHandler里面更新UI控件,那么我们可以对Hander持有的对象使用弱引用,这样在回调时候可以回收Hander持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息, 所以我们在Stop或者Destroy时应该移除消息队列中的消息,这样我们可以安全的进行写代码了。修改之后的代码如下:


MyHandler mHandler = new MyHandler(this);

private static class MyHandler extends Handler {
private WeakReference<Context> preference;

public MyHandler(Context context) {
if (context != null) {
preference = new WeakReference<Context>(context);
}
}

@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainPinglun activity= (MainPinglun)          preference.get();
activity.tv_tops.setText(msg.what + "ff");

}
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}


然后获取内存分析图:



可以看到此时的Activities以及都被回收。

下一章主要分析Android中常见的几种内存泄漏以及分析并解决。

该吃饭去了,快8点了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: