JVM的GC算法
GC分代假说
我们都知道,绝大多数对象都是朝生夕死。对于绝大多数GC算法而言都会遵循基本的分代假设。
它的结构如下所示,即
分代假说
Young
此区域使用的GC算法为Mark and Copy
,即没被回收的对象会被标记出来然后拷贝到幸存区。
- Eden(TLAB 线程私有的分配缓存区,加快分配速度)
- Survivor1
-
Survivor2
年轻代
在年轻代,会触发Minor GC,年轻代的垃圾回收能回收掉70%--90%的内存。而幸存的对象会进入Survivor区中,并且每次Minor GC触发,不会被回收的对象会在Survivor区中交替复制,直到次数大于15次,这些对象会进入老年代。
Tenured(Old)
在老年代,使用的GC算法是Mark-Sweep-Compact
,即没被回收的对象,会被标记出来,然后为了减少内存碎片,尽量的压缩成一块区域。
在老年代调用的使用的是Major GC区回收
PermGen/Metaspace
GC收集器
GC Mark
- Stop The World (STW)
在GC开始之前,会先进行标记。标记的时候,要先把进程停止掉。所有的GC在工作的过程中,都要涉及到或多或少的STW
,这是程序卡顿的原因。也是我们经常调优的目标。 - safe point
当GC触发之前,先要运行到一个安全点,才能进行STW
,通常而言,GC的停顿与堆大小无关,而是与活对象的大小有关。增加堆大小有时候反而会增加延迟。
我们下面介绍的垃圾回收器有三种Serial
,Parallel
,CMS
,G1
Serial
serial回收算法如上图,分别应用在年轻代和老年代。基本和分代假说的说法一样,一根线程,从年轻代的标记复制,到STW,到老年代的标记清除整理。
Parallel
串行回收的并发版本,所作的事情是一样的。
我们可以从上图中看到的是,Parallel有三个回收器,即ParNew
,Parallel Old
,Parallel Scavenge
,其中Parallel Old
和Parallel Scavenge
是搭配使用的。至于ParNew
是因为CMS回收器只能回收老年代,它被开发出来用于和CMS搭配使用,回收年轻代。
CMS
GC回收算法对比我们可以看到左边是一个串行收集器,右边是CMS收集器,这两个对比起来,CMS的STW
过程Initial Mark是一个比较短暂的过程,然后标记过程会和用户线程一起去执行,但是可能执行标记的不准确, 所以在之后的一段时间,又STW
一会,并发去Remark标记。之后在清除的过程中,和用户线程并发的执行。
并且在CMS中,老年代只进行Mark-Sweep,而不进行Compact,主要用于低延时的系统。
G1
G1在Java9之后成为了默认的GC。如果想在Java8中使用它,可以加个参数Use G1 GC。
它横跨了年轻代和老年代,它制定的目标是软实时,比较大的堆内存(大于4G),可以设定目标(设定停顿时间,尽量达到)。
在G1的设计中,没有分代假说的存在了。它会将堆分成若干个等大的区域。
G1
再回收的时候无需回收整个堆,而是回收一个Collection Set,即一个区域的集合。
在GC1中,有两种GC:
- Fully young GC
- Mixed GC
估计每个Region中的垃圾比例,优先回收垃圾多的Region。