JVM参数调优经验

首先既然需要调优那么我们的系统肯定是出现了一些问题。

可能会有现象:Tp99分钟响应时间超过300ms,Tp999的毛刺很多,系统率可用率下降。

先排除应用代码导致,上游接口的问题,中间件的问题

评判 GC 的两个核心指标:

  • 延迟(Latency): 也可以理解为最大停顿时间,即垃圾收集过程中一次 STW 的最长时间,越短越好,一定程度上可以接受频次的增大,GC 技术的主要发展方向。
  • 吞吐量(Throughput): 应用系统的生命周期内,由于 GC 线程会占用 Mutator 当前可用的 CPU 时钟周期,吞吐量即为 Mutator 有效花费的时间占系统总运行时间的百分比,例如系统运行了 100 min,GC 耗时 1 min,则系统吞吐量为 99%,吞吐量优先的收集器可以接受较长的停顿。

目前各大互联网公司的系统基本都更追求低延时,避免一次 GC 停顿的时间过长对用户体验造成损失,衡量指标需要结合一下应用服务的 SLA,主要如下两点来判断:

重点需要关注的几个GC Cause:

  • System.gc(): 手动触发GC操作。
  • CMS: CMS GC 在执行过程中的一些动作,重点关注 CMS Initial Mark 和 CMS Final Remark 两个 STW 阶段。
  • Promotion Failure: Old 区没有足够的空间分配给 Young 区晋升的对象(即使总可用内存足够大)。
  • Concurrent Mode Failure: CMS GC 运行期间,Old 区预留的空间不足以分配给新的对象,此时收集器会发生退化,严重影响 GC 性能,下面的一个案例即为这种场景。
  • GCLocker Initiated GC: 如果线程执行在 JNI 临界区时,刚好需要进行 GC,此时 GC Locker 将会阻止 GC 的发生,同时阻止其他线程进入 JNI 临界区,直到最后一个线程退出临界区时触发一次 GC。

3.2 判断是不是 GC 引发的问题?

到底是结果(现象)还是原因,在一次 GC 问题处理的过程中,如何判断是 GC 导致的故障,还是系统本身引发 GC 问题。这里继续拿在本文开头提到的一个 Case:"GC 耗时增大、线程 Block 增多、慢查询增多、CPU 负载高等四个表象,如何判断哪个是根因?",笔者这里根据自己的经验大致整理了四种判断方法供参考:

  • 时序分析: 先发生的事件是根因的概率更大,通过监控手段分析各个指标的异常时间点,还原事件时间线,如先观察到 CPU 负载高(要有足够的时间 Gap),那么整个问题影响链就可能是:CPU 负载高 -> 慢查询增多 -> GC 耗时增大 -> 线程Block增多 -> RT 上涨。
  • 概率分析: 使用统计概率学,结合历史问题的经验进行推断,由近到远按类型分析,如过往慢查的问题比较多,那么整个问题影响链就可能是:慢查询增多 -> GC 耗时增大 -> CPU 负载高 -> 线程 Block 增多 -> RT上涨。
  • 实验分析: 通过故障演练等方式对问题现场进行模拟,触发其中部分条件(一个或多个),观察是否会发生问题,如只触发线程 Block 就会发生问题,那么整个问题影响链就可能是:线程Block增多 -> CPU 负载高 -> 慢查询增多 -> GC 耗时增大 -> RT 上涨。
  • 反证分析: 对其中某一表象进行反证分析,即判断表象的发不发生跟结果是否有相关性,例如我们从整个集群的角度观察到某些节点慢查和 CPU 都正常,但也出了问题,那么整个问题影响链就可能是:GC 耗时增大 -> 线程 Block 增多 -> RT 上涨。

不同的根因,后续的分析方法是完全不同的。如果是 CPU 负载高那可能需要用火焰图看下热点、如果是慢查询增多那可能需要看下 DB 情况、如果是线程 Block 引起那可能需要看下锁竞争的情况,最后如果各个表象证明都没有问题,那可能 GC 确实存在问题,可以继续分析 GC 问题了。

比如:

  1. IO 交互型: 互联网上目前大部分的服务都属于该类型,例如分布式 RPC、MQ、HTTP 网关服务等,对内存要求并不大,大部分对象在 TP9999 的时间内都会死亡, Young 区越大越好。
  2. MEM 计算型: 主要是分布式数据计算 Hadoop,分布式存储 HBase、Cassandra,自建的分布式缓存等,对内存要求高,对象存活时间长,Old 区越大越好。

应用soa:年轻代越大越好

后台还有定时任务系统,那么这个old区越大越好

java -XX:+PrintFlagsFinal -version | grep ParallelGCThread

通过命令查看参数: java -XX:+PrintFlagsFinal --version | grep 参数关键字,来查看当前参数是否被使用

明确一点

并发标记线程数字只有在 CMS,G1和zgc有使用

-XX:ParallelGCThreads={value} 这个参数是指定并行 GC 线程的数量,一般最好和 CPU 核心数量相当。默认情况下,当 CPU 数量小于8, ParallelGCThreads 的值等于 CPU 数量,当 CPU 数量大于 8 时,则使用公式:ParallelGCThreads = 8 + ((N - 8) * 5/8) = 3 +((5*CPU)/ 8);同时这个参数只要是并行 GC 都可以使用,不只是 ParNew。

由于GC操作会暂停所有的应用程序线程,JVM为了尽量缩短停顿时间就必须尽可能地利用更多的CPU资源。这意味着,默认情况下,JVM会在机器的每个CPU上运行一个线程,最多同时运行8个。一旦达到这个上限,JVM会调整算法,每超出5/8个CPU启动一个新的线程。所以总的线程数就是(这里的N代表CPU的数目):ParallelGCThreads = 8 + ((N - 8) * 5/8) 有时候使用这个算法估算出来的线程数目会偏大。如果应用程序使用一个较小的堆(譬如大小为1 GB)运行在一个八颗CPU的机器上,使用4个线程或者6个线程处理这个堆可能会更高效。在一个128颗CPU的机器上,启动83个垃圾收集线程可能也太多了,除非系统使用的堆已经达到了最大上限。

相关推荐
kaikai_sk2 小时前
JVM之内存模型
jvm
Super Minion3 小时前
Java重修笔记 第五十四天 坦克大战(四)多线程基础
java·jvm·笔记
customer084 小时前
【开源免费】基于SpringBoot+Vue.JS在线视频教育平台(JAVA毕业设计)
java·jvm·vue.js·spring boot·spring cloud·java-ee·intellij-idea
羊十一4 小时前
C++(继承/虚函数)
jvm·c++
程序员清风5 小时前
JVM面试真题总结(七)
jvm·面试·职场和发展
Ty_11065 小时前
深入理解Java虚拟机:Jvm总结-Java内存区域与内存溢出异常
java·开发语言·jvm
Flying_Fish_roe5 小时前
JVM虚引用
jvm
ZachOn1y5 小时前
Java 入门指南:JVM(Java虚拟机)—— Java 类文件结构
java·开发语言·jvm·后端·java-ee·个人开发
m0_689087076 小时前
9.10 SQLITE3数据库
jvm·数据库·sqlite
得食猫9 小时前
JVM垃圾回收
java·jvm·gc