垃圾回收问题
垃圾回收算法
通过之前的学习我们可以将死亡对象标记出来了,标记出来后我们就可以进行垃圾回收操作了,在正式学习垃圾处理器之前,我们先来看一下垃圾回收器使用的几种算法.
标记-清除算法
"标记-清除"算法是基础的收集算法.算法分为"标记"和"清除"两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象.后续的收集算法都是基于这种思路加以改造并对其不足加以改进而已:
"标记-清除"算法的不足主要有两个:
1.效率问题:标记和清除这两个过程的效率都不高.
2.空间问题:标记清除后会产生大量不连续的内存碎片,内存碎片太多可能会导致以后在程序运行中需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾收集.
复制算法
"复制算法是为了解决"标记-清理"的效率问题.它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块.当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉.这样做的好处是每次都对整个半区进行内存回收,内存分配时也就不需要考虑内存碎片等复杂的情况,只需要移动堆顶指针,按顺序分配即可.此算法实现简单,运行高效.算法的执行流程如下图:
通过复制算法这种机制确实能在一定程度上规避内存问题,但也有一定缺陷.
1.总的可用内存,变少了.
2.如果每次要复制的对象比较多,此时复制的开销比较大,需要的是当前一轮(当中,大部分对象被释放,少数对象存活,在这个时候最适合复制).
现在的商用虚拟机(包括HotSpot都是采用这种收集算法来回收新生代)
引入概念:对象的年龄.JVM有专门的线程负责扫描/释放一个对象,如果被线程扫描的时候,可达了(不是垃圾), 年龄+1 =>在对象类中存储年龄.
新生代中98%的对象都是"朝生暮死"的,所以不需要按照1:1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的Eden(伊甸园)空间和两块较小的Survivor(幸存者空间),每次使用Eden和其中一块Survivor(两个Survivor区域一个称为from区,另一个称为To区).当回收时,将Eden和Survivor中还存活的对象一次性复制到另一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor的空间.
当Survivor空间不够用的时候,需要依赖其它内存(老年代)机型分配担保.
HotSpot默认Eden与Survivor的大小比例是8:1,也就是说:Eden : Survivor From : Survivor To = 8:1:1.所以每次新生代的可用空间为整个新生代容量的90%,而剩下的10%用来存放回收后存活的对象.
下面来看一下具体的算法执行流程:
(1)当代码中new出一个对象,这个对象就是被创建在伊甸区的,伊甸区能有很多对象.一个经验规律:伊甸区的对象都是"朝生暮死"的,生命周期非常短.
(2)第一轮GC扫描完成之后,少数伊甸区中幸存的对象就会通过算法拷贝到生存区,后续GC扫描线程还会持续进行扫描,不仅会扫描伊甸区,也会扫描生存区的对象,生存区中也有大量对象被标记为垃圾,少数存活的,就会使用算法,拷贝到另一个生存区中.只要这个能够在生存区中继续存活,就会被复制算法,拷贝到另一个生存区中,每经过一次GC扫描,对象年龄+1
(3) 如果该对象存储在生存区中,经历了若干轮GC依然健在,JVM就会认为,这个对象生命周期大概率会很长,就把这个对象从生存区拷贝到老年代.
(4)老年代对象,也会被GC扫描,但是扫描的频次大大降低.
(5)当对象在老年代中寿终正寝的时候,JVM按标记整理的方式,进行清除.
标记-整理算法
复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低.因此老年代一般不能使用复制算法.
针对老年代的特点,提出了一种称之为"标记-整理算法".标记过程仍与"标记-清除"的过程一致,但后续的步骤不是直接对可回收对象进行整理,而是让所有存活对象都向一端移动,然后清理掉边界以外的内存.流程图如下:
分代算法
分代算法和上面讲的三种算法不同,分代算法是通过区域的划分,实现不同区域和不同的垃圾回收策略,从而实现更好的垃圾回收.就好比中国的一国两制方针一样,对于不同的情况和地域设置更符合当地的规则,从而实现更好的管理,这就是分代算法的设计思想.
当前JVM垃圾收集器采用的是"分代收集"算法,这个算法并没有新思想,只是根据对象的存活周期的不同将内存划分为几块.一般是把Java堆分为新生代和老年代.在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高,没有额外空间对它进行分配担保,就必须采用"标记-清理"或者"标记-整理"算法.
哪些对象会进入新生代?哪些对象会进入老年代?
新生代:一般创建的对象都会进入新生代;
老年代:大对象经历了N次(默认情况下是15次)垃圾回收依然存活下来的对象会从新生代移动到老年代.
面试问题:请问了解Minor GC和Full GC么,这两种GC有什么不一样吗?
1.Minor GC又称为新生代GC:指的是发生在新生代的垃圾收集.因为Java对象大多都具有朝生暮死的特性,因此Minor GC(采用复制算法)的使用非常频繁,一般回收速度也比较快.
2.Full GC又称为老年代GC或者Major GC:指发生在老年代的垃圾收集.出现了Major GC,经常会伴随至少一次的Minor GC(并非绝对,在Parallel Scavenge收集器中就有直接进行Full GC的策略选择过程).Major GC的速度一般会比Minor GC慢10倍以上.