最新Android Studio和 MAT 内存泄漏分析(1),2024年最新7年老Android一次坑爹的面试经历

学习福利

【Android 详细知识点思维脑图(技能树)】

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司19年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!


Java的内存管理就是对象的分配和释放问题。内存的分配是由程序员来完成,内存的释放由GC(垃圾回收机制)完成。GC 为了能够正确释放对象,必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等。这是Java程序运行较慢的原因之一。

释放对象的原则:该对象不再被引用。

GC的工作原理:

将对象考虑为有向图的顶点,将引用关系考虑为有向图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从 main 进程开始执行,那么该图就是以 main 进程为顶点开始的一棵根树。在有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象与这个根顶点不可达,那么我们认为这个对象不再被引用,可以被 GC 回收。

下面举一个例子说明如何用有向图表示内存管理。对于程序的每一个时刻,我们都有一个有向图表示JVM的内存分配情况。以下右图,就是左边程序运行到第6行的示意图。

程序员Android 转于网络

另外,Java使用有向图的方式进行内存管理,可以消除引用循环的问题,例如有三个对象相互引用,但只要它们和根进程不可达,那么GC也是可以回收它们的。当然,除了有向图的方式,还有一些别的内存管理技术,不同的内存管理技术各有优缺点,在这里就不详细展开了。

四、Java中的内存泄漏


如果一个对象满足以下两个条件:

(1)这些对象是可达的,即在有向图中,存在通路可以与其相连

(2)这些对象是无用的,即程序以后不会再使用这些对象

就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,继续占用着内存。

在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄漏。

程序员Android 转于网络

五、Android中常见的内存泄漏


(1)单例造成的内存泄漏

程序员Android 转于网络

程序员Android 转于网络

这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:

1.如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以没有任何问题。

2.如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。

当然,Application 的 context 不是万能的,所以也不能随便乱用,例如Dialog必须使用 Activity 的 Context。对于这部分有兴趣的读者可以自行搜索相关资料。

(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对象,并且无法被系统回收掉。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

从上图可以看出,我们搜出了3个TestActivity的对象,一般在退出某个activity后,就结束了一个activity的生命周期,应该会被GC正常回收才对的。通常情况下,一个activity应该只有1个实例对象,但是现在居然有3个TestActivity对象存在,说明之前的操作,产生了3个TestActivity对象,并且无法被系统回收掉。

[外链图片转存中…(img-XwOShcWF-1715362454372)]

[外链图片转存中…(img-Ac1seetE-1715362454373)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值