(2)非静态内部类创建静态实例造成的内存泄漏
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();
}//…
}
class TestResource {//…
}
}
非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。
(3)匿名内部类造成的内存泄漏
匿名内部类默认也会持有外部类的引用。如果在Activity/Fragment中使用了匿名类,并被异步线程持有,如果没有任何措施这样一定会导致泄漏。
程序员Android 转于网络
ref1和ref2的区别是,ref2使用了匿名内部类。我们来看看运行时这两个引用的内存:
程序员Android 转于网络
可以看到,ref1没什么特别的。但ref2这个匿名类的实现对象里面多了一个引用:
this$0这个引用指向MainActivity.this,也就是说当前的MainActivity实例会被ref2持有,如果将这个引用再传入一个异步线程,此线程和此Acitivity生命周期不一致的时候,就会造成Activity的泄漏。
例子:Handler造成的内存泄漏
程序员Android 转于网络
在该 MainActivity 中声明了一个延迟10分钟执行的消息 Message,mHandler 将其 push 进了消息队列 MessageQueue 里。当该 Activity 被 finish() 掉时,延迟执行任务的 Message 还会继续存在于主线程中,它持有该 Activity 的 Handler 引用,然后又因 为 Handler 为匿名内部类,它会持有外部类的引用(在这里就是指 MainActivity),所以此时 finish() 掉的 Activity 就不会被回收了,从而造成内存泄漏。
修复方法:在 Activity 中避免使用非静态内部类或匿名内部类,比如将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。如果需要用到Activity,就通过弱引用的方式引入 Activity,避免直接将 Activity 作为 context 传进去。另外, Looper 线程的消息队列中还是可能会有待处理的消息,所以我们在 Activity 的 Destroy 时或者 Stop 时应该移除消息队列 MessageQueue 中的消息。见下面代码:
程序员Android 转于网络
(4)资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File, Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
(5)一些不良代码造成的内存压力
有些代码并不造成内存泄漏,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。比如,Adapter里没有复用convertView等。
六、Android中内存泄漏的排查与分析
(1)利用Android Studio的Memory Monitor来检测内存情况
先来看一下Android Studio 的 Memory Monitor界面:
程序员Android 转于网络
最原始的内存泄漏排查方式如下:
重复多次操作关键的可疑的路径,从内存监控工具中观察内存曲线,看是否存在不断上升的趋势,且退出一个界面后,程序内存迟迟不降低的话,可能就发生了严重的内存泄漏。
这种方式可以发现最基本,也是最明显的内存泄漏问题,对用户价值最大,操作难度小,性价比极高。
下面就开始用一个简单的例子来说明一下如何排查内存泄漏。
首先,创建了一个TestActivity类,里面的测试代码如下:
@Override
protected void processBiz() {
mHandler = new Handler();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
MLog.d(“------postDelayed------”);
}
}, 800000L);
}
运行项目,并执行以下操作:进入TestActivity,然后退出,再重新进入,如此操作几次后,最后最终退出TestActivity。这时发现,内存持续增高,如图所示:
程序员Android 转于网络
好了,这时我们可以假设,这里可能出现了内存泄漏的情况。那么,如何继续定位到内存泄漏的地址呢?这时候就得点击“Dump java heap”按钮来收集具体的信息了。
(2)使用Android Studio生成Java Heap文件来分析内存情况
注意,在点击 Dump java heap 按钮之前,一定要先点击Initate GC按钮强制GC,建议点击后等待几秒后再次点击,尝试多次,让GC更加充分。然后再点击Dump Java Heap按钮。
这时候会生成一个Java heap文件并在新的窗口打开:
程序员Android 转于网络
这时候,点击右上角的“Analyzer Task”,再点击出现的绿色按钮,让Android studio帮我们自动分析出有可能潜在的内存泄漏的地方:
程序员Android 转于网络
如上图所示,Android studio提示有3个TestActivity对象可能出现了内存泄漏。而且左边的Reference Tree(引用树),也大概列出了该实体类被引用的路径。如果是一些比较简单的内存泄漏情况,仅仅看这里就大概能猜到是哪里导致了内存泄漏。
但如果是比较复杂的情况,还是推荐使用MAT工具(Memory Analyzer)来继续分析比较好。
(3)使用Memory Analyzer(MAT)来分析内存泄漏
MAT是Eclipse出品的一个插件,当然也有独立的版本。下载链接:MAT下载地址
在这里先提醒一下:MAT并不会准确地告诉我们哪里发生了内存泄漏,而是会提供一大堆的数据和线索,我们需要根据自己的实际代码和业务逻辑去分析这些数据,判断到底是不是真的发生了内存泄漏。
MAT支持对标准格式的hprof文件进行内存分析,所以,我们要先在Android Studio里先把Java heap文件转成标准格式的hprof文件,具体步骤如下:
点击左侧的capture,选择对应的文件,并右键选择“Export to standard .hprof”导出标准的hprof文件:
程序员Android 转于网络
导出标准的hprof文件后,在MAT工具里导入,则看到以下界面:
程序员Android 转于网络
MAT中提供了非常多的功能,这里我们只要学习几个最常用的就可以了。上图那个饼状图展示了最大的几个对象所占内存的比例,这张图中提供的内容并不多,我们可以忽略它。在这个饼状图的下方就有几个非常有用的工具了。
Histogram:直方图,可以列出内存中每个对象的名字、数量以及大小。
Dominator Tree:会将所有内存中的对象按大小进行排序,并且我们可以分析对象之间的引用结构。
1)Dominator Tree
程序员Android 转于网络
从上图可以看到右边存在着3个参数。Retained Heap表示这个对象以及它所持有的其它引用(包括直接和间接)所占的总内存,因此从上图中看,前两行的Retained Heap是最大的,分析内存泄漏时,内存最大的对象也是最应该去怀疑的。
另外大家应该可以注意到,在每一行的最左边都有一个文件型的图标,这些图标有的左下角带有一个红色的点,有的则没有。带有红点的对象就表示是可以被GC Roots访问到的,
可以被GC Root访问到的对象都是无法被回收的。那么这就可以说明所有带红色的对象都是泄漏的对象吗?当然不是,因为有些对象系统需要一直使用,本来就不应该被回收。
如果发现有的对象右边有写着System Class,那么说明这是一个由系统管理的对象,并不是由我们自己创建并导致内存泄漏的对象。
根据我们在Android studio的Java heap文件的提示,TestActivity对象有可能发生了内存泄漏,于是我们直接在上面搜TestActivity(这个搜索功能也是很强大的):
左边的inspector可以查看对象内部的各种信息:
程序员Android 转于网络
当然,如果你觉得按照默认的排序方式来查看不方便,你可以自行设置排序的方式:
-
Group by class
-
Group by class loader
-
Group by package
程序员Android 转于网络
从上图可以看出,我们搜出了3个TestActivity的对象,一般在退出某个activity后,就结束了一个activity的生命周期,应该会被GC正常回收才对的。通常情况下,一个activity应该只有1个实例对象,但是现在居然有3个TestActivity对象存在,说明之前的操作,产生了3个TestActivity对象,并且无法被系统回收掉。
接下来继续查看引用路径。
对着TestActivity对象点击右键 -> Merge Shortest Paths to GC Roots(当然,这里也可以选择Path To GC Roots) -> exclude all phantom/weak/soft etc. references
为什么选择exclude all phantom/weak/soft etc. references呢?因为弱引用等是不会阻止对象被垃圾回收器回收的,所以我们这里直接把它排除掉
程序员Android 转于网络
接下来就能看到引用路径关系图了:
程序员Android 转于网络
从上图可以看出,TestActivity是被this0又被callback所引用,接着它又被Message中一串的next所引用…到这里,我们就已经分析出内存泄漏的原因了,接下来就是去改善存在问题的代码了。
2)Histogram
程序员Android 转于网络
这里是把当前应用程序中所有的对象的名字、数量和大小全部都列出来了,那么Shallow Heap又是什么意思呢?就是当前对象自己所占内存的大小,不包含引用关系的。
上图当中,byte[]对象的Shallow Heap最高,说明我们应用程序中用了很多byte[]类型的数据,比如说图片。可以通过右键 -> List objects -> with incoming references来查看具体是谁在使用这些byte[]。
当然,除了一般的对象,我们还可以专门查看线程对象的信息:
程序员Android 转于网络
Histogram中是可以显示对象的数量的,比如说我们现在怀疑TestActivity中有可能存在内存泄漏,就可以在第一行的正则表达式框中搜索“TestActivity”,如下所示:
程序员Android 转于网络
接下来对着TestActivity右键 -> List objects -> with outgoing references查看具体TestActivity实例
注:
List objects -> with outgoing references :表示该对象的出节点(被该对象引用的对象)
List objects -> with incoming references:表示该对象的入节点(引用到该对象的对象)
如果想要查看内存泄漏的具体原因,可以对着任意一个TestActivity的实例右键 -> Merge Shortest Paths to GC Roots(当然,这里也可以选择Path To GC Roots) -> exclude all phantom/weak/soft etc. references,如下图所示:
程序员Android 转于网络
程序员Android 转于网络
从这里可以看出,Histogram和Dominator Tree两种方式下操作都是差不多的,只是两种统计图展示的侧重点不太一样,实际操作中,根据需求选择不同的方式即可。
3)两个hprof文件的对比
为了排查内存泄漏,经常会需要做一些前后的对比。下面简单说一下两种对比方式:
1.直接对比
工具栏最右边有个“Compare to another heap dump”的按钮,只要点击,就可以生成对比后的结果。(注意,要先在MAT中打开要对比的hprof文件,才能选择对比的文件):
程序员Android 转于网络
2.添加到campare basket里对比
在window菜单下面选择compare basket:
程序员Android 转于网络
在文件的Histogram view模式下,在navigation history下选择add to compare basket:
程序员Android 转于网络
然后就可以通过 Compare Tables 来进行对比了:
程序员Android 转于网络
七、总结
最后
希望大家能有一个好心态,想进什么样的公司要想清楚,并不一定是大公司,我选的也不是特大厂。当然如果你不知道选或是没有规划,那就选大公司!希望我们能先选好想去的公司再投或内推,而不是有一个公司要我我就去!还有就是不要害怕,也不要有压力,平常心对待就行,但准备要充足。最后希望大家都能拿到一份满意的 offer !如果目前有一份工作也请好好珍惜好好努力,找工作其实挺累挺辛苦的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
参考docs.qq.com/doc/DSkNLaERkbnFoS0ZF
mpare Tables 来进行对比了:
程序员Android 转于网络
七、总结
最后
希望大家能有一个好心态,想进什么样的公司要想清楚,并不一定是大公司,我选的也不是特大厂。当然如果你不知道选或是没有规划,那就选大公司!希望我们能先选好想去的公司再投或内推,而不是有一个公司要我我就去!还有就是不要害怕,也不要有压力,平常心对待就行,但准备要充足。最后希望大家都能拿到一份满意的 offer !如果目前有一份工作也请好好珍惜好好努力,找工作其实挺累挺辛苦的。
这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。
由于篇幅有限,这里以图片的形式给大家展示一小部分。
[外链图片转存中…(img-tY2wM2zW-1724080274624)]
参考docs.qq.com/doc/DSkNLaERkbnFoS0ZF