垃圾收集器
评估GC的性能指标:吞吐量和暂停时间
垃圾回收是java的招牌能力,极大的提高了开发效率。经典的垃圾收集器有七种,
串行:serial 、serial old、
并行:parnew、parallel scavenge、parallel old
并发:CMS 、G1
垃圾收集器与分代的关系
新生代收集:serial GC、parallel scavenge GC、parnew GC
老年代收集:serial old GC、parallel old GC、CMS
整堆收集: G1
在JDK8中取消了红色线的组合,绿色的线在以后版本也会弃用。CMS垃圾收集器在JDK14之后删除了。
JDK8 中默认使用的是Parallel Scavenge GC+ Parallel Old GC 的组合。JDK9中默认使用G1.
查看默认的垃圾收集器
serial 垃圾收集器
- serial 是最基本的垃圾收集器,在JDK1.3之前是新生代唯一的垃圾收集器
- serial 在单cpu场景下性能高
- serial 采用复制算法、串行回收和STW机制进行内存回收
- 老年代还提供了一个serial Old垃圾收集器,serial old 采用标记-压缩算法、串行回收和STW机制进行内存回收
serial 是一个单线程的收集器,单线程不仅仅说明只会使用一个cpu或者一个线程去完成垃圾收集工作,更重要的是,它在垃圾回收的时候,必须暂停其他的所有线程,直到收集完成
在用户的桌面应用场景中,可以使用的内存一般不大,可以在较短时间内完成垃圾收集,只要不是频繁发生,使用serial 也是可以接受的。
在HotSpot虚拟机中,使用-XX:+UseSerialGC 参数可以指定给年轻代和老年代都使用串行垃圾收集器,即serial 和serial old。
优势:因为没有线程交互的开销,只专心做垃圾收集,可以获得最高的单线程收集效率,简单高效
总结
这种垃圾收集器只需要了解一下,现在已经不使用串行的收集器了,只有在单核cpu的场景下才可以使用,现在已经不是单核cpu了。
对于交互较强的应用而言,这种垃圾收集器是不能接受的,一般在JavaWeb应用程序中不会采用串行 的垃圾收集器
ParNew垃圾收集器
- ParNew是一个多线程的垃圾收集器 par 是parallel的缩写,new表示只能回收新生代的垃圾
- PaeNew除了采用并行回收的方式执行垃圾回收之外,与serial 没有任何区别。
- 对于新生代回收次数比较频繁,所以使用并行的方式进行回收会更高效。
- 对于老年代回收次数比较少,使用串行方式节省资源。
- 除了serial 之外,只有parnew能与CMS收集器配合工作。
多线程环境下,parnew收集器会比serial更高效,但是单线程环境下就不如serial
在HotSpot虚拟机中,使用-XX:+UseParNewGC -XX:+UseConcMarkSweepGC命令来指定新生代使用ParNew垃圾收集器,老年代使用CMS垃圾收集器。
如果不设置-XX:+UseConcMarkSweepGC命令,默认会使用 Serial old 垃圾收集器回收老年代内存。
使用-XX:ParallelGCThreads 限制线程数量,默认开启和cpu相同的线程,一般不去修改。
总结
作为serial的一个多线程版本,在新生代收集的时候采用并行的方式,STW时间稍微短一点,能够与ParNewGC组合的老年代收集器serial old 在JDK9之后版本将不再支持, CMS收集器也在JDK14之后删除。性能高的环境用不上他,性能低的环境不如serial 高效。
Parallel垃圾收集器
Parallel Scavenge收集器与ParNew一样,采用了复制算法并行回收和STW机制,也都是新生代的垃圾收集器,性能差别不是特别大。
与ParNew收集器不同,Parallel Scavenge收集器的目标是达到一个可控制的吞吐量,也被称为吞吐量优先的垃圾收集器
新生代选择Parallel Scavenge收集器老年代自动使用Parallel Old。
自适应调节
- 高吞吐量可以高效的利用cpu时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务,因此常见在服务器环境中使用。
- Parallel 收集器在JDK1.6的时候提供了用于老年代垃圾收集的Parallel Old来替代serial Old
- Parallel Old收集器采用了标记压缩算法,基于并行回收和STW机制。
- JDK8默认使用该垃圾收集器
参数设置
- -XX:+UseParallelGC 手动给年轻代使用parallel垃圾收集器
- -XX:+UseParallelOldGC手动给老年代使用ParallelOld垃圾收集器
- 上面两个参数只需要设置一个即可,默认开启一个,另一个也会开启,互相激活
- -XX:+ParallelGCThreads 设置年轻代垃圾收集器的线程
- 在默认情况下,当 CPU 数量小于 等于8 个,ParallelGCThreads 的值等于 CPU 数量。
- 当 CPU 数量大于 8 个,ParallelGCThreads 的值等于
3 + [5 * CPU_Count] / 8]
- -XX:MaxGCPauseMillis 设置垃圾收集器最大停顿时间(即 STW 的时间)。单位是毫秒
- 为了尽可能地把停顿时间控制在 MaxGCPauseMills 以内,收集器在工作时会调整 Java 堆大小或者其他一些参数。
- 对于用户来讲,停顿时间越短体验越好。但是在服务器端,我们注重高并发,整体的吞吐量。所以服务器端适合 Parallel,进行控制。
- 该参数使用需谨慎。
- -XX:GCTimeRatio垃圾收集时间占总时间的比例(= 1 /(N+1))。用于衡量吞吐量的大小。
- 取值范围(0,100)。默认值 99,也就是垃圾回收时间不超过 1%。
- 与前一个 -XX:MaxGCPauseMillis参数有一定矛盾性。暂停时间越长,Radio 参数就容易超过设定的比例。
- -XX:+UseAdaptiveSizePolicy设置 Parallel Scavenge 收集器具有自适应调节策略。
- 在这种模式下,年轻代的大小、Eden 和 Survivor 的比例、晋升老年代的对象年龄等参数会被自动调整,已达到在堆大小、吞吐量和停顿时间之间的平衡点。
- 在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量(GCTimeRatio)和停顿时间(MaxGCPauseMills),让虚拟机自己完成调优工作。
总结
适合吞吐量优先的场景,与Parallel Old 垃圾收集器是一个组合,两个相互激活,JDK8默认的垃圾收集器。可以修改多个参数,设置年轻代并行收集器的线程数,设置垃圾收集器最大停顿时间,设置垃圾收集时间占总时间的比例,设置 Parallel Scavenge 收集器具有自适应调节策略
CMS垃圾收集器
CMS是一个可以并发收集的垃圾收集器,第一次实现了让垃圾收集线程与用户线程同时工作。CMS 收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。停顿时间越短(低延迟)就越适合与用户交互的程序,良好的响应速度能提升用户体验。
- CMS采用的标记清除算法,会存在内存碎片问题,也会有短暂的STW
- CMS作为老年代垃圾收集器时,因为架构问题,新生代只能选择ParNew 或者 Serial 收集器中的一个。
- G1垃圾收集器出现之前,CMS使用非常广泛。
CMS 整个过程比之前的收集器要复杂,整个过程分为4个主要阶段,即初始标记阶段、并发标记阶段、重新标记阶段和并发清除阶段。(涉及 STW 的阶段主要是:初始标记和重新标记)
- 初始标记阶段:在这个过程中,所有的用户线程都会停止(STW),这个阶段的主要任务是标记出GC Roots 能直接关联到的对象。标记完成后,就会恢复用户线程,由于直接关联对象比较小,所以这里的速度非常快。
- 并发标记阶段:从 GC Roots 的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。
- 重新标记阶段:由于在并发标记阶段过程中,用户线程也在执行。这里是修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。这个阶段比初始标记长一些,但是比并发标记时间短。
- 并发清除阶段:这个阶段是清理掉垃圾对象,释放内存空间。由于不需要进行内存整理,所以可以与用户线程并发进行。
- 虽然CMS使用的是并发回收,但是在初始化标记和重新标记过程中仍然需要STW,不过时间不会太长。并发标记过程时间长,但是与用户线程并发,所以用户体验好。
- CMS每次执行完内存回收之后,都会产生内存碎片,为新对象分配内存时只能选在空闲列表,不能使用指针碰撞
问题:CMS为什么不适用标记整理算法?
因为在并发清除的时候,会有用户线程在并发执行,如果这个时候进行内存整理,那么对象的内存地址就会改变,会影响到用户线程。
优点: 并发收集、延时性低。
缺点:
- 会产生内存碎片,在并发清除后,无法分配大对象会产生FullGC。
- 对cpu敏感,在并发标记阶段虽然不会停止用户线程,但是会占用部分线程导致程序变慢,吞吐量降低。
- 无法处理浮动垃圾,浮动垃圾指在并发标记阶段产生的新垃圾。CMS是不会再清除阶段清除的。只能在下一次GC时回收
参数设置
- -XX:+UseConcMarkSweepGC手动指定使用 CMS 收集器执行内存回收任务。该参数开启后默认开启XX:+UseParNewGC。
- XX:CMSInitiatingoccupanyFraction设置堆内存使用率的阈值,一旦达到该阈值,便开始进行回收。
- JDK 5 及以前版本的默认值为 68,即当老年代的空间使用率达到 68% 时,会执行一次 CMS 回收。JDK 6 及以上版本默认值为 92%。
- 如果内存增长缓慢,则可以设置一个稍大的值,大的阀值可以有效降低 CMS 的触发频率,减少老年代回收的次数可以较为明显地改善应用程序性能。反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器。因此通过该选项便可以有效降低 Full GC 的执行次数
- XX:+UseCMSCompactAtFullCollection用于指定在执行完 Full GC 后对内存空间进行压缩整理,以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变得更长了。
- -XX:CMSFullGCsBeforecompaction设置在执行多少次 Full GC 后对内存空间进行压缩整理。
- -XX:ParallelcMSThreads设置 CMS 的线程数量。
- CMS 默认启动的线程数是(ParallelGCThreads + 3)/ 4,ParallelGCThreads 是年轻代并行收集器的线程数。当 CPU 资源比较紧张时,受到 CMS 收集器线程的影响,应用程序的性能在垃圾回收阶段可能会非常糟糕。
总结:
CMS垃圾收集器是老年代的垃圾收集器,新生代只能使用ParNew垃圾收集器。
使用的是标记清除算法,会产生内存碎片。
第一款真正意义上的并发垃圾收集器
特点是低延迟,低吞吐量
可以通过虚拟机参数设置相应的参数
JDK14删除CMS垃圾收集器
G1垃圾收集器
G1是一个并行的垃圾收集器,它把内存分为很多不相关的区域(region),每个region物理上可以是不连续的。使用不同的region来表示Eden区 幸存者区 Old区等。
可以有计划的避免整堆收集,G1垃圾收集器可以跟踪每个region里面的垃圾堆积的大小(回收释放的空间大小),在后台维护一个优先列表,优先回收价值大的region
G1是一款面向服务端应用的垃圾收集器,主要针对配备多核cpu及大容量内存的机器,以极高的概率满足GC停顿时间的同时还兼容高吞吐量的特征
是JDK1.9之后默认的垃圾收集器
优点
与其他的垃圾收集器相比,G1使用了全新的分区算法
- 并行性:G1 在回收期间,可以有多个 GC 线程同时工作,有效利用多核计算能力。此时用户线程 STW。
- 并发性:G1 拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况。
- 分代收集
- 从分代上看,G1 依然属于分代型垃圾回收器,它会区分年轻代和老年代,年轻代依然有 Eden 区和 Survivor 区。但从堆的结构上看,它不要求整个 Eden 区、年轻代或者老年代都是连续的,也不再坚持固定大小和固定数量。
- 将堆空间分为若干个区域(Region),这些区域中包含了逻辑上的年轻代和老年代。
- 和之前的各类回收器不同,它同时兼顾年轻代和老年代。对比其他回收器,或者工作在年轻代,或者工作在老年代
- G1 分代
- 空间整合:将内存划分为一个个的 Region。内存的回收是以 Region 作为基本单位的。Region 之间是复制算法,但整体上实际可看作是标记-压缩(Mark-Compact)算法,两种算法都可以避免内存碎片。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC。尤其是当 Java 堆非常大的时候,G1 的优势更加明显。
- 可预测的停顿时间模型
- 由于分区的原因,G1 可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制。
- G1 跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收
- 相比于 CMS GC,G1 未必能做到 CMS 在最好情况下的延时停顿,但是最差情况要好很多。
缺点
- 相较于 CMS,G1 还不具备全方位、压倒性优势。比如在用户程序运行过程中,G1 无论是为了垃圾收集产生的内存占用(Footprint)还是程序运行时的额外执行负载(Overload)都要比 CMS 要高。
- 从经验上来说,在小内存应用上 CMS 的表现大概率会优于 G1,而 G1 在大内存应用上则发挥其优势。平衡点在 6~8GB 之间。
参数设置
- -XX:+UseG1GC手动指定使用 G1 垃圾收集器执行内存回收任务 JDK1.9之后默认使用
- -XX:G1HeapRegionSize 设置每个 Region 的大小。值是 2 的幂,范围是 1MB 到 32MB 之间,目标是根据最小的 Java 堆大小划分出约 2048 个区域。默认是堆内存的 1/2000。
- -XX:MaxGCPauseMillis 设置期望达到的最大 GC 停顿时间指标(JVM 会尽力实现,但不保证达到),默认值是 200ms。
- -XX:+ParallelGcThread 设置 STW 时GC线程数的值,最多设置为 8。
- -XX:ConcGCThreads设置并发标记的线程数。将 n 设置为并行垃圾回收线程数(ParallelGCThreads)的 1/4 左右。
- -XX:InitiatingHeapOccupancyPercent设置触发并发 GC 周期的 Java 堆占用率阈值。超过此值,就触发 GC,默认值是 45。