Android内存泄漏是一个经常要遇到的问题,程序在内存泄漏的时候很容易导致OOM的发生。那么如何查找内存泄漏和避免内存泄漏就是需要知晓的一个问题,首先我们需要知道一些基础知识。
Java的四种引用
强引用: 强引用是Java中最普通的引用,随意创建一个对象然后在其他的地方引用一下,就是强引用,强引用的对象Java宁愿OOM也不会回收他
软引用: 软引用是比强引用弱的引用,在Java gc的时候,如果软引用所引用的对象被回收,首次gc失败的话会继而回收软引用的对象,软引用适合做缓存处理 可以和引用队列(ReferenceQueue)一起使用,当对象被回收之后保存他的软引用会放入引用队列
弱引用: 弱引用是比软引用更加弱的引用,当Java执行gc的时候,如果弱引用所引用的对象被回收,无论他有没有用都会回收掉弱引用的对象,不过gc是一个比较低优先级的线程,不会那么及时的回收掉你的对象。 可以和引用队列一起使用,当对象被回收之后保存他的弱引用会放入引用队列
虚引用: 虚引用和没有引用是一样的,他必须和引用队列一起使用,当Java回收一个对象的时候,如果发现他有虚引用,会在回收对象之前将他的虚引用加入到与之关联的引用队列中。 可以通过这个特性在一个对象被回收之前采取措施
下面是一个例子:
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;
}
}
}
Java GC
目前oracle jdk和open jdk的虚拟机都为Hotspot,android 为Dalvik和Art
曾经的GC算法:引用计数
简短的说引用计数就是对每一个对象的引用计算数字,如果引用就+1,不引用就-1,回收掉引用计数为0的对象。来达到垃圾回收
弊端:如果两个对象都应该被回收但是他俩却互相依赖,那么他两者的引用永远都不会为0,那么就永远无法回收, 无法解决循环引用的问题
这个算法只在很少数的虚拟机中使用过
现代的GC算法
- 标记回收算法(Mark and Sweep GC) :从"GC Roots"集合开始,将内存整个遍历一次,保留所有可以被GC Roots直接或间接引用到的对象,而剩下的对象都当作垃圾对待并回收,这个算法需要中断进程内其它组件的执行并且可能产生内存碎片。
- 复制算法(Copying) :将现有的内存空间分为两快,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
- 标记-压缩算法(Mark-Compact) :先需要从根节点开始对所有可达对象做一次标记,但之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高。
- 分代 :将所有的新建对象都放入称为年轻代的内存区域,年轻代的特点是对象会很快回收,因此,在年轻代就选择效率较高的复制算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老生代的内存空间。对于新生代适用于复制算法,而对于老年