正文
本文的内容主要如下:
GC 基础原理,涉及调优目标,GC 事件分类、JVM 内存分配策略、GC 日志分析等
CMS 原理及调优。
G1 原理及调优
GC 问题排查和解决思路
1. GC 基础原理
1.1. GC 调优目标
大多数情况下对 Java 程序进行 GC 调优,主要关注两个目标:
响应速度(Responsiveness):响应速度指程序或系统对一个请求的响应有多迅速
比如,用户订单查询响应时间,对响应速度要求很高的系统,较大的停顿时间是不可接受的。调优的重点是在短的时间内快速响应。
吞吐量(Throughput):吞吐量关注在一个特定时间段内应用系统的最大工作量
例如每小时批处理系统能完成的任务数量,在吞吐量方面优化的系统,较长的 GC 停顿时间也是可以接受的,因为高吞吐量应用更关心的是如何尽可能快地完成整个任务,不考虑快速响应用户请求
在 GC 调优中,GC 导致的应用暂停时间影响系统响应速度,GC 处理线程的 CPU 使用率影响系统吞吐量。
1.2. GC 分代收集算法
现代的垃圾收集器基本都是采用分代收集算法,其主要思想: 将 Java 的堆内存逻辑上分成两块:新生代、老年代,针对不同存活周期、不同大小的对象采取不同的垃圾回收策略。
1.2.1. 新生代(Young Generation)
新生代又叫年轻代,大多数对象在新生代中被创建,很多对象的生命周期很短。每次新生代的垃圾回收(又称 Young GC、Minor GC、YGC)后只有少量对象存活,所以使用复制算法,只需少量的复制操作成本就可以完成回收。
**新生代内又分三个区:**一个 Eden 区,两个 Survivor 区(S0、S1,又称From Survivor、To Survivor),大部分对象在 Eden 区中生成。
当 Eden 区满时,还存活的对象将被复制到两个 Survivor 区(中的一个);当这个 Survivor 区满时,此区的存活且不满足晋升到老年代条件的对象将被复制到另外一个 Survivor 区。对象每经历一次复制,年龄加 1,达到晋升年龄阈值后,转移到老年代。
1.2.2. 老年代(Old Generation)
在新生代中经历了 N 次垃圾回收后仍然存活的对象,就会被放到老年代,该区域中对象存活率高。老年代的垃圾回收通常使用“标记-整理”算法。
1.3. GC 事件分类
根据垃圾收集回收的区域不同,垃圾收集主要分为:
Young GC
Old GC
Full GC
Mixed GC
1.3.1. Young GC
新生代内存的垃圾收集事件称为 Young GC(又称 Minor GC),当 JVM 无法为新对象分配在新生代内存空间时总会触发 Young GC。比如 Eden 区占满时,新对象分配频率越高,Young GC 的频率就越高。
Young GC 每次都会引起全线停顿(Stop-The-World),暂停所有的应用线程,停顿时间相对老年代 GC 造成的停顿,几乎可以忽略不计。
1.3.2. Old GC/Full GC/Mixed GC
Old GC:只清理老年代空间的 GC 事件,只有 CMS 的并发收集是这个模式。
Full GC:清理整个堆的 GC 事件,包括新生代、老年代、元空间等 。
Mixed GC:清理整个新生代以及部分老年代的 GC,只有 G1 有这个模式。
1.4. GC 日志分析
GC 日志是一个很重要的工具,它准确记录了每一次的 GC 的执行时间和执行结果,通过分析 GC 日志可以调优堆设置和 GC 设置,或者改进应用程序的对象分配模式。
开启的 JVM 启动参数如下:
-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps复制代码
常见的 Young GC、Full GC 日志含义如下:
Young GC
Full GC
免费的 GC 日志图形分析工具推荐下面 2 个:
GCViewer:下载 jar 包直接运行
gceasy:Web 工具,上传 GC 日志在线使用
1.5. 内存分配策略
Java 提供的自动内存管理,可以归结为解决了对象的内存分配和回收的问题。前面已经介绍了内存回收,下面介绍几条最普遍的内存分配策略:
1.5.1. 对象优先在 Eden 区分配
大多数情况下,对象在先新生代 Eden 区中分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Young GC。
1.5.2. 大对象直接进入老年代
JVM 提供了一个对象大小阈值参数(-XX:PretenureSizeThreshold,默认值为 0,代表不管多大都是先在 Eden 中分配内存)。
大于参数设置的阈值值的对象直接在老年代分配,这样可以避免对象在 Eden 及两个 Survivor 直接发生大内存复制。
1.5.3. 长期存活的对象进入老年代
对象每经历一次垃圾回收,且没被回收掉,它的年龄就增加 1,大于年龄阈值参数(-XX:MaxTenuringThreshold,默认 15)的对象,将晋升到老年代中。
1.5.4. 空间分配担保
当进行 Young GC 之前,JVM 需要预估:老年代是否能够容纳 Young GC 后新生代晋升到老年代的存活对象,以确定是否需要提前触发 GC 回收老年代空间,基于空间分配担保策略来计算。
Young GC 之后如果成功(Young GC 后晋升对象能放入老年代),则代表担保成功,不用再进行 Full GC,提高性能。
如果失败,则会出现“promotion failed”错误,代表担保失败,需要进行 Full GC。
1.5.5. 动态年龄判定
新生代对象的年龄可能没达到阈值(MaxTenuringThreshold 参数指定)就晋升老年代。
如果 Young GC 之后,新生代存活对象达到相同年龄所有对象大小的总和大于任意 Survivor 空间(S0+S1空间)的一半,此时 S0 或者 S1 区即将容纳不了存活的新生代对象。年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到 MaxTenuringThreshold 中要求的年龄。
另外,如果 Young GC 后 S0 或 S1 区不足以容纳:未达到晋升老年代条件的新生代存活对象,会导致这些存活对象直接进入老年代,需要尽量避免。
2. CMS 原理及调优
2.1. 术语解释
2.1.1. 可达性分析算法
用于判断对象是否存活,基本思想是通过一系列称为“GC Root”的对象作为起点(常见的 GC Root 有系统类加载器、栈中的对象、处于激活状态的线程等),基于对象引用关系,从 GC Roots 开始向下搜索,所走过的路径称为引用链,当一个对象到 GC Root 没有任何引用链相连,证明对象不再存活。
2.1.2. Stop The World
GC 过程中分析对象引用关系,为了保证分析结果的准确性,需要通过停顿所有 Java 执行线程,保证引用关系不再动态变化,该停顿事件称为 Stop The World(STW)。
2.1.3. Safepoint
代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,说明虚拟机当前的状态是安全的,如果有需要 GC,线程可以在这个位置暂停。
HotSpot 采用主动中断的方式,让执行线程在运行期轮询是否需要暂停的标志,若需要则中断挂起。
2.2. CMS 算法简介
CMS(Concurrent Mark and Sweep 并发-标记-清除),是一款基于并发、使用标记清除算法的垃圾回收算法,只针对老年代进行垃圾回收。
CMS 收集器工作时,尽可能让 GC 线程和用户线程并发执行,以达到降低 STW 时间的目的。
通过以下命令行参数,启用 CMS 垃圾收集器:
-XX:+UseConcMarkSweepGC复制代码
值得补充的是,下面介绍到的 CMS GC 是指老年代的 GC,而 Full GC 指的是整个堆的 GC 事件,包括新生代、老年代、元空间等,两者有所区分。
2.3. 新生代垃圾回收
能与 CMS 搭配使用的新生代垃圾收集器有 Serial 收集器和 ParNew 收集器。
这 2 个收集器都采用标记复制算法,都会触发 STW 事件,停止所有的应用线程。不同之处在于,Serial 是单线程执行,ParNew 是多线程执行。
作者:零壹技术栈
链接:https://juejin.cn/post/6844903953415536654
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。