👈👈👈 欢迎点赞收藏关注哟
首先分享之前的所有文章 >>>> 😜😜😜
文章合集 : 🎁 juejin.cn/post/694164…
Github : 👉 github.com/black-ant
CASE 备份 : 👉 gitee.com/antblack/ca…
一. 前言
上一篇 # 深刻体验不同对象下 G1 回收器的内存变化 , 了解完了对象对垃圾回收的影响 ,这一篇来看一下 JVM 参数对垃圾回收的影响
二. 常见的参数汇总
三. 我们有哪些可用的垃圾回收
这一块也是老生常谈的点了, 就不详细来说了, 只是列出来, 便于后文的理解 :
以下是包括 ParNew、Parallel Scavenge 和 Parallel Old 在内的完整表格:
收集器 | 串行、并行or并发 | 新生代/老年代 | 算法 | 目标 | 适用场景 |
---|---|---|---|---|---|
Serial | 串行 | 新生代 | 复制算法 | 响应速度优先 | 单CPU环境下的Client模式 |
Serial Old | 串行 | 老年代 | 标记-整理 | 响应速度优先 | 单CPU环境下的Client模式、CMS的后备预案 |
ParNew | 并行 | 新生代 | 复制算法 | 响应速度优先 | 多CPU环境时在Server模式下与CMS配合 |
Parallel Scavenge | 并行 | 新生代 | 复制算法 | 最大化吞吐量 | 在后台运算而不需要太多交互的任务 |
Parallel Old | 并行 | 老年代 | 标记-整理 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
CMS | 并发 | 老年代 | 标记-清除 | 降低暂停时间 | 集中在互联网站或B/S系统服务端上的Java应用 |
G1 | 并发 | 新生代 / 老年代 | 标记-整理+复制算法 | 可预测的暂停时间控制 | 面向服务端应用,将来替换CMS |
Shenandoah | 并发 | 整个堆 | 标记-复制-压缩 | 极低的暂停时间 | 适合大内存低延迟应用 |
ZGC | 并发 | 整个堆 | 标记-复制-重定位 | 极低的暂停时间 | 适合大内存低延迟应用 |
我这里主要以 G1 为例 , 其中会涉及到一些通用的属性
四. 初始流程
在上一篇中我们尽量模拟实际的场景,基于软引用模拟可被回收的对象,同时基于一定的几率模拟始终不会被回收的对象。
初始参数
java复制代码-XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -Xms512m -Xmx2048m
验证代码
java
复制代码
private void add() {
// 创建一个存储软引用的列表
List<SoftReference<byte[]>> softReferenceList = new ArrayList<>();
List<WeakReference<byte[]>> weakReferenceList = new ArrayList<>();
ArrayList<byte[]> retainedObjects = new ArrayList<>();
Random random = new Random();
// 分配内存并使用软引用引用这些内存块
try {
while (true) {
int i = atomicInteger.addAndGet(1);
if (i % 10 < 1) {
// 弱引用每次 GC 都会被回收
byte[] largeArray = buildArray(); // 64K - 0.64M
WeakReference<byte[]> weakReference = new WeakReference<>(largeArray);
weakReferenceList.add(weakReference);
} else if (i % 10 < 3) {
//以一定概率保留对象引用,防止它们被回收
byte[] largeArray = buildArray();
if (random.nextInt(10) < 5) {
retainedObjects.add(largeArray);
}
// 模拟被回收的常见
if (random.nextInt(1000) < 10) {
retainedObjects = new ArrayList<>();
}
} else {
// 软引用内存不足时被回收
SoftReference<byte[]> softReference = new SoftReference<>(buildArray());
softReferenceList.add(softReference);
}
}
} catch (Exception e) {
}
}
基于这个模型,我们可以跑出一个初版数据 :
- 由于有无法清除的对象 ,老年代最小占用量在逐步上升
- 由于年轻代远远没有达到年轻代的阈值 ,所以
年轻代每次都是被 FullGC 回收走了
- 这里可以看左下角那张图的黄线部分 , 后半段基本上没有了 Minor GC
- 除了 S1 区域的老问题 ,基本上符合常见的场景
五. 进行配置调整
- 为了更快的验证 ,下面的所有操作都会加快对象产生的速度
5.1 通用配置
在初始化一个新应用的时候 ,有些配置是需要我们默认要加上的 :
-
设置内存的各项作用指标 , 基于服务器的配置进行设计
-Xms512m -Xmx2048m -Xmn512m
-
选择对应的垃圾回收器 :既可以选择单一的 ,也可以进行组合使用
-XX:+UseParallelGC -XX:+UseParallelOldGC
-XX:+UseG1GC
- 除此之外还有一些日志的配置,这些平时一般不配,配了就是有问题了
- -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log
- -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumps
在这里我们垃圾回收器选择使用 G1 , 不使用其他的回收器, 以下是核心的测试流程 :
5.2 设置最大晋升年龄阈值
java
复制代码
// - 通过10次垃圾回收才会来到老年代
-XX:+UseG1GC
-XX:+UnlockExperimentalVMOptions
-Xms512m
-Xmx2048m
-XX:MaxTenuringThreshold=10
- 预期 : 年轻代占用的空间应该明显变多 ,S1 区域会有比较大的增加 ,且一直保持活跃
-
结果 : 基本符合预期
-
- Survivor 区间并没有像之前一样缩减到最小值后不再变化
-
- 年轻代变化较小 ,主要是 Eden 区给的过大 ,不容易触发 YoungGC
-
5.3 设置年轻代的可用大小
java
复制代码
// - 通过10次垃圾回收才会来到老年代
-XX:+UseG1GC
-XX:+UnlockExperimentalVMOptions
-Xms512m
-Xmx2048m
-XX:MaxTenuringThreshold=10
-XX:G1NewSizePercent=5
-XX:G1MaxNewSizePercent=20
- 备注 : 这里很奇怪 ,按理说我的 JDK 应该没有这个参数的 ,但是确实是生效了
- 预期 : 我们设置了G1年轻代最大为20% (等同于 Xmn), 那么年轻代最大应该为 409M
-
结果 : 基本符合预期
- 相比之前 Eden 区动不到动到 700 M ,修改后的内存要小得多 (
图一
) - 由于年轻代分配的空间更小了 ,年轻代垃圾回收的频率明显变多了 (
图三
) - 可以看出 ,不同的场景下 ,年轻代合理配置能让整个回收更加健康 (
图二
)
- 相比之前 Eden 区动不到动到 700 M ,修改后的内存要小得多 (
- 问题 : 可以看出来 ,申请的空间和使用的空间差距有点大
如果把这个参数调大后
java
复制代码
-XX:G1NewSizePercent=30
-XX:G1MaxNewSizePercent=50
- 可以明确看到 ,确实生效了
5.4 配置停顿时间
❗❗❗特别注意 ,停顿时间是个很玄学的概念 ,没有特殊原因 ,不要设置它。(官方建议)
java
复制代码
// - 为了凸显效果 ,我们把这个值设置得离谱点
-XX:+UseG1GC
-XX:+UnlockExperimentalVMOptions
-Xms512m
-Xmx2048m
-XX:MaxTenuringThreshold=10
-XX:G1NewSizePercent=5
-XX:G1MaxNewSizePercent=20
// 会尝试两种修改的方式, 一种是往大了离谱点 , 一种是往小了改
-XX:MaxGCPauseMillis=1000
-XX:MaxGCPauseMillis=3
- 预期 : 设置后我也猜不到预期,直接看结果吧
-
结果 : 完全不符合预期
- 当将这个值调的非常离谱的时候 ,其实是不会生效的
- 在 JVM 里面 ,MaxGCPauseMillis 是预期目标 ,这是一个软目标,JVM 将尽最大努力实现它
- 既然是预期值 ,也就意味着可能无法达到这个目标 ,也就没有太明显的作用
设置一个较低值的效果
- 虽然没有明确控制数值在某个区间内 ,但是能看到对比没有设置 ,还是由很大区别的
5.5 设置垃圾回收的间隔
如果并发不太高 ,我不期望回收得特别频繁,可以通过垃圾回收间隔来实现 :
java
复制代码
-XX:+UseG1GC
-XX:+UnlockExperimentalVMOptions
-Xms512m
-Xmx2048m
-XX:MaxGCPauseMillis=200
// 这里由 500 改成 2000 ,来看一下效果
-XX:GCPauseIntervalMillis=2000
- 预期 : 当修改垃圾回收间隔后 ,回收得频率应该要变低
-
结果 : 符合预取 ,回收频率明显降了很多 ,
- 主要的差别可以
从左下角那张图看到
,回收的频率由1次/秒
变成了0.25次/秒
- 相当于 4 秒才发生一次 ,和我们预期的 4 倍相当
- 主要的差别可以
5.6 设置老年代回收阈值
我们知道老年代内存不够时就会触发垃圾回收 ,对于一些场景需要修改这个触发时机则可以通过 InitiatingHeapOccupancyPercent :
java
复制代码
-XX:+UseG1GC
-XX:+UnlockExperimentalVMOptions
-Xms512m
-Xmx2048m
-XX:MaxTenuringThreshold=10
// 此处由 30 -> 70
-XX:InitiatingHeapOccupancyPercent=30
- 期望 : 首次触发垃圾回收的时机变动,符合 30% 和 70% 的现象
- 实际 : 几乎无效 ,后面查询各项资料后发现 ,JDK8 早期版本存在很多问题 ,对于这个配置没有很好的兼容
- 首次 major GC 实际上还是在老年代无法申请到内存时触发
- 考虑 JDK 版本问题 ,尝试了 JDK11 后再次验证
JDK 11 结果复现
- 结果 :可以看到 ,
第一次 major GC 确实是在 30% 左右进行触发
- 那么把这个值进行调整 ,设置为 70% 后的表现如何 ?
- 这里要注意 ,这个到底是针对总的堆内存的 70% 还是 老年代的 70% 我是不太确定的,不同版本不一样
- 可以参考这篇文章 ,时间有限 ,我就没测试了 @ heapdump.cn/article/271…
- 70% 的效果不明显和 JDK的处理机制有关 ,动态的处理方式并不能保证完全和数值一致
需要注意 : 如果一开始内存就超过这个值了 ,那么其实设置也没有很好的效果
- 这里内存在启动的时候就快速跑到了 50% 以上 ,也不存在触发 FullGC 的阈值了
六. 不同版本对垃圾回收的影响
- 基于简单看到 ,相同的代码 ,相同的运行环节下面 ,JDK 8 和 JDK 11 使用 G1 的差距很大
- 甚至于在同一个大版本下 ,不同 JDK8 的小版本里面对于 Region 的各项数据计算都会有不同的差异
总结
- 不同的参数 一定要 注意 JDK 版本号 ,版本不一样对应的实现和方案都不一样
- 虽然采用一样的环境和代码 ,但是这种不可能完全体现出配置的作用,毕竟没读过源码,里面有些机制还不清楚
- 修改配置要谨慎 , 首先是可能会失效 , 同时不同版本的效果又不一样 ❗❗❗
- 关于核心配置每经过压测和测试环境测试别直接上生产,
尤其是早期的 JDK 版本 ,可能有 BUG
附录 : 如何查看支持的 JVM 参数及默认参数
这是我这个 JDK 版本支持的参数 ,这里列出来了
java
复制代码
> java -XX:+PrintFlagsFinal
double G1ConcMarkStepDurationMillis = 10.000000 {product} {default}
uint G1ConcRefinementThreads = 23 {product} {ergonomic}
uintx G1ConfidencePercent = 50 {product} {default}
size_t G1HeapRegionSize = 4194304 {product} {ergonomic}
uintx G1HeapWastePercent = 5 {product} {default}
uintx G1MixedGCCountTarget = 8 {product} {default}
uintx G1PeriodicGCInterval = 0 {manageable} {default}
bool G1PeriodicGCInvokesConcurrent = true {product} {default}
double G1PeriodicGCSystemLoadThreshold = 0.000000 {manageable} {default}
intx G1RSetUpdatingPauseTimePercent = 10 {product} {default}
uint G1RefProcDrainInterval = 1000 {product} {default}
uintx G1ReservePercent = 10 {product} {default}
uintx G1SATBBufferEnqueueingThresholdPercent = 60 {product} {default}
size_t G1SATBBufferSize = 1024 {product} {default}
size_t G1UpdateBufferSize = 256 {product} {default}
bool G1UseAdaptiveIHOP = true {product} {default}
uintx MaxGCMinorPauseMillis = 18446744073709551615 {product} {default}
uintx MaxGCPauseMillis = 200 {product} {default}
int ParGCArrayScanChunk = 50 {product} {default}
uintx ParallelGCBufferWastePct = 10 {product} {default}
uint ParallelGCThreads = 23 {product} {default}
// .................
源文:面试官 :你好 ,对 JVM 垃圾回收参数有没有深入的理解 ?
如有侵权请联系站点删除!
技术合作服务热线,欢迎来电咨询!