JVM---GC-垃圾回收器
概述
/** * 【GC---垃圾回收器】 * <概述> * 垃圾回收器 没有在规范中进行过多规定,可以由不同的厂商、不同版本的JVM来实现; * 由于JDK版本高速迭代,Java发展至今已经衍生出众多的 垃圾回收器 版本; * * <分类> * 1、按 垃圾回收器 线程数,分为: * 串行垃圾回收器Serial Collector * 同一段时间内只允许有一个垃圾回收线程 在单个CPU上 用于执行垃圾回收; * * 场景: * 单CPU硬件平台 * * 并行垃圾回收器Parallel Collector * 多个垃圾回收线程 在多个CPU上执行垃圾回收; * * 2、按 工作模式,分为: * 并发式垃圾回收器: * 用户线程 与 垃圾回收线程 同时执行,垃圾收集线程不会暂停用户线程; * * 独占式垃圾回收器: * 垃圾回收器工作时,停止所有的用户线程,直到垃圾回收完成; * * 3、按 碎片处理方式,分为: * 压缩式垃圾回收器 * 垃圾回收完成后,对存活对象进行压缩整理,清除回收后的碎片; * * 非压缩式垃圾回收器 * 仅对垃圾对象进行回收,不处理碎片; * * 4、按 工作的内存空间,分为: * 年轻代垃圾回收器、老年代垃圾回收器 * * <性能指标> * 吞吐量throughput: * 用户线程时间 与 总运行时间(用户时间+垃圾回收时间) 比例 * * 这种情况下,应用程序能容忍较高的暂停时间; * * 垃圾回收开销: * 垃圾回收时间 与 总运行时间(用户时间+垃圾回收时间) 比例 * * 暂停时间pause time: * 执行垃圾回收时,用户线程被暂停的时间; * * 回收频率: * 相对于 应用程序的执行,回收发生的频率; * * 内存占用: * 堆空间大小 * * 快速: * 一个对象从诞生到被回收所经历的时间; * * ***主要2点:吞吐量、暂停时间; * * ***现在标准: * 在最大吞吐量优先的情况下,降低停顿时间; */
如何查看当前JVM默认的GC
/** * 【GC---垃圾回收器】 * 为什么需要不同的GC? * Java的使用场景很多,移动端、服务器... * 针对不同的场景,提供不同的GC,提高垃圾回收的性能; * * <发展史> * 1999年,JDK1.3.1使用串行方式的Serial GC(第一款GC);ParNew GC 是Serial GC的多线程版本; * 2002年,JDK1.4.2,Parallel GC、CMS(Concurrent Mark Sweep) GC 发布; * Parallel GC在JDK6之后称为Hotspot默认GC; * 2012年,JDK1.7,G1; * 2017年,JDK9G1称为默认GC,替代CMS; * ... * * <7种经典GC> * 分类: * 串行: * Serial、Serial Old * 并行: * ParNew、Parallel Scavenge、Parallel Old * 并发: * CMS、G1 * * 与堆分代的关系: * 新生代: * Serial GC、ParNew GC、Parallel Scavenge GC; * 老年代: * Serial Old GC、Parallel Old GC、CMS; * 新生代与老年代: * G1 GC * * <如何查看当前JVM默认的GC> * 1、-XX:+PrintCommandLineFlags * eg:jdk8 * -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC * 2、jinfo -flag UseParallelGC 进程ID */
Serial回收器
/** * 【GC---垃圾回收器-Serial回收器】 * * 单线程回收器: * a, 只使用一个CPU或一个线程完成回收工作; * b, 回收时,STW,暂停用户线程,直到回收结束; * * 优势: * 简单高效 * (对单CPU的环境,Serial回收器没有线程切换的开销) * * 设置Hotspot中使用Serial回收器: * -XX:+UseSerialGC * 指定新生代使用Serial GC、老年代使用Serial Old GC; * * * <Serial GC> * what * 新生代GC; * 最基本、历史最悠久的GC; * 作为 Hotspot client模式下默认的新生代回收器; * ***复制算法 + 串行回收 + STW机制; * * <Serial Old GC> * what * 老年代GC; * 标记-压缩算法 + 串行回收 + STW机制 * client模式下,老年代默认GC; * server模式: * 与新生代Parallel Scavenge GC配合使用; * 作为老年代CMS的后备方案; */
ParNew GC
/** * 【GC---垃圾回收器-ParNew GC】 * what * Par:Parallel缩写 New:只能处理新生代 * ***复制算法 + 并行回收 + STW机制; * * 设置Hotspot中使用ParNew GC: * -XX:+UseParNewGC * 默认线程数与CPU数相同; * * * ParNew GC在任何场景下都比Serial GC效率高? * ParNew GC在多CPU环境下,可以利用多CPU、多核的物理硬件资源优势; * 但是在单CPU环境下,Serial GC是单线程,不需要切换资源,ParNew GC是多线程,需要线程切换,Serial GC效率更高; */
Parallel回收器
/** * 【GC---垃圾回收器-Parallel回收器】 * * 高吞吐量: * 可以高效利用CPU时间,尽快完成程序的运算任务; * 适合在后台运算而不需要太多交互的场景: * 批量处理、订单处理、工资支付、科学计算... * * JDK8默认使用Parallel回收器 * eg: -XX:+PrintCommandLineFlags * -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC * * 设置Parallel回收器 * 使用Parallel回收器 * -XX:+UseParallelGC 或 -XX:+UseParallelOldGC 添加其中一个即可(互相激活) * * Parallel Scavenge GC的线程数 * -XX:ParallelGCThreads * 默认情况下,当CPU数<8,ParallelGCThreads等于CPU数; * 当CPU数>8,ParallelGCThreads = 3+[5*CPU_Count/8] * * 垃圾回收器 最大停顿时间(STW时间,单位ms) * -XX:MaxGCPauseMillis * * 垃圾回收时间 与 总时间的 比例(用于衡量吞吐量大小) * -XX:GCTimeRatio * 默认值99,范围(0,100) * * Parallel Scavenge GC自适应策略 * -XX:+UseAdaptiveSizePolicy * 开启自适应模式,年轻代大小、Eden和Survivor比例、晋升老年代的对象年龄等参数会自动调整,已达到 在堆大小、吞吐量、停顿时间之间的平衡点; * 手动调优困难的场合,可以使用自适应; * * <Parallel Scavenge GC> * what * 年轻代GC * ***复制算法 + 并行回收 + STW机制 * * Parallel Scavenge GC 与 ParNew GC的区别: * Parallel Scavenge GC目标是达到可控制的吞吐量; * 自适应调节策略; * * <Parallel Old> * what * 老年代GC * JDK1.6时提供,用来替代Serial Old GC; * ***标记-压缩算法 + 并行回收 + STW机制 */
CMS GC
/** * 【GC---垃圾回收器-CMS GC】 * what * Concurrent Mark Sweep GC * 老年代GC * JDK1.5,Hotspot推出一款在强交互应用中几乎可认为有划时代意义的垃圾回收器CMS; * Hotspot中第一款真正意义上的并发回收器,实现了让垃圾回收线程与用户线程同时工作; * * 关注点: * 尽可能缩短垃圾回收时用户线程的停顿时间; * 停顿时间越短(低延迟)越适合与用户交互的程序; * * ***标记-清除算法 + STW机制 * * 工作原理: * 经历4个阶段: * 1、初始标记 initial Mark * 这个阶段,会进行STW,用户线程出现短暂的暂停; * 这个阶段主要任务:仅仅标记出GC Roots能直接关联到的可达对象; * 标记完成后,就会恢复之前被暂停的用户线程; * 由于直接关联对象比较小,所以速度非常快; * * 2、并发标记 Concurrent Mark * 从GC Roots直接关联对象开始遍历整个对象图的过程,这个过程耗时长但不需要停顿用户线程,可以与垃圾回收线程并发执行; * * 3、重新标记 Remark * 由于并发标记阶段,程序的用户线程与垃圾回收线程同时运行; * 为了修正并发标记期间,因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录; * 会进行STW,比初始标记时间长,比并发标记时间短; * * 4、并发清除 Concurrent Sweep * 清理标记阶段已经是垃圾的对象; * 垃圾回收线程与用户线程并发执行; * * 由于垃圾回收时,用户线程没有暂停,所以在CMS回收时,还应该确保用户线程有足够的内存可用; * 因此,CMS回收器 不能像其他回收器那样等到老年代空间不足再进行回收,而是当堆内存使用率达到一定阈值时,开始进行回收; * 如果CMS回收时,预留的内存无法满足需要,会出现Concurrent Mode Failure,这时JVM将启动后备方案,临时启用 Serial Old GC进行回收; * * 优点: * 1、并发回收: * 垃圾回收线程与用户线程并发执行; * 2、低延迟: * 初始标记 和 重新标记 STW时间短; * * 缺点: * 1、碎片化: * CMS GC采用标记-清除算法,会产生内存碎片; * 当为新对象分配内存时,只能使用空闲列表执行内存分配; * * 当大对象分配时,可用空间不足 且 达到阈值,触发GC; * * ***为什么不适用标记-压缩算法? * 标记-压缩 需要进行对象移动,影响正在执行的用户线程; * * 2、对CPU资源敏感: * 虽然不会导致用户线程暂停,但是因为占用了一部分线程导致总吞吐量降低; * * 3、无法处理浮动垃圾: * 并发标记阶段,用户线程产生新的垃圾对象,只能下次GC时释放; * * 设置CMS GC * 老年代使用 CMS GC * -XX:+UseConcMarkSweepGC * 当老年代使用CMS后,将自动开启 ParNew GC + Serial Old 组合; * eg: * -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:MaxNewSize=697933824 -XX:MaxTenuringThreshold=6 -XX:OldPLABSize=16 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC * * 堆内存使用阈值 * -XX:CMSInitiatingOccupanyFraction * JDK5及之前的版本,默认是68; * JDK6及以上,默认是92; * * CMS执行Full GC后进行内存压缩整理 * -XX:+UseCMSCompactAtFullCollection * 内存压缩整理时,只能暂停用户线程; * * CMS执行多少次Full GC后进行内存压缩 * -XX:CMSFullGCBeforeCompaction * * CMS线程数量 * -XX:ParallelCMSThreads * 默认启动的线程数 (ParallelCMSThreads+3)/4 */
G1 GC
/** * 【GC---垃圾回收器-G1 GC】 * why? * 为什么需要G1 GC? * 随着业务不断增长,用户越来越多,经常造成STW的GC跟不上实际的需求,需要对GC进行优化; * 适应现在不断扩大的内存和不断增加的CPU数量,进一步降低暂停时间 同时 兼顾良好的吞吐量; * * ***官方给G1的目标:在延迟可控的情况下获得尽可能高的吞吐量; * * 为什么叫G1? * G1 把堆内存分为 很多不相关的区域(region,物理上不连续),不同的region表示Eden、S0、S1、Old等; * G1 有计划的避免了 对整个堆空间的全区域回收,跟踪每个region的垃圾堆积的价值大小,在后台维护一个优先列表, * 每次根据允许的回收时间,优先回收价值最大的region; * 由于这种方式侧重于垃圾最大量的region,所以取名Garbage First; * * what * 面向服务端应用的垃圾回收器,主要针对配备多核CPU及大容量内存的机器; * JDK7正式启动,移除了Experimental标识; * JDK9的默认垃圾回收器,被Oracle官方称为 全功能的垃圾回收器; * JDK8需要使用-XX:+UseG1GC启用G1 GC; * * <优点> * 1、并行与并发 * 并行性 * G1回收期间,有多个GC线程同时工作,有效利用多核计算能力 * 并发性 * G1线程与用户线程并发执行; * * 2、分代回收 * 从分代上看,G1属于分代型垃圾回收器,区分年轻代和老年代,年轻代有Eden、Survivor区; * 从堆结构上看,G1不要求Eden、Survivor、老年代是连续的,不坚持固定大小、固定数量; * 将堆空间分为若干个区域,区域包含了逻辑上的新生代、老年代; * 与之前的回收器不同,同时兼顾年轻代、老年代; * 其他回收器,仅工作在年轻代或老年代; * * 3、空间整合 * CMS: * 标记-清除算法,存在内存碎片化问题,若干次GC后可进行一次碎片整理; * G1: * 内存的回收以region为基本单位; * region之间采用复制算法; * * 4、可预测的停顿时间模型 soft real-time * G1除了低停顿外,还能建立可预测的停顿时间模型(让使用者在一个长度为M毫秒的时间片段内,消耗在垃圾回收的时间不超过N毫秒) * 由于分区的原因,G1只选取部分区域进行回收,这样缩小了回收范围; * G1根据各个region的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的回收时间,优先回收价值最大的region,保证G1在有限的时间内获得尽可能高的回收效率; * * <缺点> * 相较于CMS,在垃圾回收产生的内存占用、程序运行时的额外负载 都要比CMS高; * 经验上讲,小内存应用CMS表现大概率由于G1,G1在大内存应用上优势更明显; * (平衡点在6~8GB之间) * * <设置G1 GC> * 使用G1 GC * -XX:+UseG1GC * 设置region大小 * -XX:G1HeapRegionSize * 值是2的幂 * 范围是1~32MB * 目标是根据最小的堆大小划分出约2048个区域 * 默认是堆的1/2000 * 设置期望达到的最大GC停顿时间指标 * -XX:MaxGCPauseMillis * JVM尽力实现,不保证达到; * 默认是200ms; * 设置GC工作线程数的值 * -XX:ParallelThread * 最多设置为8 * 设置并发标记线程数 * -XX:ConcGCThread * 设置为ParallelThread的1/4左右; * 设置触发并发GC周期的Java堆占用率阈值 * -XX:InitiatingHeapOccupancyPercent * 超过此值,触发GC; * 默认45; * * <适用场景> * 1、面向服务端应用,针对大内存、多CPU的机器; * 2、低GC延迟 且 有大堆应用程序; * 3、替换CMS GC * 以下情况,G1比CMS效果更好: * a,超过50%的堆内存被活跃数据占用; * b,对象分配频率或年代提升频率 变化很大; * c,GC停顿时间过长(>0.5~1s) * * ***在Hotspot垃圾回收器中,除了G1外,其他都使用内置的JVM执行GC操作; * G1 GC可以采用用户线程进行GC操作(当JVM的GC线程处理慢时,系统会调用用户线程协助GC) * * <region> * 将整个堆空间分为2048个大小相同的独立的region,每个region大小根据堆空间的实际大小而定,被控制在1~32MB,为2的N次幂(1、2、4、8、16、32); * region大小可以通过-XX:G1HeapRegionSize设置; * 所有的region大小相同 且 在JVM生命周期内不会被改变; * 虽然保留新生代、老年代概念,但是物理上不需要连续,通过region的动态分配实现逻辑上的连续; * 一个region可能属于Eden、S0、S1、Old,但是一个region被GC前只能属于一个角色,回收后可以变化角色; * G1新增了一种内存区域Humongous,用于存储大对象,如果超过1.5个region,就放到Humongous; * ***为什么要有Humongous? * 对于堆中大对象,默认分配到老年代,但如果是一个短期存在的大对象,会对垃圾回收产生影响,为了解决这个问题,划分了Humongous; * 如果一个Humongous放不下,会寻找连续的Humongous存放; * 如果找不到连续的Humongous,会进行Full GC; * * <RSet (Remembered Set)> * 解决什么问题? * 一个对象被不同的角色的region引用; * 一个region的对象可能被其他region中对象引用,回收新生代不得不扫描老年代; * * How? * 无论是G1 还是 其他分代回收器,JVM都使用 Remembered Set避免全局扫描; * 每个region都有一个 Remembered Set; * 每次Reference类型数据写操作时,都会生成一个Write Barrier暂时中断操作,然后检查将要写入的引用指向的对象是否和该Reference数据在同一个region: * 如果不同,需要将信息记录在 新引用对象所在的region的Remembered Set; * 当进行GC时,在GC Roots加入Remembered Set,就可以保证不进行全局扫描,也不会有遗漏; * * <G1 GC回收过程> * Young GC -> 老年代并发标记 Concurrent Marking -> 混合回收 Mixed GC -> 如果需要,Full GC * */
经典Garbage Collector总结
如何选择垃圾回收器
/** * 【GC---如何选择垃圾回收器】 * 1、优先调整堆大小让JVM自适应 * 2、如果内存小于100MB,选择Serial GC; * 3、如果是单核、单机程序 且 无停顿时间限制,选择 Serial GC; * 4、如果是多CPU、高吞吐量、允许停顿时间超过1s,选择并行或JVM自行选择; * 5、如果是多CPU、低停顿时间、快速响应,使用并发回收器; * 官方推荐G1; * * ***现在的互联网项目,基本上都选择G1 GC; * ***没有最好的垃圾回收器,没有万能的垃圾回收器; * ***调优永远是针对特定场景、特定需求,不存在一劳永逸的回收器; */