点击上方 Android扫地僧 ,选择 星标 公众号
重磅资源、干货分享,快上车!
内存泄漏往往面试会问到是否有解决过实际问题,这个如果答不好,也是很容易露馅的,面试时必须得把这艘火箭造好,才有机会进去拧螺丝。其他完整面试专题,请关注公众号查看。
1、Java虚拟机内存模型
完整内容参考 Java内存模型
虚拟机栈:线程私有,随线程创建而创建。栈里面是一个一个“栈帧”,每个栈帧对应一次方法调用。栈帧中存放了局部变量表(基本数据类型变量和对象引用)、操作数栈、方法出口等信息。当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误。
本地方法栈:线程私有,这部分主要与虚拟机用到的Native方法相关,一般情况下,并不需要关心这部分的内容。
程序计数器:也叫PC寄存器,JVM支持多个线程同时运行,每个线程都有自己的程序计数器。倘若当前执行的是 JVM 的方法,则该寄存器中保存当前执行指令的地址;倘若执行的是native方法,则PC寄存器中为空。(PS:线程执行过程中并不都是一口气执行完,有可能在一个CPU时钟周期内没有执行完,由于时间片用完了,所以不得不暂停执行,当下一次获得CPU资源时,通过程序计数器就知道该从什么地方开始执行)
方法区:方法区存放类的信息(包括类的字节码,类的结构)、常量、静态变量等。字符串常量池就是在方法区中。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的是与Java堆区分开来。很多人都更愿意把方法区称为“永久代”(Permanent Generation)。从jdk1.7已经开始准备“去永久代”的规划,jdk1.7的HotSpot中,已经把原本放在方法区中的静态变量、字符串常量池等移到堆内存中。
堆:堆中存放的是数组(PS:数组也是对象)和对象。当申请不到空间时会抛出OutOfMemoryError。
2、内存泄漏原理
Android是基于Java的一门语言,其垃圾回收机制也是基于Jvm建立的,所以说Android的GC也是通过可达性分析算法来判定的。但是如果一个存活时间长的对象持有另一个存活时间短的对象就会导致存活时间短的对象在GC时被认定可达而不能被及时回收,而继续停留在堆内存中,也就是我们常说的内存泄漏。Android对每个App内存的使用有着严格的限制,大量的内存泄漏就可能导致OOM(内存溢出),也就是在new对象请求空间时,堆中没有剩余的内存分配所导致的。
3、Java引用类型
Java对引用的分类有 StrongReference(默认引用类型), SoftReference, WeakReference,PhatomReference 四种。
4、OOM是否可以try catch?
只有在一种情况下,这样做是可行的:
在try语句中声明了很大的对象,导致OOM,并且可以确认OOM是由try语句中的对象声明导致的,那么在catch语句中,可以释放掉这些对象,解决OOM的问题,继续执行剩余语句。但是这通常不是合适的做法。
5、finalize()方法
finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。
finalize方法至多由GC执行一次,即使在finalize()方法中复活对象,第二次GC如果对象仍不可达,那么还是会被回收。
6、Android查看内存/CPU占用信息
adb shell dumpsys meminfo pid
adb shell dumpsys cpuinfo|find "包名"
adb shell top
7、Android内存泄漏如何定位
使用Android Studio 自带的AndroidProfiler工具或MAT
使用Square产品的LeakCanary.
8、LeakCanary原理
监测机制利用了Java的WeakReference和ReferenceQueue,通过将Activity(对象)包装到WeakReference中,被WeakReference包装过的Activity对象如果被回收,该WeakReference引用会被放到ReferenceQueue中,通过监测ReferenceQueue里面的内容,就能检查到Activity是否能够被回收。
详细原理:https://blog.csdn.net/tobetheender/article/details/53609563
9、常见内存泄漏的场景
集合类泄漏
Vector v = new Vector(10);
单例造成的内存泄漏
由于单例的静态特性使得其生命周期跟应用的生命周期一样长,如果单例内部持有Activity/Fragment/View等的引用,会导致其无法被回收。
匿名内部类/非静态内部类和异步线程
Java中,非静态内部类和匿名内部类默认会持有外部类的引用,比较容易引起内存泄漏的有Handler, AsyncTask使用匿名内部类形式。
资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
一些不良代码造成的内存压力
例如,只进行注册而没有反注册,构造 Adapter 时,没有使用缓存的 convertView。
10、避免内存泄漏的优化
直接就是针对上面提到的4点进行优化,集合资源add后不用及时remove;Handler使用静态内部类+弱引用,AsyncTask可以在onDestroy()内调用cancel方法;资源使用完及时进行close获取release;注册与反注册成对出现,Adapter进行convertView复用。
11、一个线程OOM后,其他线程是否还能正常工作?
美团18年三面题目
结合第1题,大家第一反应容易直观觉得是堆溢出,然后结合第1题堆是线程共享的,所以其他线程也都异常。实际上并非如此,当一个线程抛出OOM异常后,它所占据的内存资源会立即全部被释放掉,从而不会影响其他线程的运行。同理,栈溢出也是一样的。如果主线程抛异常退出了,子线程也还能运行,除非这些子线程是守护线程,那么会随着主线程异常结束而结束。
在看点这里
本文分享自微信公众号 - Android扫地僧(Android-Mas)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。