Android内存泄漏是一个经常要遇到的问题,程序在内存泄漏的时候很容易导致OOM的发生。那么如何查找内存泄漏和避免内存泄漏就是需要知晓的一个问题,首先我们需要知道一些基础知识。
强引用: 强引用是Java中最普通的引用,随意创建一个对象然后在其他的地方引用一下,就是强引用,强引用的对象Java宁愿OOM也不会回收他
软引用: 软引用是比强引用弱的引用,在Java gc的时候,如果软引用所引用的对象被回收,首次gc失败的话会继而回收软引用的对象,软引用适合做缓存处理 可以和引用队列(ReferenceQueue)一起使用,当对象被回收之后保存他的软引用会放入引用队列
弱引用: 弱引用是比软引用更加弱的引用,当Java执行gc的时候,如果弱引用所引用的对象被回收,无论他有没有用都会回收掉弱引用的对象,不过gc是一个比较低优先级的线程,不会那么及时的回收掉你的对象。 可以和引用队列一起使用,当对象被回收之后保存他的弱引用会放入引用队列
虚引用: 虚引用和没有引用是一样的,他必须和引用队列一起使用,当Java回收一个对象的时候,如果发现他有虚引用,会在回收对象之前将他的虚引用加入到与之关联的引用队列中。可以通过这个特性在一个对象被回收之前采取措施
下面是一个例子:
public class Main {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue
目前oracle jdk和open jdk的虚拟机都为Hotspot,android 为Dalvik和Art
曾经的GC算法:引用计数
简短的说引用计数就是对每一个对象的引用计算数字,如果引用就+1,不引用就-1,回收掉引用计数为0的对象。来达到垃圾回收
弊端:如果两个对象都应该被回收但是他俩却互相依赖,那么他两者的引用永远都不会为0,那么就永远无法回收,无法解决循环引用的问题
这个算法只在很少数的虚拟机中使用过
现代的GC算法
以上四种算法信息引用自QQ空间团队分享Android GC 那点事&version=11000003&pass_ticket=nhSGhYD4LC9FWvUPv26Y7AdIzqEDu8FTImf2AKlyrCk%3D) ,总结的特别棒
对象在GC Root中可达,也就是他的引用不为空,所以GC无法回收它也就会导致内存泄漏
GC Root起点
当一个对象在引用链中失S#x53BB;了引用,那么他就真的要告别世界了吗,其实并不是,虚拟机会给他“缓刑”,每一个对象有一个finalize() 方法,虚拟机是否给他缓刑取决于这个对象的这个方法是否被执行,如果这个对象的这个方法没有被覆盖或者这个方法被执行过一次,那么就要“行刑”了。真的是“续一秒”
如果这个对象的finalize()方法应该被执行,那么虚拟机会将它放在F-Queue队列中,稍后虚拟机会自动创建一个Finalizer线程去执行这个队列中的对象的这个方法。如果对象在finalize()中成功自救,举个例子,把自己和一个存在的对象强引用,那么就不会被回收,否则就真的被回收了。
但是虚拟机并不会保证Finalizer线程执行结束再进行回收,因为如果在某一个对象的finalize()方法中执行了死循环或者超级耗时的操作,虚拟机等待这个执行结束的话就会导致整个Gc崩溃了
首先注意这个方法只能被执行一次,第二次就会标记了这个方法被执行过不会再执行了,其次,这个方法不一定会被执行到,所以不要依赖finalize()去自救。这不是好的做法。
Android2.3之后支持了并发的GC。
两者的差别:
首先非并发GC简单粗暴,直接挂起所有的线程,此时Java堆中肯定不会有任何的添加和修改,此时去递归GC树,然后标记-清理。但是这样会造成很大的开销,大家都等着你岂不是很没面子= =
然而非并发的GC是一点一点来的,跟线程同步进行这样就不会有很长时间的等待,但是你要明白一个道理,想把地扫干净这段时间必须没人来踩,所以他要有挂起线程的过程。
那么并发是怎么实现的呢?首先有个知识点就是Jvm在分配内存的时候,有两种方式
创建对象是一个频繁的操作,那么我们如何保证原子性呢?两种方案
我们用的是第二种 233
所以获取Java堆锁的时候,重点来了,我们逐个线程去锁TLAB,而不是一次全锁住,当然提高了并发GC的效率,所以更快。但是引来的问题就是并发的问题,所以下一步要重复去修改在一个个探索时候被改的对象。也就需要更多的CPU资源。
首先我们知道虚拟机如何去GC才能了解到如何让一个对象被正确的回收,这样才不能内存泄漏
其次无论是并发GC还是非并发GC都会导致挂起其他的所有线程,那么就会带来程序卡顿。
ART在GC上做到了更加细粒度的控制,可以更加流畅的GC
首先铺垫一句话:非静态的内部类和匿名类会隐式的持有外部类的引用
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d("smallSohoSolo", "Hello Handler");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
Log.d("smallSohoSolo", "Running");
}
}, 1000 * 60 * 10); //10分钟之后执行
finish();
}
}
这段代码有很明显的内存泄漏,首先Handler和Runnable都是匿名内部类的实例,他们都会持有MainActivity的引用,
有人可能会说短暂的内存泄漏又能怎样?这是错误的想法,因为只要发生内存泄漏,在这段时间只要进行了大内存的操作(比如加载一个照片墙),就有风险因为这个内存泄漏造成OOM(占用内存肯定剩下的少了)
上面这个如何修改呢?
将Runnable和Handler改成static 或者在外部定义内部使用。
简单粗暴 —> LeakCanary: Square出品的库,当出现内存泄漏的时候会出现
精打细算 —> Android Studio 内存工具: 可以Dump下来当前的内存路径,然后分析出来哪些对象目前的状态。很强
来自:https://techblog.toutiao.com/2017/08/16/untitled-4/