Android内存泄漏是一个常常要遇到的问题,程序在内存泄漏的时候很容易致使OOM的发生。那么如何查找内存泄漏和避免内存泄漏就是须要知晓的一个问题,首先咱们须要知道一些基础知识。java
强引用: 强引用是Java中最普通的引用,随意建立一个对象而后在其余的地方引用一下,就是强引用,强引用的对象Java宁愿OOM也不会回收他android
软引用: 软引用是比强引用弱的引用,在Java gc的时候,若是软引用所引用的对象被回收,首次gc失败的话会继而回收软引用的对象,软引用适合作缓存处理 能够和引用队列(ReferenceQueue)一块儿使用,当对象被回收以后保存他的软引用会放入引用队列算法
弱引用: 弱引用是比软引用更加弱的引用,当Java执行gc的时候,若是弱引用所引用的对象被回收,不管他有没有用都会回收掉弱引用的对象,不过gc是一个比较低优先级的线程,不会那么及时的回收掉你的对象。 能够和引用队列一块儿使用,当对象被回收以后保存他的弱引用会放入引用队列缓存
虚引用: 虚引用和没有引用是同样的,他必须和引用队列一块儿使用,当Java回收一个对象的时候,若是发现他有虚引用,会在回收对象以前将他的虚引用加入到与之关联的引用队列中。 能够经过这个特性在一个对象被回收以前采起措施并发
下面是一个例子:oracle
public class Main { public static void main(String[] args) throws InterruptedException { ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); String sw = "虚引用"; switch (sw) { case "软引用": Object objSoft = new Object(); SoftReference<Object> softReference = new SoftReference<>(objSoft, referenceQueue); System.out.println("GC前获取:" + softReference.get()); objSoft = null; System.gc(); Thread.sleep(1000); System.out.println("GC后获取:" + softReference.get()); System.out.println("队列中的结果:" + referenceQueue.poll()); break; /* * GC前获取:java.lang.Object@61bbe9ba * GC后获取:java.lang.Object@61bbe9ba * 队列中的结果:null * */ case "弱引用": Object objWeak = new Object(); WeakReference<Object> weakReference = new WeakReference<>(objWeak, referenceQueue); System.out.println("GC前获取:" + weakReference.get()); objWeak = null; System.gc(); Thread.sleep(1000); System.out.println("GC后获取:" + weakReference.get()); System.out.println("队列中的结果:" + referenceQueue.poll()); /* * GC前获取:java.lang.Object@61bbe9ba * GC后获取:null * 队列中的结果:java.lang.ref.WeakReference@610455d6 * */ break; case "虚引用": Object objPhan = new Object(); PhantomReference<Object> phantomReference = new PhantomReference<>(objPhan, referenceQueue); System.out.println("GC前获取:" + phantomReference.get()); objPhan = null; System.gc(); //此处的区别是当objPhan的内存被gc回收以前虚引用就会被加入到ReferenceQueue队列中,其余的引用都为当引用被gc掉时候,引用会加入到ReferenceQueue中 Thread.sleep(1000); System.out.println("GC后获取:" + phantomReference.get()); System.out.println("队列中的结果:" + referenceQueue.poll()); /* * GC前获取:java.lang.Object@61bbe9ba * GC后获取:null * 队列中的结果:java.lang.ref.WeakReference@610455d6 * */ break; } } }
目前oracle jdk和open jdk的虚拟机都为Hotspot,android 为Dalvik和Artide
曾经的GC算法:引用计数工具
简短的说引用计数就是对每个对象的引用计算数字,若是引用就+1,不引用就-1,回收掉引用计数为0的对象。来达到垃圾回收post
弊端:若是两个对象都应该被回收可是他俩却互相依赖,那么他二者的引用永远都不会为0,那么就永远没法回收, 没法解决循环引用的问题ui
这个算法只在不多数的虚拟机中使用过
现代的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下来当前的内存路径,而后分析出来哪些对象目前的状态。很强