C2 CompilerThread11引起的CPU较高分析

目录

1、问题描述

2、问题解决过程


1、问题描述

       在日常巡检时发现,线上部分服务器的性能会出现波动,tp等指标出现飙升。经过一系列的排查,最终发现是由于C2编译线程长时间运间消耗了CPU.

        异常的堆栈信息如下:

"C2 CompilerThread11" #17 daemon prio=9 os_prio=0 tid=0x00007fdd8c6fc800 nid=0x133 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

      jdk版本如下:

 java version "1.8.0_20"

Java(TM) SE Runtime Environment (build 1.8.0_20-b26)

Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)

2、问题解决过程

(1) 是否因gc引起

先看一下一台预发机的性能。

 

确实,这台机器是由于gc导致的,在性能升高的点因Metadata GC Threshold导致的full gc。对应的gc日志如下:

2021-03-09T16:54:16.228+0800: 429252.720: [Full GC (Metadata GC Threshold)  1291M->110M(4096M), 1.2160712 secs]
   [Eden: 816.0M(2824.0M)->0.0B(2864.0M) Survivors: 40.0M->0.0B Heap: 1291.7M(4096.0M)->110.6M(4096.0M)], [Metaspace: 96237K->95604K(1136640K)]
Heap after GC invocations=106 (full 4):
 garbage-first heap   total 4194304K, used 113234K [0x00000006c0000000, 0x00000007c0000000, 0x00000007c0000000)
  region size 4096K, 0 young (0K), 0 survivors (0K)
 Metaspace       used 95604K, capacity 96816K, committed 98304K, reserved 1136640K
  class space    used 10374K, capacity 10684K, committed 11008K, reserved 1048576K
}

       从JDK8开始,永久代被废弃了,取而代之的是一个称为Metaspace的存储空间,它使用的是本地内存而不是堆内存,也就是说在默认情况下它的大小只与本地内存大小有关。虽然Metaspace区的最大值(在没有主动设置-XX:MaxMetaspaceSize)只与本地内存有关,但是-XX:MetaspaceSize是有初始默认值的,可以通过java -XX:+PrintFlagsFinal 或者 -XX:+PrintFlagsInitials查看初始参数值(uintx MetaspaceSize  = 21807104)。

      为了避免Metadata GC导致的full gc, 合理设置metaspace的大小,并将MetaspaceSizeMaxMetaspaceSize设置一样大即可,在我的应用中设置了256M足矣。

(2) 线上机器的性能

      生产环境的jvm参数中已经避免了Metadata GC Threshold导致的full gc。在巡检过程中,发现并没有频繁的ygc以及因full gc导致的性能问题,继续深入分析该问题。首先确认是否codeCache是否被填满导致的性能,通过非堆内存的监控(非堆内存最大值为1.3G,已经使用107M)发现服务器的CodeCache并没有使用满。

JVM在编译代码的时候,会在CodeCache中保存一些汇编后指令。由于其大小是固定的,一旦被填满了,就会导致部分热点代码没法被编译,JVM就无法编译其他代码,应用的性能将会急剧下降(基于解释性编译执行代码)。

Java HotSpot(TM) 64-Bit Server VM warning: CodeCacheis full. Compiler has been disabled.

Java HotSpot(TM) 64-Bit Server VM warning: Tryincreasing the code cache size using -XX:ReservedCodeCacheSize=

在我所使用jvm的code cache默认大小为240M。

-XX:ReservedCodeCacheSize=251658240(jinfo -flag ReservedCodeCacheSize pid)

     继续排查原因(由于每隔几天出现一次性能问题,显然就不能归究于网络抖动),通过top及jstack查看最耗CPU的线程堆栈,发现是0X144,具体的看问题描述部分。

Tasks: 493 total,   0 running, 493 sleeping,   0 stopped,   0 zombie
Cpu(s): 21.4%us,  6.0%sy,  0.0%ni, 71.6%id,  0.0%wa,  0.0%hi,  1.0%si,  0.0%st
Mem:  263727056k total, 234156976k used, 29570080k free,  3490188k buffers
Swap: 16777212k total,    55228k used, 16721984k free, 97451304k cached

   PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                    
   307 admin     20   0 36.9g 2.4g  12m S 14.3  0.9   16:55.77 java                                                        
   454 admin     20   0 36.9g 2.4g  12m S  1.0  0.9    9:29.54 java                                                        
   455 admin     20   0 36.9g 2.4g  12m S  1.0  0.9    9:48.97 java 

     下面我们就开始聊聊JIT编译。

   JIT编译类型有C1编译器,C2编译器,分层编译器。这里我简单介绍一下分层编译的5个层次,其他的就略了……

    分层编译将JVM执行的状态分为5个层次:

  • 解释执行
  • 执行不带profiling的C1代码
  • 执行仅带方法调用次数以及循环回边执行次数profiling的C1代码
  • 执行带所有profiling的C1代码
  • 执行C2代码

         profiling其实就是收集能够反映程序执行状态的数据,比如方法的调用次数,以及循环回边的执行次数等。

        通常情况下,C2代码的执行效率要比C1代码的高出30%以上。C1层执行的代码,按执行效率排序从高至低则是1层>2层>3层。这5个层次中,1层和4层都是终止状态,当一个方法到达终止状态后,只要编译后的代码并没有失效,那么JVM就不会再次发出该方法的编译请求的。服务实际运行时,JVM会根据服务运行情况,从解释执行开始,选择不同的编译路径,直到到达终止状态。下图中就列举了几种常见的编译路径:

                    

  • 图中第①条路径,代表编译的一般情况,热点方法从解释执行到被3层的C1编译,最后被4层的C2编译。
  • 如果方法比较小(比如Java服务中常见的getter/setter方法),3层的profiling没有收集到有价值的数据,JVM就会断定该方法对于C1代码和C2代码的执行效率相同,就会执行图中第②条路径。在这种情况下,JVM会在3层编译之后,放弃进入C2编译,直接选择用1层的C1编译运行。
  • 在C1忙碌的情况下,执行图中第③条路径,在解释执行过程中对程序进行profiling ,根据信息直接由第4层的C2编译。
  • 前文提到C1中的执行效率是1层>2层>3层,第3层一般要比第2层慢35%以上,所以在C2忙碌的情况下,执行图中第④条路径。这时方法会被2层的C1编译,然后再被3层的C1编译,以减少方法在3层的执行时间。
  • 如果编译器做了一些比较激进的优化,比如分支预测,在实际运行时发现预测出错,这时就会进行反优化,重新进入解释执行,图中第⑤条执行路径代表的就是反优化。

 这段出自 :Java即时编译器原理解析及实践

         在jdk8中,默认是开启分层编译,TieredCompilation编译确实可以在启动时平滑CPU负载,但分层编译是综合考虑C1和C2编译器的优点衍生出来的一种折中编译器,在一些特殊情况下可以采用了比较激进甚至不可靠的优化方法,如出现分支预测失败,重而进行反优化重新进入解释执行。

        因此,线上的服务器部分关闭分层编译,启用C2编译:-XX:-TieredCompilation,对比观察观察(由于我们的服务器访问量级QPM:100,如果用分层编译,感觉刚达到优化层级,又不调用,反倒浪费优化时间)【当然对于量级较大的应用,开启分层编译还是有很大优势的】。

   

   上图是对比图,唯一区别就在于:绿色是:-XX:+TieredCompilation, 红色:-XX:-TieredCompilation。

 

 

 

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值