垃圾收集器
一、常见的垃圾收集器?
常见的垃圾收集器如下:
- 新生代收集器:Serial、 ParNew、Parallel Scavenge
- 老年代收集器:Serial Old、Parallel Old、CMS
- 老年代和新生代收集器:Garbage First (G1)
二、Serial 垃圾收集器(标记复制算法):
Serial是一种应用于新生代的单线程的垃圾回收器,采用标记复制算法。这个收集器是一个单线程的收集器,在工作的时候只会使用一个处理器或一条收集线程去处理工作,重要的是Serial在进行垃圾收集的时候,必须暂停其他所有的线程,包括用户线程。
开启Serial收集器参数:
-XX:+UserSerialGC #选择Serial作为新生代垃圾收集器
Serial的优点:简单高效,在所有收集器中额外内存消耗是最小的,由于是单线程减少了线程阻塞和线程上下文切换。
Serial的缺点:在用户无感知的情况下会暂停用户线程,降低用户体验感。
Serial的应用场景:Client模式(桌面应用)、单核cpu服务器。
三、ParNew垃圾收集器(标记复制算法):
ParNew收集器实质上是Serial收集器的多线程并行版本,他在单核cpu上的表现并不会比Serial好,在多核机器上,其默认开启的收集线程数与cpu数量相等,可以通过下面命令进行修改:
-XX:ParallelGCThreads #设置JVM垃圾收集的线程数
ParNew的优点:随着cpu核心数的增多,可以有效的利用cpu资源。
ParNew的缺点:和Serial一样,因为是采用标记复制回收算法,所以垃圾收集过程中会暂停用户线程。
ParNew的应用场景:ParNew是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为CMS只能和Sarial或者ParNew来配合使用,当在多核系统环境下,首选的就是ParNew。ParNew也是CMS垃圾收集器的默认新生代收集器。
四、Parallel Scavenge垃圾收集器(标记复制算法):
Parallel Scavenge也是一种应用于新生代的多线程垃圾收集器,采用标记复制算法,他于ParNew的不同之处就是Parallel Scavenge收集器的目的是达到一个可控制的吞吐量,而ParNew关注的点在于尽可能的缩短垃圾收集时用户的停顿时间。
吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
Parallel Scavenge的优点:有效的利用cpu,吞吐量优先,并且能精确控制。
Parallel Scavenge的缺点:和ParNew一样,因为是采用标记复制回收算法,所以垃圾收集过程中会暂停用户线程。
Parallel Scavenge的应用场景:适用于后台运算而不需要太多交互的分析任务。
Parallel Scavenge提供了两个参数用于精确控制吞吐量:
控制最大垃圾收集停顿时间:
-XX:MaxGCPauseMilis
直接设置吞吐量大小:
-XX:GCTimeRadio
五、Serial Old垃圾收集器(标记复制算法):
Serial Old垃圾收集器是Serial的老年代版本,同样也是一个采用标记复制算法的单线程垃圾收集器。
Serial Old的用途:jdk5以及以前的版本中与Parallel Scavenge收集器来搭配使用,另外一种就是作为CMS收集器失败时的候选备案。
六、Parallel Old垃圾收集器(标记整理算法):
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并行收集,他采用的是标记整理回收算法。
七、CMS垃圾收集器(标记清除算法):
CMS收集器是一种以获得最短回收停顿时间为目的的收集器,支持多线程并行收集,并且采用标记清除算法。
CMS的执行步骤大致可以分为四步:
- 初始标记:需要暂停用户线程,初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。
- 并发标记:不需要暂停用户线程,并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长,但是不需要停顿用户线程,可以与垃圾收集器一起并行运行。
- 重新标记:需要暂停用户线程,重新标记则是为了修正 并发标记 阶段,因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。
- 并发清除:并发清除阶段会清理删除掉标记阶段的已经死亡的对象,由于不需要移动存活的对象,所以这个阶段也是可以和用户线程同时并发的。
CMS收集器的优点:支持并发收集,低停顿,几乎不会暂停用户线程。
CMS收集器的缺点:
- CMS收集器对处理器的资源非常敏感,在并发阶段,他虽然不会导致用户停顿,但是他却因占用了一部分线程(或者处理器的计算能力)而导致应用程度变慢,降低吞吐量。CMS默认启动的回收线程数是(处理器核心数 + 3)/ 4,也就是说如果处理器核心数在4个以上,并发收集线程只占用不少于百分之25的处理器运算资源,并且会随着cpu核心数的增多而下降。
- 在CMS并发标记和并发清理阶段,用户线程还是会继续运行的,程序自然就会伴随着有新的垃圾对象不断产出,但这一部分对象是出现在标记过程结束之后,CMS无法在当次收集中处理掉他们,只能留在下一次手机时才清理,这部分垃圾成为浮动垃圾。同样也是因为在垃圾收集阶段用户线程还会持续运行,那就还需要预留足够的内存空间提供给用户线程使用,因此CMS不能像其他垃圾收集器那样等到老年代几乎填满了在进行回收。如果CMS预留的空间无法满足程序分配的新对象,就会出现一次并发失败:这个时候需用冻结用户所有线程,临时采用Serial Old收集器来重新进行老年代的垃圾收集。
- CMS是一种基于标记清除算法的回收器,在收集结束的时候会产生大量的空间碎片,空间碎片太多的时候将会给大对象的分配带来麻烦,这就会出现老年代还有很多剩余空间,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。
如果解决CMS收集器提前Full GC问题:
CMS提供了两个参数
-XX:UserCMSCompactAtFullCollection #开启碎片整理(默认是开的) -XX:CMSFullGCsBeforeCompaction #执行多少次不压缩的Full GC之后,跟着来一次压缩的Full GC
八、G1收集器(标记整理算法):
G1收集器是一款面向服务端应用的垃圾收集器,目前是JDK9的默认垃圾收集器。与其他收集器相比,G1具有如下特点:
- 并行与并发:G1能够充分利用多cpu,多核环境下的硬件优势。
- 分代收集:能够采用不同的方式去处理新创建的对象和已经存活了一段时间的对象,不需要与其他收集器进行合作。
- 空间整合:G1整体上是采用标记整理来实现的垃圾收集器,从局部上看是基于复制算法实现的,因此运行期间不会产生空间碎片。
- 可预测的停顿模型:G1能建立可预测的时间停顿模型,能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不超过N毫秒。
G1收集器将java堆划分为多个大小相等的独立区域(Region)
每一个方块就是一个区域,每个区域可能是 Eden、Survivor、老年代,每种区域的数量也不一定。JVM 启动时会自动设置每个区域的大小(1M ~ 32M,必须是 2 的次幂),最多可以设置 2048 个区域(即支持的最大堆内存为 32M*2048 = 64G),假如设置 -Xmx8g -Xms8g,则每个区域大小为 8g/2048=4M。
G1收集器可以有计划地避免在整个Java堆全区域的垃圾收集。G1可以跟踪各个Region里面垃圾堆积的价值大小(回收所获得的空间大小及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,收集加载最大的region,这种方式保证了有限时间内可以获取尽可能多高的收集效率。
为了在 GC Roots Tracing 的时候避免扫描全堆,在每个 Region 中,都有一个 Remembered Set 来实时记录该区域内的引用类型数据与其他区域数据的引用关系(在前面的几款分代收集中,新生代、老年代中也有一个 Remembered Set 来实时记录与其他区域的引用关系),在标记时直接参考这些引用关系就可以知道这些对象是否应该被清除,而不用扫描全堆的数据。
G1收集器的优点:
- 可指定最大停顿时间,该值要合理的进行设置,如果设置的太小,会使得每次筛选出的回收集只占堆内存的很小一部分,收集速度跟不上分配速度,导致垃圾堆积,最终产生Full GC,通常该为100~300ms较为合理
- 按受益动态确定回收集
- 低内存碎片,从整体上看,G1采用“标记-整理”的垃圾回收算法,从局部上看又是基于“标记-复制”算法实现,这两种算法使得G1在GC后不会产生内存碎片,有利于程序的长时间运行
G1收集器的缺点:
- 内存占用高,由于堆内存被划分为许多个小的Region分区数量,面对跨Region对象引用问题,每个Region分区都需要独立维护一份记忆集,使得用于维持G1正常运行的额外内存空间占到了总堆内存空间的10%~20%。
- 执行负载高,CMS用写后屏障来更新维护卡表,G1除使用写后屏障来更新维护卡表外,为了实现原始快照搜索算法,还需要使用写前屏障来跟踪并发时的指针变化情况。