JVM垃圾回收机制(三)--如何回收
1.3 如何回收
对于如何回收,这里就要说到虚拟机的算法实现了.我们常见的算法有Mark-Sweep,Mark-Compact,Copying,Generational Garbage Collection.
1.3.1 Mark-Sweep,标记-清除算法,按照字面过程,分为标记和清除两个过程.
标记过程就是标记哪些对象是在用的,哪些是不再被引用可以回收的.(见Marking)
清除过程就是将标记过的对象进行回收.(见Normal Deletion)
缺点:①Mark-Sweep算法效率不高②Mark-Sweep会产生不连续的内存碎片,当分配大对象的时候,若无法找到足够大的内存空间,不得不再进行一次垃圾回收.
直接上图,简单明了,一目了然.
1.3.2 Mark-Compact,标记-整理紧凑算法(zzm的书上翻译的是标记整理算法,可能不是那么准确,Compact原意是使紧凑,我翻译成了整理紧凑更好些).
标记-整理紧凑算法的过程,标记跟1.3.1中的标记过程一样,整理紧凑(Compact)是在Sweep(清除)后进行一次整理紧凑处理,就是将原来不连续的内存碎片整理成紧凑型.
直接上图:
1.3.3 Generational Garbage Collection(分代收集算法)
因为要面对全体对象,因此,当对象数量很多时,标记和整理紧凑的效率并不高.随着越来越多的对象被分配,对象列表的增长导致更长的垃圾收集时间.
然而,通过应用程序的实证分析表明,大多数对象的存活时间是短暂的.见图
根据对象分配行为,我们可以将堆进行更小粒度的区分,将堆分为:新生代(Young Generation), 老年代(Old or Tenured Generation)和永久代( Permanent Generation).
又将新生代分为Eden区和Survivor的S0区和S1区,默认的内存空间比例为Eden:S0=8:1,我们也可以动态设置比例大小,设置方法:增加虚拟机参数,-XX:SurvivorRatio=8。
1.3.3.1新生代具体分配及回收过程如下:
①新对象分配的时候都会被分配到Eden区,而幸存区Survivor的两部分都是空的.每个新分配的对象都有一个"对象年龄计数器".
②当Eden区的空间被占用满后,将会触发一次minor garbage collection,简称minor GC.
③引用的对象(在使用中的)被移动到第一块幸存区(S0),存活年龄+1,不再引用的对象在Eden区清理(minor GC)的时候被清除.
④当Eden区再次被占满后,进行minor GC,此次GC会把Eden区和S0区的存活对象都移动到S1区,而Eden区和S0区的不再被引用的对象被清除.被移动到S1区的对象,对象年龄+1
⑤当下次Eden区再次被占满后,Eden区和S1进行minor GC,将存活对象移到S0区,对象存活年龄+1.
⑥当对象的存活年龄达到默认(默认为15,或配置的)的年龄阈值时,对象将进入老年代(Tenured Generation).虚拟机参数设置:-XX:MaxTenuringThreshold,规定进入老年代的年龄值大小.
这里要注意,以上规则只是一般情况.虚拟机提供了另外三种进入老年代的机制(动态年龄判定,大对象直接进入老年代,担保进入):
①动态年龄判定成功后,进入老年代:这里zzm的书上讲的很片面,并且容易误导别人,因此在这里重新阐述.
JVM会将每个对象的年龄信息、各个年龄段对象的总大小记录在"age table"表中.基于"age table",根据survivor区大小,survivor区目标使用率(-XX:TargetSurvivorRatio,-XX:TargetSurvivorRatio=50,设定survivor区的目标使用率,默认50,即survivor区对象目标使用率为50%),最大晋升年龄阈值(-XX:MaxTenuringThreshold),JVM会动态的计算tenuring threshold的值.一旦对象年龄达到了tenuring threshold就会晋升到老年代.计算公式为:size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100);
源码位置:https://github.com/FlashLightNing/openjdk-notes/blob/master/src/share/vm/gc_implementation/shared/ageTable.cpp
第81行开始的uintageTable::compute_tenuring_threshold(size_t survivor_capacity)方法中.
在源码中我们很容易知道动态年龄判断的标准,当某个age年龄段的对象大小及以下的年龄段所占总Survivor(S0区或者S1区)比例达到Survivor区目标使用率,就把此age年龄作为新的晋升阈值(TenuringThreshold),然后再与最大晋升年龄阈值比较,若age<MaxTenuringThreshold,则把age作为最终的晋升阈值,若不是,则以MaxTenuringThreshold作为最终晋升阈值.
②对象大小达到设定值进入老年代:规定在对象达到多大大小的时候,直接进入老年代.通过虚拟机参数设置进入老年代的对象大小:-XX:PretenureSizeThreshold=[byte size],这里的单位是字节,如512k,要写成512*1024=524288
③担保进入:当新生代中对象在经过n次minor GC后,依然无法满足对象的存储空间要求,那么就需要通过担保机制直接进入老年代.
担保机制过程:当进行minor GC前,虚拟机会检查老年代的空间是否大于新生代中所有对象空间,如果大于,默认minor GC是安全的,虚拟机会把minor GC后Survivor区无法容纳的对象通过担保机制移动到老年代.
如果不大于,虚拟机会检查设置的虚拟机参数-XX:HandlePromotionFailure的值.若为false,则表示不允许担保失败,老年代会进行一次major GC,清理出更多的空间.若为true,
表示允许担保失败,那么虚拟机会根据历次晋升到老年代的对象所占空间的平均值作为经验值,与老年代中剩余空间大小比较,决定是否再进行major GC.
1.3.3.2老年代垃圾回收
老年代(Tenured Generation)采用标记-整理紧凑(Mark-Compact)算法实现垃圾回收.