文章内容
一、什么是JVM的垃圾回收机制
释放垃圾占用的空间,防止内存泄漏。有效的使用可以使用的内存,对内存堆已经死亡或者长时间没有使用的对象进行清除和回收。
二、怎么定义垃圾
1、引用计数法
在堆中存储对象时,在对象头处维护一个counter计数器,如果一个对象增加了一个引用与之相连,则将counter++。如果一个引用关系失效则counter–。如果一个对象的counter变为0,则说明该对象已经被废弃,不处于存活状态。
1)优点
逻辑简单
2)缺点
引用和去引用伴随加法和减法,影响性能
3)致命的缺陷
对于循环引用的对象无法进行回收
循环引用的例子:
public class Object {
java.lang.Object field = null;
public static void main(String[] args) {
Object objectA = new Object();
Object objectB = new Object();//位置1
objectA.field = objectB;
objectB.field = objectA;//位置2
objectA = null;
objectB = null;//位置3
}
}
当执行到位置1的时候,两个对象的引用计数器是1
执行到位置2,两个对象的引用计数器是2
执行到位置3,两个对象各自释放一个,引用计数器变成1
上面代码如果采用引用计数法,则两个对象就不能回收(实际上可以回收)。
2、可达性分析
通过一系列的名为“GC Root”的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可使用的,垃圾收集器将回收其所占的内存。所以JVM判断对象需要存活的原则是:能够被一个根对象到达的对象。
什么是可达呢?就是对象A中引用了对象B,那么就称A到B可达。
哪些可以做GC root
- java虚拟机栈中的引用的对象。
- 方法区中的类静态属性引用的对象。 (一般指被static修饰的对象,加载类的时候就加载到内存中。)
- 方法区中的常量引用的对象。
- 本地方法栈中的JNI(native方法)引用的对象。
三、垃圾回收算法
1、标记-清除
标记-清除算法,将垃圾回收分为两个阶段:标记阶段和清除阶段。
一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象;然后,在清除阶段,清除所有未被标记的对象。
这种方式清理出来的空闲内存是不连续的
2、复制算法
将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
- 与标记-清除算法相比,复制算法是一种相对高效的回收方法
- 不适用于存活对象较多的场合,如老年代(复制算法适合做新生代的GC)
- 堆得内存利用率不高,只能用一半
如果在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选中这种算法。
3、标记-整理
标记过程仍与”标记-清除”中标记的过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界(除存活对象)以外的内存。
一句话:先标记,再整理,最后删除。最终避免内存碎片的产生。
4、分代收集算法:(新生代的GC+老年代的GC)
只是根据对象的存活周期的不同将内存划分为几块儿。一般是把Java划分为新生代和老年代:短命对象归为新生代,长命对象归为老年代。
- 少量对象存活,适合复制算法:在新生代中,每次GC时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成GC。
- 大量对象存活,适合用标记-清理/标记-整理:在老年代中,因为对象存活率高、没有额外空间对他进行分配担保,就必须使用“标记-清理”/“标记-整理”算法进行GC。