1. 垃圾收集器
下图是作用于不同分代的收集器。如果他们之间有连线,那么说明他们可以搭配使用。
1.1 Serial收集器
单线程
Stop the world--停掉用户所有正常工作的线程。
新生代采用复制算法,老年代采用标记-整理算法。
简单而高效,它依然是虚拟机运行在Client模式下的默认新生代收集器。比如说桌面应用场景,新生代不会太大,停顿时间完全可以控制在几十毫秒最多一百毫秒以内,只要不是频繁发生,是可以接受的。
运行过程:
1.2 ParNew收集器
Serial的多线程版本
运行在Server模式下的虚拟机在新生代的首选收集器。因为只有它和Serial收集器可以和CMS收集器一起使用。
-XX:+UserConcMarkSweepGC使用后的默认新生代收集器;也可以使用-XX:+UseParNewGC的选项强制指定。
存在回收线程之间交互的开销,所以单CPU环境下,没有Serial收集器好。
运行过程:
PS: 在讨论垃圾收集器的时候,会出现并行和并发两种概念,分别如下:
1. 并行:指的是多条垃圾手机线程并行工作,但此时用户工作线程处于等待状态。
2. 并发:指的是垃圾收集线程和用户线程同时执行,分别在不同的CPU上执行。
1.3 Parallel Scavenge收集器
新生代收集器
使用的是复制算法
多线程
“吞吐量优先”收集器,目标是达到一个可控制的吞吐量(运行用户代码时间/(运行用户代码事件+垃圾收集时间))。
-XX:MaxGCPauseMillis 最大垃圾收集停顿时间;-XX:GCTimeRatio吞吐量大小(0-100)。MaxGCPauseMillis 的值设置太小,可能会增加GC的频率;
-XX:+UseAdaptiveSizePolicy 当设置了这个参数之后,就不需要手工指定新生代大小(-Xmn),Eden和Surivivor的比例(-XX:SurivivorRatio),晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集新能监控信息,动态的调节这些参数以提供最合适的停段时间或最大的吞吐量,这种调节方式叫做:GC自适应的调节策略。
运行过程:
PS: 停顿时间越短就越适合与用户交互的程序,良好的交互能够提升用户体验,而高吞吐量则可以更好的利用CPU时间,尽快的完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
1.4 Serial Old收集器
Serial收集器的老年代版本
单线程
标记-整理算法
和Serial收集器一样,主要用于以Client模式运行的虚拟机上。
如果实在Server模式下,有两大用途:
JDK1.5及之前,搭配Parallel Scavenge收集器使用。(因为Parallel old在1.6时候才有)
作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。
运行过程:
1.5 Parallel Old收集器
Parallel Scavenge收集器的老年代版本(1.6版本之后才有)
多线程
标记-整理算法
在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑使用Parallel Scavenge加Parallel Old收集器。
运行过程:
1.6 CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
标记-清除算法
并发低停顿收集器
整个过程分为四步:
初始标记:标记GC Roots能直接关联到的对象,速度很快
并发标记:进行GC Root Tracing的过程
重新标记:修正并发标记期间因用户程序执行而导致标记产生变动的那一部分对象 的标记记录。
并发清理:回收对象
初始标记非常快,重新标记时间稍长,但远比并发标记的时间短。整个过程中耗时最长的并发标记和并发清理都是可以和用户线程一起工作的。所以停顿时间很短。
运行过程:
缺点:
CMS收集器对CPU资源非常敏感,它牺牲了部分的运算能力来降低停顿时间来保证系统的响应时间。
CMS无法处理浮动垃圾,可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。由于并发清理阶段用户程序还在运行,伴随着程序的运行自然会产生新的垃圾,这些垃圾出现在标记之后,CMS无法再当次收集中处理掉它们,只好等待下一次GC时再清理掉。这部分垃圾称为:浮动垃圾。也是由于在垃圾收集阶段用户线程还在执行,那也就需要预留足够的内存空间给用户线程使用,因此CMS不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时程序运行使用。在1.5的默认设置下,CMS收集器会在老年代使用了68%的空间时被激活,这是一个偏保守的设置,如果在应用中老年代增长的不是太快,可以适当的调高蚕食-XX:CMSInitiatingOccupancyFraction的值来提高触发的百分比,以降低内存回收次数从而获取更好的性能。在JDK1.6中,CMS收集器的启动阈值已经提升到92%,要是CMS运行区间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机启动后备预案:临时启用Serial Old收集器重新进行老年代的垃圾收集,这样停顿时间就会很长了。(这时候我咋想到了读写分离呢,好像有点这个意思)
标记-清除算法的缺点,产生大量的空间碎片。空间碎片过多时,将会给大对象分配带来麻烦,旺旺会出现老年代还有很多空间,但是无法找到足够大的连续空间来分配给当前对象,不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数(默认开启),用于在CMS收集器顶不住要进行Full GC时,开启内存碎片的整理过程,但停顿时间不得不变长。
1.7 G1收集器
G1是一款面向服务端应用的垃圾收集器。它的使命就是替换在JDK1.5中发布的CMS收集器。
优点:
并行和并发:G1能够充分的利用多CPU,多核环境下的硬件优势,使用多个CPU来减少Stop-The-World停顿的时间,部分其他收集器原来需要停顿Java线程执行GC操作,G1能够通过并发的方式让Java程序继续执行。
分代收集:G1可以不需要其他收集器的配合完成整个GC堆得独立管理,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间,熬过多次GC的就对象以获得更好的收集效果。
空间整合:与CMS的标记-清理算法不同,G1从整体上来看是基于标记-整理算法来实现的收集器,从局部(两个Region之间)上来看是基于复制算法实现的,但无论如何,这两种算法都意味着G1运行期间不会产生内存空间碎片。这种特性有利于程序长时间的运行。
可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是CMS和G1共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java的垃圾收集器的特征。
使用G1收集器时,Java堆得内存布局就与其他收集器有很大的差别,它将整个Java堆分为多个大小相等的独立区域(Region),虽然保存着新生代和老生代的概念,但新生代和老生代不再是物理隔离了,他们都是一部分Region的集合。
G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划的避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所需空间的大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值更大的Region。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器能够在有限的时间内获得尽可能高的收集效率。
G1收集器中,Region之间的对象引用以及其他收集器中新生代与老生代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region中,如果是,便通过CardTable把相关的引用信息记录到被引用对象所属Region的Remembered Set之中。当进行垃圾回收时,在GC 根节点的枚举范围中加入加入Remembered Set即可保证不对全堆扫描也不会有遗漏。
G1收集器步骤:
初始标记
并发标记
最终标记
筛选回收
初始标记:标记GC Roots能够直接关联到的对象,并修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建对象,这阶段需要停顿线程,耗时很短。
并发标记:从GC Roots出发对堆中对象进行可达性分析,找出存货对象,耗时较长,但可以与用户程序并发执。
最终标记:为了修正在并发标记中因用户程序继续执行而导致的标记产生变化的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的Remembered Set Logs中,最终标记阶段需要将Remembered Set Logs的数据合并到Remembered Set中,这个阶段需要停顿线程,但是可并行执行。
筛选回收:首先对各个Region的回收价值和成本进行排序,根据用户期待的GC停顿时间来执行回收计划,这一阶段也是可以喝用户程序并发执行的,但是因为只回收一部分Region,时间用户可以控制,而且停顿用户线程将大幅提高收集效率。
执行过程: