CPU、内存打满排查流程、JVMGC调优

This is my, my, my, beautiful day!
 
一、问题确认  
1、top 命令查看各个进程cpu、内存使用量
  1. top     //每隔5秒显式所有进程的资源占用情况
  2. top -d 2  //每隔2秒显式所有进程的资源占用情况
  3. top -c  //每隔5秒显式进程的资源占用情况,并显示进程的命令行参数(默认只有进程名)
  4. top -p 12345 -p 6789//每隔5秒显示pid是12345和pid是6789的两个进程的资源占用情况
  5. top -d 2 -c -p 123456 //每隔2秒显示pid是12345的进程的资源使用情况,并显式该进程启动的命令行参数
2、示例: top  -d 2 -c -p 8338
二、CPU打满问题排查
对于后台开发来说cpu打满就是看自己写的那段代码是不是有问题,要定位具体线程、代码,废话少说下面开始……
1、排查过程
  1. 查找进程id: top 
  2. 查找进程中耗cpu比较高的线程id: top -p pid -H (top -Hp pid)
  3. 线程id十进制转十六进制: printf %x 线程id 
  4. 通过栈信息定位具体代码: jstack -pid | grep -A 20  线程id
 2、示例
  1. top -c
  1.   top -p 4247 -H
  1. Printf %x 4273
  1. 定位具体代码
这里定位到是虚拟机的一个定时任务线程,线程状态是等待条件发生
tid :  Java memory address of its internal Thread control structure
nid :   native thread id. 每一个nid对应于linux下的一个tid, 即lwp  (light weight process, or thread).
  1. 在查一个进程的线程
Concurrent-Mark-Sweep GC Thread : BackGround CMS 即周期性执行的CMS垃圾收集线程。JVM中默认2秒执行一次。
Parallel CMS Threads:   CMS默认启动的回收线程数目。
如果业务代码比较耗cpu则会在这里找到具体类方法以及行数……

三、内存打满问题排查

1、排查过程

  1. 查找进程id:top -d 2 -c 
  2. 查看JVM堆内存分配情况:jmap -heap pid 
  3. 查看占用内存比较多的对象 jmap -histo pid | head -n 100 ,查看占用内存比较多的存活对象 jmap -histo:live pid | head -n 100 

2、示例

  1. top -d 2 -c 

  1. jmap -heap 8338

  1. 定位占用内存比价多的对象

这里就能看到对象个数以及对象大小……

这里看到一个自定义的类,这样我们就定位到具体对象,看看这个对象在那些地方有使用、为何会有大量的对象存在……

四、JVM内存、CPU、线程分析工具推荐

     如果在本地则可以使用JDK提供的工具来查看jvm相关信息比如jconsole、jvisualvm 等能更直观的查看JVM信息……最好用的貌似是MAT……

1、MAT 是使用比较多的,可以安装eclipse 安装插件来使用……

2、jvisualvm : 红色括号需要安装插件…… 可以分析线程dump、内存dump、应用dump 感觉够用

3、jconsole

五、使用jvisualvm工具定位消耗过高cpu、内存问题

项目频繁YGC 、FGC问题排查

1、内存问题

  1. 对象内存占用、实例个数监控

  1. 对象内存占用、年龄值监控

通过上面两张图发现这些对象占用内存比较大而且存活时间也是比较常,所以survivor 中的空间被这些对象占用,而如果缓存再次刷新则会创建同样大小对象来替换老数据,这时发现eden内存空间不足,就会触发yonggc 如果yonggc 结束后发现eden空间还是不够则会直接放到老年代,所以这样就产生了大对象的提前晋升,导致fgc增加……

优化办法:优化两个缓存对象,将缓存对象大小减小。优化一下两个对象,缓存关键信息!

2、cpu耗时问题排查

  1. Cpu使用耗时监控

  1. 耗时、调用次数监控

从上面监控图可以看到主要耗时还是在网络请求,没有看到具体业务代码消耗过错cpu……

六、JVM CMS 垃圾收集器参数配置
1、test、stage 环境jvm使用CMS 参数配置(jdk8)
    
-server -Xms256M -Xmx256M -Xss512k -Xmn96M -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M -XX:InitialHeapSize=256M -XX:MaxHeapSize=256M  -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=80 -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=2 -XX:+CMSParallelInitialMarkEnabled -XX:+CMSParallelRemarkEnabled -XX:+UnlockDiagnosticVMOptions -XX:+ParallelRefProcEnabled -XX:+AlwaysPreTouch -XX:MaxTenuringThreshold=8  -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+PrintGC -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC  -XX:+PrintTenuringDistribution  -XX:SurvivorRatio=8 -Xloggc:../logs/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=../dump
2、online 环境jvm使用CMS参数配置(jdk8)
    -server -Xms4G -Xmx4G -Xss512k  -Xmn1536M -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M -XX:InitialHeapSize=4G -XX:MaxHeapSize=4G  -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=80 -XX:+CMSClassUnloadingEnabled -XX:+CMSParallelRemarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=2 -XX:+CMSParallelInitialMarkEnabled -XX:+CMSParallelRemarkEnabled -XX:+UnlockDiagnosticVMOptions -XX:+ParallelRefProcEnabled -XX:+AlwaysPreTouch -XX:MaxTenuringThreshold=10  -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+PrintGC -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC  -XX:+PrintTenuringDistribution  -XX:SurvivorRatio=8 -Xloggc:../logs/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=../dump
备注:jvm 参数按照具体服务具体配置大小……
七、JVM G1 垃圾收集器参数配置
后续补充……
八、垃圾收集调优基础
1、性能属性
  1. 吞吐量:是评价垃圾收集器能力的重要指标之一,是不考虑垃圾收集引起的停顿时间或内存消耗,垃圾收集器能支撑应用程序达到的最高性能指标。
  2. 延迟:评价垃圾收集器能力的重要指标,度量标准是缩短由于垃圾收集引起的停顿时间或完全消除因垃圾收集所引起的停顿,避免应用程序运行时发生抖动。
  3. 内存占用: 垃圾收集器流畅运行所需要的内存数量……
2、原则
  1.  MinorGC尽可能多的收集垃圾对象。我们把这个称作MinorGC原则,遵守这一原则可以降低应用程序FullGC 的发生频率。FullGC 较耗时,是应用程序无法达到延迟要求或吞吐量的罪魁祸首。
  2. 处理吞吐量和延迟问题时,垃圾处理器能使用的内存越大,即java堆空间越大垃圾收集效果越好,应用运行也越流畅。这称之为GC内存最大化原则。
  3. 在这三个属性(吞吐量、延迟、内存)中选择其中两个进行jvm调优。称之为GC调优3选2。
九、JVM GC调优堆内存分配
1、初始堆空间大小设置
  1. 使用系统默认配置在系统稳定运行一段时间后查看记录内存使用情况:Eden、survivor0 、survivor1 、old、metaspace
  2. 按照通用法则通过gc信息分配调整大小,整个堆大小是Full GC后老年代空间占用大小的3-4倍
  3. 老年代大小为Full GC后老年代空间占用大小的2-3倍
  4. 新生代大小为Full GC后老年代空间占用大小的1-1.5倍 
  5. 元数据空间大小为Full GC后元数据空间占用大小的1.2-15.倍
  6. 按照这个通用法则先粗略调整各个内存大小……
  7. 活跃数大小是应用程序运行在稳定态时,长期存活的对象在java堆中占用的空间大小。也就是在应用趋于稳太时FullGC之后Java堆中存活对象占用空间大小。(注意在jdk8中将jdk7中的永久代改为元数据区,metaspace 使用的物理内存,不占用堆内存)
2、堆大小调整的着手点、分析点
  1. 统计Minor GC 持续时间
  2. 统计Minor GC 的次数
  3. 统计Full GC的最长持续时间
  4. 统计最差情况下Full GC频率
  5. 统计GC持续时间和频率对优化堆的大小是主要着手点,我们按照业务系统对延迟和吞吐量的需求,在按照这些分析我们可以进行各个区大小的调整……
十、年轻代调优
1、年轻带调优
MinorGC是收集eden+from survivor 区域的,当业务系统匀速生成对象的时候如果年轻带分配内存偏小会发生频繁的MinorGC,如果分配内存过大则会导致MinorGC停顿时间过长,无法满足业务延迟性要求。所以按照堆分配空间分配之后分析gc日志,看看MinorGC的频率和停顿时间是否满足业务要求。
  1. MinorGC频繁原因:
        MinorGC 比较频繁说明eden内存分配过小,在恒定的对象产出的情况下很快无空闲空间来存放新对象所以产生了MinorGC,所以eden区间需要调大。
  1. 年轻代大小调整:
        Eden调整到多大那,我们可以查看GC日志查看业务多长时间填满了eden空间,然后按照业务能承受的收集频率来计算eden空间大小。比如eden空间大小为128M每5秒收集一次,则我们为了达到10秒收集一次则可以调大eden空间为256M这样能降低收集频率。年轻代调大的同时相应的也要调大老年代,否则有可能会出现频繁的concurrent model  failed  从而导致Full GC 。
  1. MinorGC停顿时间过长
        MinorGC 收集过程是要产生STW的。如果年轻代空间太大,则gc收集时耗时比较大,所以我们按业务对停顿的要求减少内存,比如现在一次MinorGC 耗时12.8毫秒,eden内存大小192M ,我们为了减少MinorGC 耗时我们要减少内存。比如我们MinorGC 耗时标准为10毫秒,这样耗时减少16.6% 同样年轻代内存也要减少16.6% 即192*0.1661 = 31.89M 。年轻代内存为192-31.89=160.11M,在减少年轻代大小,而要保持老年代大小不变则要减少堆内存大小至512-31.89=480.11M
堆内存:512M 年轻代: 192M  收集11次耗时141毫秒 12.82毫秒/次
堆内存:512M 年轻代:192M 收集12次耗时151毫秒   12.85毫秒/次
按照上面计算调优
堆内存: 480M 年轻带: 160M 收集14次 耗时154毫秒  11毫秒/次 相比之前的 12.82毫秒/次 停顿时间减少1.82毫秒
但是还没达到10毫秒的要求,继续按照这样的逻辑进行  11-10=1 ;1/11= 0.909 即 0.09 所以耗时还要降低9% 
年轻代减少:160*0.09 = 14.545=14.55 M;  160-14.55 =145.45=145M 
堆大小: 480-14.55 = 465.45=465M
但是在这样调整后使用jmap -heap 查看的时候年轻代大小和实际配置数据有出入(年轻代大小为150M大于配置的145M),这是因为-XX:NewRatio 默认2 即年轻代和老年代1:2的关系,所以这里将-XX:NewRatio 设置为3 即年轻代、老年大小比为1:3 ,最终堆内存大小为:
MinorGC耗时 159/16=9.93毫秒
MinorGC耗时 185/18=10.277=10.28毫秒
MinorGC耗时 205/20=10.25毫秒
Ok 这样MinorGC停顿时间过长问题解决,MinorGC要么比较频繁要么停顿时间比较长,解决这个问题就是调整年轻代大小,但是调整的时候还是要遵守这些规则。
2、年轻代调优规则
  1. 老年代空间大小不应该小于活跃数大小1.5倍。老年代空间大小应为老年代活跃数2-3倍。
  2. 新生代空间至少为java堆内存大小的10% 。新生代空间大小应为1-1.5倍的老年代活跃数。
  3. 在调小年轻代空间时应保持老年代空间不变。
  4. gc 配置参数: -server -Xms460M -Xmx460M -Xss512k -Xmn140M -XX:MetaspaceSize=460M -XX:MaxMetaspaceSize=256M -XX:InitialHeapSize=460M -XX:MaxHeapSize=460M -XX:NewRatio=3    堆大小无关数据省略……
十一、 老年代调优
在年轻代调优结束后,按照同样的思路对老年代进行调优,同样分析FullGC 频率和停顿时间,按照优化设定的目标进行老年代大小调整
1、老年代调优流程
  1. 分析每次MinorGC 之后老年代空间占用变化,计算每次MinorGC之后晋升到老年代的对象大小。
  2. 按照MinorGC频率和晋升老年代对象大小计算提升率即每秒钟能有多少对象晋升到老年代。
  3. FullGC之后统计老年代空间被占用大小计算老年带空闲空间,再按照第2部计算的晋升率计算该老年代空闲空间多久会被填满而再次发生FullGC,同样观察FullGC 日志信息,计算FullGC频率,如果频率过高则可以增大老年代空间大小老解决,增大老年代空间大小应保持年轻代空间大小不变
  4. 如果在FullGC 频率满足优化目标而停顿时间比较长的情况下可以考虑使用CMS、G1收集器。并发收集减少停顿时间
十二、CMS垃圾收集器调优
1、survivor  调优 
  1. 年轻代组成
         在JVM各种垃圾收集器中年轻代都是Eden+survivor from +survivor to  这样一个结构组成
  1. survivor to 区过小导致的问题
        survivor 中保存Minor GC之后存活并且年龄未达到阀值的对象,如果survivor 过小则会产生survivor溢出现象,导致年轻对象过早提升到老年代从而引发Stop-The-Word 压缩式FullGC,导致GC延时过长,影响响应时长和吞吐量。
  1. survivor to 区调到合适大小的好处
         避免survivor 溢出,达到对象老龄化目标,老年代保存正真长期存活对象,减少老年代空间碎片,减少FullGC。
  1. survivor大小应该设置为多少
      在CMS垃圾收集器中默认使用survivor50% 的空间。
(1)、设置-XX:+PrintTenuringDistribution ,GC日志输出所有对象年龄的总大小以及目标生存空间大小。 
Desired survivor size 107347968 bytes , new threshold 1 (max 15 )
- age   1 :  157548032 bytes ,  157548032 total
: 1677824K -> 154017K ( 1887488K ) , 0.1942010 secs ] 1677824K -> 154017K ( 3984640K ) , 0.1942870 secs ] [ Times : user = 0.20 sys = 0.08 , real = 0.19 secs ]
年龄:1 年龄为1的存活对象:157548032 byte 即150 M 
当前survivor大小: 107347968 bytes 即 102 M
Survivor = total /50% = 150/50% = 300M 所以Survivor 大小设置为300M比较合适。
(2)、使用总存活对象大小作为Survivor 空间大小
 程序运行稳定之后监控每次MinorGC之后survivor to大小和晋升到老年代大小:survivor to+晋升大小
  1. 设置Survivor 大小
    按照4中计算大小设置Survivor 大小,但是注意在设置survivor大小的时候不能改变Eden 大小,如果Eden变小则会导致MinorGC频繁。Eden变大则会导致MinorGC 停顿过长。
年轻代= eden+survivor from +survivor to 
survivor = (eden+ (2* survivor from )) /(survivorRatio +2) = 年轻代/(survivorRatio +2 )  ;保持eden不变则调小survivorRatio 即单个survivor在年轻代中所占比例。
2、晋升阀值 -XX:MaxTenuringThreshold 调优
1、MaxTenuringThreshold 年龄阀值过大
    在survivor 调优的时候说过,如果survivor 区空间大小过小会导致survivor 溢出。同样 MaxTenuringThreshold 值如果过大则会存在很多老对象在survivor from 和survivor to  之间来回复制,第一会导致复制量比较大,第二占用大量survivor空间导致survivor溢出,年龄阀值失效。
2、MaxTenuringThreshold 年龄阀值过小
    年龄阀值过小会导致eden区的对象会陆续送入old区,对象移动本身就是开销,cms老年代回收还会造成碎片化。
   
阀值大小设置可以观察  -XX:+PrintTenuringDistribution打印信息,当age增加而存活对象数没有大幅增加的时候说明这些对象是产期存活对象,为了验证可以观察几次向老年代的晋升,如果趋于稳定的age年龄段的存活对象大小和晋升大小吻合则说明这些对象确实是长期存活对象,这样MaxTenuringThreshold 设置为存活对象趋于稳定的年龄……
3、old调优
1、老年代垃圾收集器CMS两种运行模式:
  BackGround CMS(也有叫周期性执行) 和ForeGround CMS(主动执行) 两种模式,调优的目的就是减少ForeGround CMS,所以我们这里调优是调的BackGround CMS。
2、BackGround CMS垃圾收集线程运行(Concurrent-Mark-Sweep GC Thread)
 BackGround CMS 周期性执行是JVM启动线程周期性来执行CMS垃圾收集,这个周期默认2秒执行一次,但是在执行时不一定进行垃圾收集操作,因为我们有配置老年代空间占有率。只有达到这个阀值才会进行老年代垃圾收集。
执行流程:
3、垃圾收集条件设置
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+CMSInitiatingOccupancyFraction=80
两个配置参数一起使用, UseCMSInitiatingOccupancyOnly 一直使用用户配置的老年代空间占有率阀值,如果不配置这个参数则只有第一次使用用户配置占有率阀值,后期使用jvm自己统计阀值。CMSInitiatingOccupancyFraction设置老年代空间占有率。
4、老年代空间占有率阀值设置过小有什么问题
老年代空间占有率阀值设置过小,会导致BackGround CMS频繁执行而执行过程回收空间很小,所以浪费资源。更极端的情况下,如果设置阀值小于应用程序的活跃数据(Full GC 之后堆内存占用)大小,则BackGround CMS会陷入死循环……
5、老年代空间占有率阀值设置过大有什么问题
阀值过大则会产生“失速”现象,产生大量 concurrent model failed  从而导致大量Full GC ,而Full GC相比CMS或paraNew 是缓慢的。所以会产生严重的延时问题和吞吐量问题……
“失速” : 在BackGround CMS运行期间,并发执行的应用程序生产新对象晋升到老年代,但是老年代没有足够的内存或没有足够的连续内存(碎片问题)从而导致concurrent model failed 。也就是说垃圾收集过晚,导致新晋升的对象无空间存放。
6、老年代空间占有率阀值确定
按照通用阀值为引用程序的活跃数据大小的1.5倍。
-Xmx1536M -Xms1536M -Xmn 512 ,如果应用程序的活跃数大小为350M,则按照通用法则计算如下:
老年代大小= 1536 - 512 = 1024 M
通用阀值= 350*1.5 = 525 M ,老年代空间沿用达到525M 开始执行BackGround CMS . 
占有率阀值= 525/1024= 51.3%
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+CMSInitiatingOccupancyFraction=51
十三、个人公众号
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值