我们知道,在JVM中的堆是分代的,分为新生代、年老代、永久代(新虚拟机应该叫做metaspace之类的)。
在新生代中垃圾回收叫MinorGC,现在主流商业虚拟机在新生代中采用的垃圾回收算法都是复制算法。
复制算法
- 什么是复制算法呢?复制算法为了解决什么问题呢?。
其实在复制算法之前还有个垃圾回收算法叫标记清除算法。标记清除算法简单来说就是,根据可达性分析法来判断一个应用是否或者,如果判断出不可达,那么就会对其标记,之后对标记的引用进行回收。这种算法很简单,但是有个很明显的缺点,就是内存碎片化,变得不连续。
那么复制算法就是为了解决内存碎片化的这个问题。基本思想是,可以把内存等分成两部分A,B,对象首先在A块内存上分配,当A块内存满时,无法为新对象继续分配内存时,也就会触发GC,此时GC会将A块内存中存活的对象移动到B块内存,然后将A块内存清除,这样就解决了内存碎片化问题。
新生代分区
通过上面介绍的复制算法,我们可以看到解决了内存碎片化的问题,但是新的问题出现了,好像有一半的内存浪费了,因为只能在其中一半的内存上产生对象,内存利用率不高。为了解决这个问题,复制算法是对新生代内存这样划分的,分成Eden区,Survivor区。其中Survivor区有两块,分别叫From Survior和To Survior。
- 为什么要这样划分呢?
因为新生代中大部分的对象都是存活时间很多的,也就是每次GC都会回收掉大部分,只有少数的对象能存活下来,也就是没有必要等分新生代。Eden区会占有大部分比例,Survivor区占有较小的比例。虚拟机默认的比例是8:1.这个可以通过虚拟机参数来调节-XX:SurvivorRatio=8。新键的对象现在Eden区中分配内存,当内存不够时,会触发GC,这是会将Eden中存活的对象移动到To Survivor区中,并将对象年龄加一,之后将Eden区清空,这样就回收了Eden区。
这之后还有个步骤,From Survivor和To Survivor两块区域会调换,也就是原先的To Survivor会变成下次GC的From Survivor区,原先的From Survivor区会变成下次GC的To Survivor区。下次GC时会将Eden中和From Survivor中存活的对象移动到To Survivor中,同时增加对象的年龄,如果达到对象进入老年代的年龄阈值,就会将对象移动到老年代,如果没有就还在Survivor区中,同时回收Eden和From Survivor区的内存。
- 为什么Survivor分成两块
个人理解,分成两块还是为了避免Survivor区域中内存碎片化问题。因为Survior区在GC的时候也是需要回收不可达对象的,如果只有一块Survivor,依然会有碎片化问题。
- 思考,如果Eden区中存活的对象很多,Survivor区不能容纳那么多的对象怎么办?
虚拟机还有个分配担保机制,什么意思呢,简单理解,就是如果Survivor区容量不够了,那么会把Eden区中存活的对象移动到老年代,也即是老年代会提供个担保。分配担保也是有一些判断逻辑的,且听后续分解!