JVM优化分析笔记

 1.前言

 我们的Java应用一般会配置JVM参数,比如GC策略,Java堆大小等等,当系统遇到问题是,我们可以通过jmap查看堆内对象示例的统计信息,通过jstack命令可以继续查看该线程当前的堆栈状态,通过 jstat命令可以查看堆内存各部分的使用量,以及加载类的数量等各种指标,通过JVisualVM(命令jvisualvm)来查库JVM的运行信息包括线程和内存使用情况,强烈推荐用工具Java Mission Control(,还可以设置参数打印GC日志,-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/export/Logs/jvm_gc.log,下面分析一个系统的GC日志,环境是Young GC:ParNew,Full GC:ConcurrentMarkSweep,

  2.Young GC日志:

35.421: [GC (Allocation Failure) 35.421: 
[ParNew: 4369059K->198269K(4718592K), 0.2634503 secs] 
4446544K->275754K(7766016K), 0.2640093 secs] 
[Times: user=0.68 sys=0.29, real=0.27 secs] 

 3.Young GC日志分析:

1.35.421 –GC事件开始时,相对于JVM启动时的间隔时间,单位是秒。
2. GC – 用来区分 Minor GC 还是 Full GC 的标志。 GC 表明这是一次 GC(Minor GC)
3. AllocationFailure –触发垃圾收集的原因。本次GC事件,是由于年轻代中没有适当的空间存放新的 数据结构引起的。
4. ParNew –垃圾收集器的名称。这个名字表示的是在年轻代中使用的:并行的标记­复制(mark­copy),全 线暂停(STW)垃圾收集器, 专门设计了用来配合老年代使用的 Concurrent Mark & Sweep 垃圾收集器。
5. 4369059K‐>198269K –在垃圾收集之前和之后的年轻代使用量。
6. (4718592K) – 年轻代的总大小。
7. 0.2634503 secs – 垃圾收集器在 w/o final cleanup 阶段消耗的时间 
8. 4446544K‐>275754K –在垃圾收集之前和之后堆内存的使用情况。
9. (7766016K) –可用堆的总大小。
10. 0.2640093 secs – 垃圾收集器在标记和复制年轻代存活对象时所消耗的时间。包括和
ConcurrentMarkSweep 收集器的通信开销, 提升存活时间达标的对象到老年代,以及垃圾收集后期的一些
最终清理。
11. [Times: user=0.68 sys=0.29, real=0.27 secs] – GC 事件的持续时间, 通过三个部分来衡量:
user – 在此次垃圾回收过程中, 由 GC 线程所消耗的总的 CPU 时间。
sys – GC过程中中操作系统调用和系统等待事件所消耗的时间。
real – 应用程序暂停的时间。在并行 GC(Parallel GC)中, 这个数字约等于: (user time + system time)/GC 线程数

4.Full GC日志:

14.718: [GC (CMS Initial Mark) [1 CMS-initial-mark: 0K(3047424K)] 3691278K(7766016K), 0.3509810 secs] [Times: user=1.34 sys=0.45, real=0.35 secs] 
15.069: [CMS-concurrent-mark-start]
15.081: [CMS-concurrent-mark: 0.012/0.012 secs] [Times: user=0.04 sys=0.02, real=0.01 secs] 
15.081: [CMS-concurrent-preclean-start]
15.087: [CMS-concurrent-preclean: 0.006/0.006 secs] [Times: user=0.04 sys=0.01, real=0.01 secs] 
15.087: [CMS-concurrent-abortable-preclean-start]
16.181: [GC (Allocation Failure) 16.181: [ParNew16.281: [CMS-concurrent-abortable-preclean: 0.067/1.194 secs] [Times: user=3.73 sys=0.22, real=1.19 secs] 
: 4194304K->313911K(4718592K), 0.5798048 secs] 4194304K->313911K(7766016K), 0.5799021 secs] [Times: user=1.31 sys=0.43, real=0.58 secs] 
16.761: [GC (CMS Final Remark) [YG occupancy: 397798 K (4718592 K)]16.761: [Rescan (parallel) , 0.0352362 secs]16.797: [weak refs processing, 0.0000208 secs]16.797: [class unloading, 0.0082855 secs]16.805: [scrub symbol table, 0.0049771 secs]16.810: [scrub string table, 0.0007627 secs][1 CMS-remark: 0K(3047424K)] 397798K(7766016K), 0.0510646 secs] [Times: user=0.34 sys=0.01, real=0.06 secs] 
16.812: [CMS-concurrent-sweep-start]
16.812: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
16.812: [CMS-concurrent-reset-start]
16.831: [CMS-concurrent-reset: 0.016/0.018 secs] [Times: user=0.17 sys=0.01, real=0.01 secs] 

5.Full GC日志分析

5.1 初始化标记(CMS Initial Mark)

  1. CMS Initial Mark – 垃圾回收的阶段名称为 “Initial Mark”。 标记所有的 GC Root。
  2. 0K –老年代的当前使用量。
  3. (3047424K) –老年代中可用内存总量。
  4. 3691278K –当前堆内存的使用量。
  5. (7766016K) –可用堆的总大小。
  6. 0.3509810 secs] [Times: user=1.34 sys=0.45, real=0.35 secs] – 此次暂停的持续时间, 以 user,system 和 real time 3 个部分进行衡量。

5.2 并发标记 (Concurrent Mark)

1. CMS‐concurrent‐mark –并发标记("ConcurrentMark")是CMS垃圾收集中的一个阶段,遍历老年代并标 记所有的存活对象。
2. 0.012/0.012 secs – 此阶段的持续时间, 分别是运行时间和相应的实际时间。
3. [Times: user=0.04 sys=0.02, real=0.01 secs] – Times 这部分对并发阶段来说没多少意义, 因为是
从并发标记开始时计算的,而这段时间内不仅并发标记在运行,程序也在运行

5.3 并发预清理(Concurrent Preclean)

1. CMS‐concurrent‐preclean – 并发预清理阶段, 统计此前的标记阶段中发生了改变的对象。
2. 0.006/0.006 secs – 此阶段的持续时间, 分别是运行时间和对应的实际时间。
3. [Times: user=0.04 sys=0.01, real=0.01 secs] – Times 这部分对并发阶段来说没多少意义, 因为是
从并发标记开始时计算的,而这段时间内不仅 GC 的并发标记在运行,程序也在运行。

5.4 并发可取消的预清理(Concurrent Abortable Preclean )

1. CMS‐concurrent‐abortable‐preclean – 此阶段的名称: “Concurrent Abortable Preclean”。
2. 0.067/1.194 secs – 此阶段的持续时间, 运行时间和对应的实际时间。有趣的是, 用户时间明显比时钟 时间要小很多。通常情况下我们看到的都是时钟时间小于用户时间, 这意味着因为有一些并行工作, 所以运行时间才会小于使用的 CPU 时间。这里只进行了少量的工作 — 0.167 秒的 CPU 时间,GC 线程经历了很 多系统等待。从本质上讲,GC 线程试图在必须执行 STW 暂停之前等待尽可能长的时间。默认条件下,此阶段可以持续最多 5 秒钟。
3. [Times: user=3.73 sys=0.22, real=1.19 secs] – “Times” 这部分对并发阶段来说没多少意义, 因为是 从并发标记开始时计算的,而这段时间内不仅 GC 的并发标记线程在运行,程序也在运行此阶段可能显著影响 STW 停顿的持续时间, 并且有许多重要的配置选项和失败模式。

5.5  最终标记(Final Remark)

1. CMS Final Remark – 此阶段的名称为 “Final Remark”, 标记老年代中所有存活的对象,包括在此前的并 发标记过程中创建/修改的引用。
2. YG occupancy: 397798 K (4718592 K) – 当前年轻代的使用量和总容量。
3. [Rescan (parallel) , 0.0352362 secs] – 在程序暂停时重新进行扫描(Rescan),以完成存活对象的标 记。此时 rescan 是并行执行的,消耗的时间为 0.0352362 。
4. weak refs processing, 0.0000208 secs]16.797 – 处理弱引用的第一个子阶段(sub­phases)。 显示的 是持续时间和开始时间戳。
5. class unloading, 0.0082855 secs]16.805 – 第二个子阶段, 卸载不使用的类。 显示的是持续时间和开 始的时间戳。
6. symbol tables, 0.0049771 secs]16.810 以及内部化字符串对应的 string tables。当然也显示了暂停的时钟时间。
7. scrub string table,0.0007627 secs – 最后一个子阶段, 清理持有 class 级别 metadata 的符号表 
8. 0K(3047424K) –此阶段完成后老年代的使用量和总容量
9. 397798K(7766016K) –此阶段完成后整个堆内存的使用量和总容量
10. 0.0510646 secs – 此阶段的持续时间。
11. [Times: user=0.34 sys=0.01, real=0.06 secs] – GC 事件的持续时间, 通过不同的类别来衡量: user,
system and real time。

5.6  并发清除(Concurrent Sweep)

1. CMS‐concurrent‐sweep – 此阶段的名称, “Concurrent Sweep”, 清除未被标记、不再使用的对象以释放 内存空间。
2. 0.000/0.000 secs – 此阶段的持续时间, 分别是运行时间和实际时间
3. [Times: user=0.00 sys=0.00, real=0.00 secs] – “Times”部分对并发阶段来说没有多少意义, 因为是
从并发标记开始时计算的,而这段时间内不仅是并发标记在运行,程序也在运行。

5.7 :  并发重置( Concurrent Reset)

1. CMS‐concurrent‐reset – 此阶段的名称, “Concurrent Reset”, 重置 CMS 算法的内部数据结构, 为下一次 GC 循环做准备。
2. 0.016/0.018 secs – 此阶段的持续时间, 分别是运行时间和对应的实际时间
3. [Times: user=0.17 sys=0.01, real=0.01 secs] – “Times”部分对并发阶段来说没多少意义, 因为是从
并发标记开始时计算的,而这段时间内不仅 GC 线程在运行,程序也在运行。

6 GC优化

   6.1 针对java内存常规设置项

 1.针对堆内存(Eden+Survivor+Old)的设置项:-Xms(与Xmx一致,因为GC垃圾回收后回到Xms这个值)  -Xmx(一般不超过容器内存的50%,具体可根据内存组成自己计算)  -XX:+AlwaysPreTouch(启动即分配全部堆内存)
 2.狭义非堆内存(Metaspace+Compressed Class Space+Code Cache):   -XX:CompressedClassSpaceSize
-XX:MetaspaceSize -XX:MaxMetaspaceSize -XX:+UseCodeCacheFlushing -XX:ReservedCodeCacheSize
 3.栈(Stack) : -Xss -XX:ParallelGCThreads  -XX:ConcGCThreads  -XX:CICompilerCount
 4.其他(Native): -XX:MaxDirectMemorySize  -XX:NativeMemoryTracking  -Dio.netty.noUnsafe   -Dio.netty.allocator.type

   6.2 场景 YOUNG GC比较频繁


    GC调优是个长期的过程如当请求量大时,YOUNG GC比较频繁,适当增大新生代的堆内存,可以适当减少YOUNG GC的次数,从而间接减少GC暂停的总耗时,虽然扩大的话,可能会增大并发吞吐量,但可能也会增大垃圾回收耗时,在没有显示配置新生代NewSize大小的情况下:NewSize = min(NewRatioSize,ScaleForWordSize)
其中:NewRatioSize = MaxHeapSize/NewRatio+1 其中MaxHeapSize为-Xms配置值;NewRatio为新生代与老年代比例,默认为2
ScaleForWordSize = (64M*ParallelGCThreads*13)/10 其中ParallelGCThreads为并发垃圾回收线程数,可以通过-XX:ParallelGCThreads指定,默认情况下,8核cpu下值为8。 在-Xms6192m -Xmx6192m  -XX:ParallelGCThreads=8 -XNewRatio=2的JVM参数下:新生代NewSize大小为   64M*8*13/10 = 665.6M  而不是 6192/3=2064M

   6.3 场景 堆内存超过90%,不触发Full GC

     针对这种情况,先确定下docker的内存,堆内存的大小(通过jmap -J-d64 -heap pid可以查看堆信息),如果docker的内存是16G,而堆设置的是4G,则可以适当增加下堆内存的大小,比如改成8G

    通过java -XX:+PrintFlagsFinal查看当前使用的垃圾收集器,如果是JDK1.8默认的是 UseParallelGC(ParallelGC 默认的是 Parallel Scavenge(新生代)+ Parallel Old(老年代),而 Parallel Old(serial old也是)收集器触发FULL GC的前提是 old区满了直到无法容纳新对象的时候才触发(cms收集器可以设置老年代回收比例)。

   此时需要考虑下应用是worker服务还是对外提供接口服务,也就是说最求吞吐量还是最少停顿时间,如果是worker服务,那么用UseParallelGC就可以(此收集器关注的吞吐量,不关注响应时间,配置简单,较长的FullGc停顿且内存耗尽才会触发GC)jdk8 默认应该是启用UseAdaptiveSizePolicy自适应,垃圾回收器会为了提高吞吐量来自动管理年轻代和老年代的堆大小,而如果是对外提供接口服务想要最少停顿时间,那么建议直接用CMS垃圾回收器,在JVM中是+XX配置实现的搭配组合如下:
UseSerialGC 表示 “Serial” + "Serial Old"组合
UseParNewGC 表示 “ParNew” + “Serial Old”
UseConcMarkSweepGC 表示 “ParNew” + “CMS”. 组合,“CMS” 是针对旧生代使用最多的
UseParallelGC 表示 “Parallel Scavenge” + "Parallel Old"组合
UseParallelOldGC 表示 “Parallel Scavenge” + "Parallel Old"组合
在实践中使用UseConcMarkSweepGC 表示 “ParNew” + “CMS” 的组合是经常使用的

6.4 其它

JVM快速找出耗内存大对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值