推荐阅读:
JVM高级特性与最佳实战(一)————JAVA内存区域
JVM高级特性与最佳实战(二)————对象的创建过程,内存布局,访问定位
JVM中字符串常量池的详细剖析
JVM高级特性与最佳实战(三)————如何判断对象已死?
引言
在讨论垃圾回收算法之前,我必须得补充下昨天我们讨论的JVM高级特性与最佳实战(三)————如何判断对象已死?。我们先来看下就算在不可达的队列中,对象如何活过来?请看代码:
package JVM高级特性与实战.对象死里逃生;
/**
* Author:haozhixin
* Func: 对象在垃圾收集标记后的死里逃生
* Date: 20190817
*/
public class AliveFormGC {
public static AliveFormGC SAVE_HOOK=null;
public void isAlive(){
System.out.println("oh im alive now");
}
@Override
protected void finalize() throws Throwable{
super.finalize();
System.out.println("finalize method executed!!");
//重新赋引用
AliveFormGC.SAVE_HOOK= this;
System.out.println("1.SAVE_HOOK value is :"+SAVE_HOOK);
}
public static void main(String [] args) throws Throwable{
SAVE_HOOK = new AliveFormGC();
SAVE_HOOK=null;
//调用finalize方法。
System.gc();
//因为finalize优先级低,请下边的方法稍微等一下。
Thread.sleep(500);
//第一次竟然活过来了?
System.out.println("2.SAVE_HOOK value is :"+SAVE_HOOK);
if(SAVE_HOOK!=null){
SAVE_HOOK.isAlive();
System.out.println("3.SAVE_HOOK value is :"+SAVE_HOOK);
}else {
System.out.println("oh im dead now");
System.out.println("4.SAVE_HOOK value is :"+SAVE_HOOK);
}
SAVE_HOOK=null;
System.gc();
Thread.sleep(500);
//第二次没有活过来?
//是因为任何一个对象的finalize方法都只会被系统自动调用一次。面临下次回收的时候,finalize方法不会再被执行
System.out.println("5.SAVE_HOOK value is :"+SAVE_HOOK);
if(SAVE_HOOK!=null){
SAVE_HOOK.isAlive();
System.out.println("6.SAVE_HOOK value is :"+SAVE_HOOK);
}else {
System.out.println("oh im dead now");
System.out.println("7.SAVE_HOOK value is :"+SAVE_HOOK);
}
}
}
运行结果:
finalize method executed!!
1.SAVE_HOOK value is :JVM高级特性与实战.对象死里逃生.AliveFormGC@1b87e2a
2.SAVE_HOOK value is :JVM高级特性与实战.对象死里逃生.AliveFormGC@1b87e2a
oh im alive now
3.SAVE_HOOK value is :JVM高级特性与实战.对象死里逃生.AliveFormGC@1b87e2a
5.SAVE_HOOK value is :null
oh im dead now
7.SAVE_HOOK value is :null
思路分析我写到代码备注里了,大家仔细分析,下面我们言归正传,继续来讲垃圾回收算法。
标记-清除算法
- 思路
算法分为标记和清除两部分,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,他的标记过程就是对象的标记判定,这一点如果还有不清楚的去看我的博客补充下基础知识。JVM高级特性与最佳实战(三)————如何判断对象已死?。 - 特点
(1)这个方法是最基本的算法,后续的收集算法都是基于这个标记清除算法演变的。
(2)标记和清除的效率太低,影响回收速度。
(3)标记清除后会产生大量的不连续的内存碎片。如果碎片过多,在进行下一次大对象的内存分配时,内存不够会提前触发另一次的垃圾回收动作。
复制算法
- 思路
他将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。 - 特点:
(1)每次都是对整个半区进行内存回收,内存分配时不用考虑内存碎片等复杂情况。
(2)实现思路简单,而且运行高效。
(3)但是每次都把内存分为两块,可用的却只有一块,代价有点大。
(4)如果对象存活率较高的时候,要进行较多的复制,效率也会变的低。 - 优化
IBM公司的专门研究表明,新生代中的对象98%都是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间。而是将内存分为一块较大的Eden空间和两块较小的Survivior空间,每次使用Eden和其中的一块Survivor空间。当回收的时候,将Eden和Survivior的存活的对象一次性复制到另外一块Survivior,然后清理之前的Eden和Survivor。
这里我提一下,当Survivor内存不够了,会跟其他内存(老年代)进行内存的分配担保,至于怎么实现的这里不多提了。
标记-整理算法
- 思路
标记的过程与标记-清除算法一致,但是后续步骤增加了使对象向一端移动,直接清理另外一端的内存。 - 特点
主要针对老年代
分代收集算法
- 思路
根据对象的存活周期将内存分为几块。一般是把JAVA堆分为新生代和老年代。根据各个年代的特点选择合适的算法,比如,在新生代中,每次垃圾回收都发现有大批的对象死去,只有少量存活,那就选用复制算法。而老年代因为对象存活率高,没有额外空间进行分配担保,所以要使用标记-清除, 标记-整理的算法。
总结
大家主要想明白理论就可以了,一般的JAVA面试中也不会对虚拟机进行代码级深入的提问。下一节我们讲解下当前主流的垃圾收集器。
作者:select you from me
来源:CSDN
转载请联系作者获得授权并注明出处。