咱们接着下面一篇持续学习JVM的基本知识。
对象存活判断上篇中咱们介绍过JVM垃圾回收综述中说过一次垃圾回收之后会有一些对象存活。这节咱们介绍两个判断对象存活的算法。
判断对象存活有援用计数算法和可达性剖析算法。
1、援用计数算法
给每一个对象增加一个援用计数器,每当有一个中央援用它时,计数器值加1;每当有一个中央不再援用它时,计数器值减1,这样只有计数器的值不为0,就阐明还有中央援用它,它就不是无用的对象。
这种办法看起来非常简单,但目前许多支流的虚拟机都没有选用这种算法来治理内存,起因就是当某些对象之间相互援用时,无奈判断出这些对象是否已死。如下图,对象1和对象2都没有被堆外的变量援用,而是被对方相互援用,这时他们尽管没有用途了,然而援用计数器的值依然是1,无奈判断他们是死对象,垃圾回收器也就无奈回收。
2、可达性剖析算法
理解可达性剖析算法之前先理解一个概念——GC Roots,垃圾收集的终点,能够作为GC Roots的有虚拟机栈中本地变量表中援用的对象、办法区中动态属性援用的对象、办法区中常量援用的对象、本地办法栈中JNI(Native办法)援用的对象。 当一个对象到GC Roots没有任何援用链相连(GC Roots到这个对象不可达)时,就阐明此对象是不可用的,是死对象。如下图:object1、object2、object3、object4和GC Roots之间有可达门路,这些对象不会被回收,但object5、object6、object7到GC Roots之间没有可达门路,这些对象就是死对象。
下面被断定为非存活的死对象(object5、object6、object7)并不是必死无疑,还有解救的余地。进行可达性剖析后对象和GC Roots之间没有援用链相连时,对象将会被进行一次标记,接着会判断如果对象没有笼罩Object的finalize()办法或者finalize()办法曾经被虚拟机调用过,那么它们就会革除;如果对象笼罩了finalize()办法且还没有被调用,则会执行finalize()办法中的内容,所以在finalize()办法中如果从新与GC Roots援用链上的对象关联就能够援救本人。当然,理论中个别不会这么做。
GC算法接下来讲GC的算法,次要有标记-革除算法、复制算法、标记-整顿算法、分代收集算法。
1、标记-革除算法
最根底的收集算法是“标记-革除”(Mark-Sweep)算法,分两个阶段:首先标记出所有须要回收的对象,在标记实现后对立回收所有被标记的对象。
长处:不须要进行对象的挪动,并且仅对不存活的对象进行解决,在存活对象比拟多的状况极为无效。
有余:一个是效率问题,标记和革除两个过程的效率都不高;另一个是空间问题,标记革除之后会产生大量不间断的内存碎片,空间碎片太多可能导致当前在程序运行过程须要调配较大对象时,无奈找到足够的间断内存而不得不提前触发另一个的垃圾收集动作。
上面两张图从两个角度说明了标记-分明算法:
2、复制算法
为了解决效率问题,一种称为复制(Copying)的收集算法呈现了,它将可用内存按容量划分为大小相等的两块,每次只应用其中的一块。当这一块内存用完了,就将还存活着的对象复制到另外一块上,而后再把曾经应用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存调配时也就不必思考内存碎片等简单状况,只有挪动堆顶指针,按程序分配内存即可,实现简略,运行高效。代价是内存放大为原来的一半。
复制算法过程如上面两张图示意:
商业虚拟机用这个回收算法来回收新生代。IBM钻研表明98%的对象是“朝生夕死“,不须要依照1-1的比例来划分内存空间,而是将内存分为一块较大的”Eden“空间和两块较小的Survivor空间,每次应用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活的对象一次性复制到另外一个Survivor空间上,最初清理掉Eden和方才用过的Survivor空间。Hotspot虚拟机默认Eden和Survivor的比例是8-1.即每次可用整个新生代的90%, 只有一个survivor,即1/10被”节约“。当然,98%的对象回收只是个别场景下的数据,咱们没有方法保障每次回收都只有不多于10%的对象存活,当Survivor空间不够时,须要依赖其余内存(老年代)进行调配担保(Handle Promotion).
如果另外一块survivor空间没有足够空间寄存上一次新生代收集下来的存活对象时,这些对象将间接通过调配担保机制进入老年代。
上面大略介绍一下这个eden survivor复制的过程。
Eden Space字面意思是伊甸园,对象被创立的时候首先放到这个区域,进行垃圾回收后,不能被回收的对象被放入到空的survivor区域。
Survivor Space幸存者区,用于保留在eden space内存区域中通过垃圾回收后没有被回收的对象。Survivor有两个,别离为To Survivor、 From Survivor,这个两个区域的空间大小是一样的。执行垃圾回收的时候Eden区域不能被回收的对象被放入到空的survivor(也就是To Survivor,同时Eden区域的内存会在垃圾回收的过程中全副开释),另一个survivor(即From Survivor)里不能被回收的对象也会被放入这个survivor(即To Survivor),而后To Survivor 和 From Survivor的标记会调换,始终保障一个survivor是空的。
为啥须要两个survivor?因为须要一个残缺的空间来复制过去。当满的时候降职。每次都往标记为to的外面放,而后调换,这时from曾经被清空,能够当作to了。
3、标记-整顿算法
复制收集算法在对象成活率较高时就要进行较多的复制操作,效率将会变低。更要害的是,如果不想节约50%的空间,就须要有额定的空间进行调配担保,以应答被应用的内存中所有对象都100%存活的极其状况,所以,老年代个别不能间接选用这种算法。
依据老年代的特点,有人提出一种”标记-整顿“Mark-Compact算法,标记过程依然和标记-革除一样,但后续步骤不是间接对可回收对象进行清理,而是让所有存活的对象都向一端挪动,而后间接清理端边界以外的内存。
上面两张图讲了这个算法的过程:
4、分代收集算法
以后商业虚拟机的垃圾收集都采纳”分代收集“(Generational Collection)算法,这种算法依据对象存活周期的不同将内存划分为几块。个别把Java堆分为新生代和老年代,这样就能够依据各个年代的特点采纳最适当的收集算法。在新生代,每次垃圾收集时都发现少量对象死去,只有大量存活,那就选用复制算法,只须要付出大量存活对象的复制老本就能够实现收集。而老年代中因为对象存活率较高,没有额定的空间对它进行调配担保,就必须应用”标记-清理“和”标记-整顿“算法来进行回收。
这种算法就是咱们在后面JVM垃圾回收综述中讲述的内容。其本质是更为灵便的应用”标记-清理“和”标记-整顿“算法。
常见的GC回收器当初常见的垃圾收集器有如下几种
...