• jstack: Java占用高CPU分析之- C2 CompilerThread

    1. 现象

    应用刚上线时发现Java进程占用了大量的CPU份额,但过了几分钟后会降下来(流量没变的情况下),因为已经做了负载均衡,于是拿一台实例重新部署代码上线来分析。具体分析步骤参考另外一篇文章《jstack: Java占用高CPU分析之- GC task thread》。这里简单说一下步骤,重点是分析结果后的解决方法,不过强调一点:当发现Java应用占用高CPU的时候,先把Java的线程号和JVM的堆栈信息记录到文件(这个可以用脚本实现),因为堆栈信息随时在变

     

    2.定位

    top | grep java

    cpu2_1

    cpu2_2

    cpu2_3

    cpu2_4

     

    3.原因和解释

    定位到 C2 CompilerThread0这个线程占用了比较高的CPU。C2 Compiler 是JVM在server模式下字节码编译器,JVM启动的时候所有代码都处于解释执行模式,当某些代码被执行到一定阈值次数,这些代码(称为热点代码)就会被 C2 Compiler编译成机器码,编译成机器码后执行效率会得到大幅提升。

    流量进来后,大部分代码成为热点代码,这个过程中C2 Compiler需要频繁占用CPU来运行,当大部分热点代码被编译成机器代码后,C2 Compiler就不再长期占用CPU了,这个过程也可以看作抖动。

    4.解决方案

    (1)最直接有效的方法是“预热(warm up)”:可以使用Jmeter等压测工具模拟线上访问流量,让C2 Compiler预先将热点代码编译成机器码, 减少对正式环境流量的影响。

    warmup

    (2) 设置JVM启动参数:-XX:CICompilerCount=threads

    默认是2, 可以设置4或6。在默认值下抖动时CPU已经满载,设置成更多的线程也不一定起作用,但对于CPU“高而不满”的情况会有用,能减少抖动时间。

    CICompilerCount

     

    参考文章:

    http://www.javaworld.com/article/2078635/enterprise-middleware/jvm-performance-optimization-part-2-compilers.html

    https://answers.atlassian.com/questions/22651310/c1c2-compiler-thread-eats-a-lot-of-cpu

    http://www.cnblogs.com/LBSer/p/3703967.html

    http://qa.blog.163.com/blog/static/190147002201392221426372/

    https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html

     

  • jstack: Java占用高CPU分析之- GC task thread

    jstack 是JDK自带的堆栈跟踪工具,作用有两个:

    1. 为Java 进程或者核心文件打印出线程的堆栈信息;
    2. 远程调试服务器。

    查看用法:

    jstack -help
    Usage:
    jstack [-l] <pid>
    (to connect to running process)
    jstack -F [-m] [-l] <pid>
    (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
    (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
    (to connect to a remote debug server)

    Options:
    -F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m to print both java and native frames (mixed mode)
    -l long listing. Prints additional information about locks
    -h or -help to print this help message

    下面开始具体的调试步骤:

    1. top 命令可以看到具体的Java进程号:(注意这里的PID是进程ID)

    pid

    2. 然后按 Shift + h 直接列出线程号:注意这里的PID是线程ID(对应的是native 线程的id, 即nid)

    tid

    3. 可以看到主要是19226和19227这两个线程消耗大量的CPU,而且时间累占用了一个小时左右,需要把线程号转换成十六进制,因为打印出来的堆栈信息里面的线程号是十六进制形式: printf “%x/n” 19226

    printf

    4. 获得占用CPU的Java进程号和线程号后可以用jstack工具来打印堆栈信息了:

    (1) 先把整个进程堆栈信息打印预览: jstack 19214

    stack1

    stack2

    (2)如果要快速定位线程造成的问题用:jstack [PID(进程号)] | grep [PID(线程号十六进制)] -A 10

    stack3

     

    5. 从进程和线程的堆栈信息可以看出是JVM的GC线程一直在占用大量CPU, 定位代码得出的结论是:Java程序连MySQL频繁new connection而且没有调用close方法,导致GC线程一直占用CPU。