大家好,我是方木
前言
之前方木就带大家聊聊 JVM 垃圾回收机制(GC) 中的 JVM那些事之垃圾回收算法,今天继续带着大家来聊聊 JVM 垃圾回收机制(GC) 中的垃圾回收器,垃圾回收算法 可以简单的理解为 理论知识,垃圾回收器 则可以认为是 具体实现。由于Java虚拟机规范并没有对垃圾收集器如何实现进行明确的规定,所以不同的JVM厂商以及不同虚拟机版本所提供的垃圾收集器都可能会有较大的差别,并且一般也都会提供对应的参数,供用户根据自己的应用特点组合出各个内存区块,主要是新生代、老年代所使用的收集器。
接下来方木带着大家一起看看都有哪些垃圾收集器以及它们的特点。在详细介绍具体垃圾收集器之前,我们先看下这些收集器适用的范围以及组合配对关系。
垃圾回收器适用的范围以及组合配对关系
1999 年随 JDK1.3.1 一起来的是串行方式的 Serial GC,它是第一款垃圾回收器;此后,JDK1.4 和 J2SE1.3 相继发布。2002 年 2 月 26 日,J2SE1.4 发布;Parallel GC 和 Concurrent Mark Sweep (CMS) GC 跟随 JDK1.4.2 一起发布,并且 Parallel GC 在 JDK6 之后成为 HotSpot 默认 GC。这三个垃圾回收器也是各有千秋。Serial GC 适合最小化地使用内存和并行开销的场景;Parallel GC 适合最大化应用程序吞吐量的场景;CMS GC 适合最小化中断或停顿时间的场景。
不过随着应用程序所应对的业务越来越庞大、复杂,用户越来越多,没有合适的回收器就不能保证应用程序正常进行,而经常造成 STW 停顿的回收器又跟不上实际的需求,所以才会不断地尝试对搜集器进行优化。Garbage First(G1)GC 正是面向这种业务需求所生,它是一个并行回收器,把堆内存分割为很多不相关的区间(Region);每个区间可以属于老年代或者年轻代,并且每个年龄代区间可以是物理上不连续的。
垃圾回收器概要
当标记阶段完成后,GC 开始进入下一阶段,删除不可达对象,关于标记阶段有几个关键点是值得注意的:
Serial GC
串行回收器主要有两个特点:第一,它仅仅使用单线程进行垃圾回收;第二,它是独占式的垃圾回收。在串行回收器进行垃圾回收时,Java 应用程序中的线程都需要暂停,等待垃圾回收的完成,这样给用户体验造成较差效果。虽然如此,串行回收器却是一个成熟、经过长时间生产环境考验的极为高效的 回收器。新生代串行处理器使用复制算法,实现相对简单,逻辑处理特别高效,且没有线程切换的开销。在诸如单 CPU 处理器或者较小的应用内存等硬件平台不是特别优越的场合,它的性能表现可以超过并行回收器和并发回收器。在 HotSpot 虚拟机中,使用 -XX:+UseSerialGC 参数可以指定使用新生代串行回收器和老年代串行回收器。当 JVM 在 Client 模式下运行时,它是默认的垃圾回收器。老年代串行回收器使用的是标记-压缩算法。和新生代串行回收器一样,它也是一个串行的、独占式的垃圾回收器。由于老年代垃圾回收通常会使用比新生代垃圾回 收更长的时间,因此,在堆空间较大的应用程序中,一旦老年代串行回收器启动,应用程序很可能会因此停顿几秒甚至更长时间。虽然如此,老年代串行回收器可以 和多种新生代回收器配合使用,同时它也可以作为 CMS 回收器的备用回收器。若要启用老年代串行回收器,可以尝试使用以下参数:-XX:+UseSerialGC: 新生代、老年代都使用串行回收器。
Serial GC 工作步骤
ParNew GC
并行回收器是工作在新生代的垃圾回收器,它只简单地将串行回收器多线程化。它的回收策略、算法以及参数和串行回收器一样。
并行回收器 也是独占式的回收器,在收集过程中,应用程序会全部暂停。但由于并行回收器使用多线程进行垃圾回收,因此,在并发能力比较强的 CPU 上,它产生的停顿时间要短于串行回收器,而在单 CPU 或者并发能力较弱的系统中,并行回收器的效果不会比串行回收器好,由于多线程的压力,它的实际表现很可能比串行回收器差。开启并行回收器可以使用参数 -XX:+UseParNewGC,该参数设置新生代使用并行回收器,老年代使用串行回收器。老年代的并行回收回收器也是一种多线程并发的回收器。和新生代并行回收回收器一样,它也是一种关注吞吐量的回收器。老年代并行回收回收器使用标记-压缩算法,JDK1.6 之后开始启用。
Parallel GC
Parallel Scavenge 收集器的特点是它的关注点与其他收集器不同,CMS 等收集器的关注点尽可能地缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量(Throughput)。Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器是在 JDK 1.6 中才开始提供的。使用 -XX:+UseParallelOldGC 可以在新生代和老生代都使用并行回收回收器,这是一对非常关注吞吐量的垃圾回收器组合,在对吞吐量敏感的系统中,可以考虑使用。参数 -XX😛arallelGCThreads 也可以用于设置垃圾回收时的线程数量。
Parallel GC 工作步骤
CMS GC
CMS( Concurrent Mark-Sweep ) 是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器,适用于对停顿比较敏感,并且有相对较多存活时间较长的对象(老年代较大)的应用程序;不过 CMS 虽然减少了回收的停顿时间,但是降低了堆空间的利用率。CMS GC 采用了 Mark-Sweep 算法,因此经过CMS收集的堆会产生空间碎片;为了解决堆空间浪费问题,CMS 回收器不再采用简单的指针指向一块可用堆空间来为下次对象分配使用。而是把一些未分配的空间汇总成一个列表,当 JVM 分配对象空间的时候,会搜索这个列表找到足够大的空间来存放住这个对象。另一方面,由于 CMS 线程和应用程序线程并发执行,CMS GC 需要更多的 CPU 资源。同时,因为 CMS 标记阶段应用程序的线程还是在执行的,那么就会有堆空间继续分配的情况,为了保证在 CMS 回收完堆之前还有空间分配给正在运行的应用程序,必须预留一部分空间。也就是说,CMS 不会在老年代满的时候才开始收集。相反,它会尝试更早的开始收集,已避免上面提到的情况:在回收完成之前,堆没有足够空间分配!默认当老年代使用 68% 的时候,CMS就开始行动了。 –
XX:CMSInitiatingOccupancyFraction = n 来设置这个阀值。
CMS GC 工作步骤
G1 GC
G1 GC 是 JDK 1.7 中正式投入使用的用于取代 CMS 的压缩回收器,它虽然没有在物理上隔断新生代与老生代,但是仍然属于分代垃圾回收器;G1 GC 仍然会区分年轻代与老年代,年轻代依然分有 Eden 区与 Survivor 区。G1 GC 首先将堆分为大小相等的 Region,避免全区域的垃圾收集,然后追踪每个 Region 垃圾堆积的价值大小,在后台维护一个优先列表,根据允许的收集时间优先回收价值最大的Region;同时 G1 GC 采用Remembered Set 来存放 Region 之间的对象引用以及其他回收器中的新生代与老年代之间的对象引用,从而避免全堆扫描。
G1 GC 分区
随着 G1 GC 的出现,Java 垃圾回收器通过引入 Region 的概念,从传统的连续堆内存布局设计,逐步走向了物理上不连续但是逻辑上依旧连续的内存块;这样我们能够将某个 Region 动态地分配给 Eden、Survivor、老年代、大对象空间、空闲区间等任意一个。每个 Region 都有一个关联的 Remembered Set(简称 RS),RS 的数据结构是 Hash 表,里面的数据是 Card Table (堆中每 512byte 映射在 card table 1byte)。简单的说 RS 里面存在的是 Region 中存活对象的指针。当 Region 中数据发生变化时,首先反映到 Card Table 中的一个或多个 Card 上,RS 通过扫描内部的 Card Table 得知 Region 中内存使用情况和存活对象。在使用 Region 过程中,如果 Region 被填满了,分配内存的线程会重新选择一个新的 Region,空闲 Region 被组织到一个基于链表的数据结构(LinkedList)里面,这样可以快速找到新的 Region。
总结而言,G1 GC 的特性如下:
G1 GC 工作步骤
专注分享Java技术干货,还有我整理的上百份面试题库,持续更新中!期待你的关注!
声明:本站部分文章内容及图片转载于互联 、内容不代表本站观点,如有内容涉及侵权,请您立即联系本站处理,非常感谢!