博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何快速定位JVM相关GC问题
阅读量:2165 次
发布时间:2019-05-01

本文共 5684 字,大约阅读时间需要 18 分钟。

1.涉及问题

  • 如何使用jstat 命令查看jvm的GC情况
  • 面对海量GC日志,如何快速抓住问题根源- 如何使用日志分析工具
  • 对代码优化、扩容、参数调优、内存估算,都需要一些支撑信息加以判断
    GC与代码优化、扩容、估算、参数调优的关系

GC 日志输出

GC版本差异

  • java版本更新速度很快,jvm参数配置变化较大,如java9几乎是推翻重来。追踪、定位GC日志之前需确认java版本- 事故出现时间不定,需要保留事故出现现场,可通过设置一些JVM参数进行保留相关信息

java8

  • jdk8 具体使用例子
    #! /bin/sh# 使用将下面添加注释去掉(markdown语法需要转义#,在使用时需要将转义符去掉)# GC 日志存放目录LOG_DIR=”/tmp/logs” # 打印GC日志JAVA_OPT_LOG=”-verbose:gc” # 打印GC详细信息JAVA_OPT_LOG=”${JAVA_OPT_LOG} -XX:+PrintGCDetails” # 打印GC日志带系统时间JAVA_OPT_LOG=”${JAVA_OPT_LOG} -XX:+PrintGCDateStamps” # 打印应用停顿(STW)时间JAVA_OPT_LOG=”${JAVA_OPT_LOG} -XX:PrintGCApplicationStoppedTime”# 打印对象年龄分布,对调优MaxTenuringThreshold参数帮助很大JAVA_OPT_LOG=”${JAVA_OPT_LOG} -XX:+PrintTenuringDistribution” #将以上GC内容输出到文件中,并指定gc日志文件的格式JAVA_OPT_LOG=”${JAVA_OPT_LOG} -Xloggc:${LOG_DIR}/gc_%p.log” # 指定堆内存溢出相关的日志存储目录,Dump文件保存路径,历史错误日志文件路径及命名格式JAVA_OPT_OOM=” -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOG_DIR} -XX:ErrorFile=${LOG_DIR}/hs_error_pid%p.log” JAVA_OPT=”${JAVA_OPT_LOG} ${JAVA_OPT_OOM}” # JVM缩简日志输出JAVA_OPT=”${JAVA_OPT} -XX:-OmitStackTraceInFastThrow”

java13

  • java9 开始,移除40个GC日志相关参数,具体见JEP 158.java13实例如下
    #! /bin/sh # GC日志存储父目录LOG_DIR=”/tmp/logs”   # 打印GC日志JAVA_OPT_LOG=” -verbose:gc”#将GC 引用、堆相关的debug日志输出到日志文件		   JAVA_OPT_LOG=”${JAVA_OPT_LOG} -Xlog:gc,gc+ref=debug,gc+heap=debug,gc+age=trace:file=${LOG_DIR}/gc_%p.log:tags,uptime,time,level” # 将jvm安全点(用于锁降级机制)相关信息输出到日志文件JAVA_OPT_LOG=”${JAVA_OPT_LOG} -Xlog:safepoint:file=${LOG_DIR}/safepoint_%p.log:tags,uptime,time,level” # 打印OOM信息,指定Dump日志文件路径,错误日志文件JAVA_OPT_OOM=” -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOG_DIR} -XX:ErrorFile=${LOG_DIR}/hs_error_pid%p.log” # 配置JAVA_OPT_LOG及JAVA_OPT_OOMJAVA_OPT=”${JAVA_OPT_LOG} ${JAVA_OPT_OOM}” # 对jvm输出日志进行缩简JAVA_OPT=”${JAVA_OPT} -XX:-OmitStackTraceInFastThrow” # 打印jvm的最终配置参数Echo $JAVA_OPT
  • java13除了GC日志,还输出了safepoint日志(可以安全地暂停线程的点,用来实现锁的降级),当GC发生时,用户线程必须全部停止,才可以进行垃圾回收,我们称此为JVM是安全的(safe),整个堆是稳定的。
    jvm-safepoint
  • 如果在GC之前,有线程迟迟无法进入safepoint,那么整个JVM都在等待这个阻塞(blocked)线程,进而会造成整体GC的实际变长- 因此,在GC进行之前无法进入safepoint也会挂起JVM(GC也会挂起JVM)。
  • 因此,一般项目中会输出详细GC日志,并加上可读性强的GC日志时间戳,特别情况下会加一些反映对象分布,晋升情况和堆详细信息,用来排查问题。同样,设置OOM时自动Dump堆栈

GC 日志的意义

GC日志参数及数值解析

  • 具体例子

    GC日志样例

  • 上述例子解析

    • 步骤1,表示GC发生时机,一般使用可读方式打印
    • 步骤2,表示是G1的“转移暂停:混合模式”,停顿月223ms
    • 步骤3,表示由8个worker线程并行执行,消耗214ms
    • 步骤4,表示Diff越小越好,越说明每个worker线程速度都很均匀
    • 步骤5,表示外部根区扫描,外部根是堆外区。JNI(Java Native Interface)引用,JVM系统目录,ClassLoader等
    • 步骤6,表示更新RememberedSet的时间信息
    • 步骤7,表示该任务主要是对CSet中存活对象进行赋值
    • 步骤8,表示花在GC之外的工作线程的时间
    • 步骤9,表示并行阶段的GC总时间
    • 步骤10,表示其他清理活动
    • 步骤11,表示收集结果统计
    • 步骤12,表示时间花费统计

分析工具

  • 即使理解日志中上述每个阶段每个数值意义,分析问题时,面对大量日志信息也会吃力,需要使用图形化分析工具进行分析
  • time ls / 进行三种维度的时间统计
    time ls例子
    • real,实际花费时间,从开始到结束花费时间。如进行等待I/O完成,阻塞时间也会计入
    • user,进程在用户态(user Mode) 花费时间,只统计本进程所使用时间,是指多核
    • sys,进程内核态(kernel Mode)花费的CPU时间,指内核中系统调用花费时间,只统计本进程使用时间
    • 对于并行垃圾收集器,如上述GC日志,real < user + sys,因为我们使用多核垃圾收集,所以实际发生实际发生时间比(user+sys)少很多,这种情况多核机器很常见
    • 对于串行垃圾收集器,GC时始终仅使用一个线程,故 real = user+sys
    • 一般来说,用户关心系统停顿多久,对世界的影响时间非常关心,即real时间

GC 日志可视化

  • gceasy
    • 1.查看堆内存信息
      堆内存信息
    • 2.关键信息,如吞吐量98.6%(一般超过95%就行),最大延迟:230ms,平均延迟42.8ms,延迟看服务接收程度(如SLA定义为50ms,则最大延迟有问题),下图服务接近99%的停顿在100ms以下,运行状态优秀。
      gceasy查看关键信息
    • 3.看这些信息,一定结合宿主服务器监控去看,如GC发生期间,CPU会发生尖峰,就证明GC对CPU资源使用的有点多。多数情况下,如果吞吐和延迟在可接受范围内,这些对CPU的超额使用可接受(毕竟要极力压榨机器资源)
    • 4.交互式图表,对问题区域放大查看,图中表示垃圾回收后空间释放,可以看到效果比较好
      gceasy查看交互式图表
    • 5.G1的时间耗时,下图展示每个阶段花费时间。看到平均耗时最长阶段是Concurrent Mark阶段,由于是并发的,影响不大。随着时间推移,YoungGC竟然达到136485次,运行5天,花在GC时间的就2小时,比较可观。
      gceasy查看GC耗时
    • 6.其他,如下图,整个JVM创建100多T的数据,其中2.4TB被提升(promoted)到老年代,说明年轻代生命周期较久。另外还有safepoint的信息
      gceasy查看safepoint相关信息
    • 7.有GC问题的图表样例
      • 停顿时间明显超长的GC问题
        gceasy查看停顿时间超长
      • 内存泄漏
        gceasy查看内存泄漏例子
      • 上面问题都很明显。但大多数情况是,问题是偶发的。从基本衡量指标,就能考量整体的服务水准。如果这些没问题,就要看曲线的尖峰。
      • 一般来说,任何不平滑的曲线,都值得怀疑,那就需要看一下当时业务的具体情况。是用户请求突增引起,还是执行了批量定时任务,再或者查询了大批量数据,需要和一些服务监控一起才能定位出根本问题
    • 只靠GC来定位问题比较困难,我们只需要知道GC有问题就可以了。
    • jstat
      • gceasy等可视化工具,必须经历导出、上传、分析三个阶段,速度慢,无法向jstat进行实时查看堆内存- jstat输出例子,使用jstat -gcutil $pid 1000
    S0 S1 E O M CCS YGC YGCT FGC FGCT GCT	   0.00 0.00 72.03 0.35 54.12 55.72 11122 16.019 0 0.000 16.0190.00 	   0.00 95.39 0.35 54.12 55.72 11123 16.024 0 0.000 16.0240.00 	   0.00 25.32 0.35 54.12 55.72 11125 16.025 0 0.000 16.0250.00 	   0.00 37.00 0.35 54.12 55.72 11126 16.028 0 0.000 16.0280.00 	   0.00 60.35 0.35 54.12 55.72 11127 16.028 0 0.000 16.028           说明:S0: 年轻代的survivor 0           	S1: 年轻代的survivor 1           	O: 年老代           	M: MetaSpacke,即元空间(之前的方法区)           	YGC:  年轻代回收次数           	YGCT: 年轻代回收耗时           	FGC:  FullGC回收次数           	FGCT:  FullGC回收耗时
    -gcutil位置的参数可有多种,最常见有gc,gcutil,gccause,gcnew
    jstat -gc $pid 1000 : 显示和GC相关堆信息jstat -gcutil $pid 1000 : 显示垃圾回收信息jstat -gccause $pid 1000 : 显示垃圾回收相关信息(同 -gcutil),同时显示最后一次或当前发生垃圾回收的诱因jstat -gcnew $pid 1000 : 显示新生代信息jstat -gccapacity $pid 1000 : 显示各个代的容量即使用情况jstat -gcmetacapacity $pid 1000 : 显示元空间metaspace的大小jstat -gcnewcapacity $pid 1000 : 显示新生代大小和使用情况jstat -gcold $pid 1000 : 显示老年代和永久代信息jstat -gcoldcapacity $pid 1000 : 显示老年代大小jstat -printcompilation $pid 1000 : 输出JIT编译的方法信息jstat -class $pid 1000 : 显示类加载器classloader相关信息jstat -compiler $pid 1000 : 显示JIT编译相关信息
  • 如果GC问题特别明显,通过jstat快速发现。我们可知启动命令行中加上参数-t (time),可以输出程序从启动到现在的时间。如果FGC和启动时间比值太大,说明系统吞吐量较小,GC花费时间太多。另外,如果老年代Full GC之后,没有内存占用明显下降,可能是内存达到瓶颈,或发生内存泄漏。如下面例子,追加GC时间增量和GC时间比率两列
    Jstat -gcutil -t 90542 1000 |awk ‘begin {pre=0} {if(NR>1) {print $0 “\t”  ($12-pre) “\t” $12*100/$1 ; pre=$12 } else {print $0 “\tGCT_INC\tRate”} }’Timestamp	S0	S1	E	O	M	CCS	YGC	YGCT	FGC	FGC	TGC	TGC_INC Rate 18.7	0.00	100.00	6.02	1.45	84.81	76.09	1	0.002	0	0.000	0.0020	0.010695219.7	0.00	100.00	6.02	1.45	84.81	76.09	1	0.002	0	0.000	0.0020	0.0101523 	````

GC日志也会搞鬼

  • 搞鬼例子,ES 插入、查询数据非常快。为压榨其性能,对磁盘读写几乎全速。它在后台对批次提交的数据做了很多Merge动作,将小块所有合并成大块索引。还有TransLog等预写动作,都是I/O大户
  • 解决思路
    1. 问题:其中一套ES机器,在访问高峰,多个ES节点发生严重STW问题,有点节点停顿了7~8s。用户态420ms,真实停顿7.62s [Times: user=0.42 sys=0.03, real=7.62 secs] 2.使用iostat -x 1 看到具体I/O使用情况3.盘点资源后,唯一超额利用就是I/O资源(%util保持在90以上),GC可能在等I/O4.通过搜索,发现有类似问题,原因:写GC日志的write动作,是统计在STW的时间中,我们场景中,由于ES索引数据,和GC放置一个磁盘,GC时写日志的动作,就和写数据文件发生资源争用。解决:将ES日志文件,单独放到一块普通HDD磁盘

es例子

转载地址:http://bhjzb.baihongyu.com/

你可能感兴趣的文章
用 RNN 训练语言模型生成文本
查看>>
RNN与机器翻译
查看>>
用 Recursive Neural Networks 得到分析树
查看>>
RNN的高级应用
查看>>
TensorFlow-7-TensorBoard Embedding可视化
查看>>
轻松看懂机器学习十大常用算法
查看>>
一个框架解决几乎所有机器学习问题
查看>>
特征工程怎么做
查看>>
机器学习算法应用中常用技巧-1
查看>>
机器学习算法应用中常用技巧-2
查看>>
通过一个kaggle实例学习解决机器学习问题
查看>>
决策树的python实现
查看>>
Sklearn 快速入门
查看>>
了解 Sklearn 的数据集
查看>>
用ARIMA模型做需求预测
查看>>
推荐系统
查看>>
TensorFlow-11-策略网络
查看>>
浅谈 GBDT
查看>>
如何选择优化器 optimizer
查看>>
一文了解强化学习
查看>>