虽然垃圾收集器的技术在不断进步,但直到现在还没最好的收集器出现,更加不存在“万能”的收集器,所以我们选择的只是对具体应用最合适的收集器。
图 HotSpot中的垃圾收集器,连线表示可搭配使用
是最基础、历史最悠久的收集器。是一个单线程工作的收集器,在进行垃圾收集时,必须暂停其他所有线程,而且只会使用一个处理器或一条收集线程去完成垃圾收集工作。
迄今为止,仍是HotSpot虚拟机运行在客户端模式下的默认新生代收集器。
简单而高效(与其他收集器的单线程相比)。
对于内存资源受限的环境,它是所有收集器里额外内存消耗最小的;
对于单核处理器或处理器核心数比较少的环境来说,其可以获得最高的单线程收集效率。
用户桌面以及部份微服务应用中。对于运行在客户端模式下的虚拟机来说是一个很好的选择。
是Serial收集器的老年代版本,单线程收集器,使用标记-整理算法。
实质上是Serial收集器的多线程并行版本(工作时需暂停所有用户线程)。除了同时使用多条线程进行垃圾收集之外,其余行为等与Serial一致,在实现上两种收集器也共用了相当多的代码。
是除Serial收集器外,目前唯一能CMS收集器配合工作的收集器。
同样基于标记-复制算法实现的多线程收集器。
特点是它的关注点与其他收集器不同。其他收集器关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。
高吞吐量可以最高效率地利用处理器资源,尽快完成程序的运算任务。主要适合在后台运算而不需要太多交互的分析任务。
-XX:MaxGCPauseMillis:最大垃圾收集器停顿时间。收集器将尽力保证内存回收花费的时间不超过用户设定值。垃圾收集停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的(停顿时间缩短,会直接导致垃圾收集发生更频繁)。
-XX:GCTimeRatio:吞吐量大小(0,100)。如果设置为N,则用户代码执行时间 : 总执行时间 = N : N+1。
是Parallel Scavenge的老年代版本,支持多线程并发收集,基于标记-整理算法实现。
是一种以获取最短回收停顿时间为目标的收集器。基于标记-清除算法实现。
分为四个步骤:1)初始标记;2)并发标记;3)重新标记;4)并发清除。
初始标记和重新标记仍然需要“Stop The Word(STW)”初始标记是单线程,其他步骤是多线程。
初始标记 |
仅仅只是标记一下GC Roots能直接关联的对象,速度很快。 |
并发标记 |
开始遍历整个对象图,耗时长,但不需要停顿用户线程 |
重新标记 |
为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部份对象的标记记录。 |
并发清除 |
清理删除掉标记阶段判断的已经死亡的对象 |
表 CMS运作各个过程的内容
1)对处理器资源非常敏感
事实上面向并发设计的程序都对处理器资源比较敏感。CMS默认启动的回收线程数是(处理器核心数量+3)/ 4。
2)无法处理浮动垃圾
在CMS并发标记和并发清理阶段,用户线程还在继续运行,会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉。这一部份垃圾就称为浮动垃圾。
3)空间碎片
“标记-清除”算法引起的。
是垃圾收集器技术发展历史上的里程碑式的成功,开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。
主要面向服务端应用。设计者们希望做出一款能建立起“停顿时间模型”的收集器。
把连续的java堆划分为多个大小相等的独立区域(Region),每个Region都可以根据需要,每个Region都可以根据需要扮演新生代的Eden空间、Survivor空间或者老年代空间。
收集器能够对扮演不同角色的Region采取不同的策略去处理,无论是新建对象还是已存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。
图 堆空间的Region布局
Region的大小为2的N次幂。
超过Region容量一半的对象即可判定为大对象。对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中。
G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待。
能支持指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间大概率不超过N毫秒的目标。
G1可以面向堆内存任何部份来组成回收集(Collection Set,一般简称CSet)进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大。这就是G1的Mixed Gc模式。
图 G1垃圾收集过程
Region同样也是使用记忆集避免全堆扫描。但是其的记忆集要复杂很多,每个Region都有维护自己的记忆集。这些记忆集会记录下别的Region指向自己的指针,并标记这些指针分别在哪些卡页的范围内。
图G1的记忆集
G1记忆集在存储结构本质是一种哈希表。key是别的Region的起始地址,value存储了卡表的索引号。
每个Region又被分成了若干个大小为512字节的Card,Card中的每个元素对应其标识的内存区域中一块特殊大小的内存块(卡页),加入G1默认的Region大小是2M,则Region对应的Card的每个元素的卡页大小为2M/512=4k。
通过原始快照(SATB)算法来实现。
以衰减均值为理论基础来实现。
G1收集器会记录每个Region的回收耗时、每个Region记忆集里的脏卡数量等各个可测量的步骤花费的成本,并分析得出平均值、标准偏差、置信度等统计信息。
“衰减平均值”是指它会比普通平均值更容易受到新数据等影响。
初始标记 |
仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Region的指针)的值,让下阶段用户线程并发运行时,能正确地在可用的Region中分配新的对象。 |
并发标记 |
从GC Roots开始对堆中堆对象进行可达性分析,此过程与用户线程并发执行,耗时较长。 |
最终标记 |
对用户线程做一个短暂的暂停。用于处理并发阶段结束后仍遗留下来的SATB记录。 |
筛选回收 |
负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划。基于复制-清理的算法。 需要暂停用户线程。多条收集器线程并行完成。 |
表 G1收集器的运作过程
从G1开始,最先进的垃圾收集器的设计向导都不约而同地变为追求能够应付应用的内存分配速率,而不追求一次把整个Java堆全部清理干净。