CPU热点一般来说指的是Java代码占用过多CPU,导致应用性能下降,影响用户体验,甚至导致业务不可用。那么如何去分析呢?一个思路是周期性地打印所线程的栈,分析占用CPU最多的Java方法。
比如说现在有一个线程在占用CPU,我们可以对线程的调用栈做一个采样。如下图,我们看到是a调了b。过一会儿之后,再次采样,看到是a调b调c。再过一会儿,继续采样,可以看到一样的栈,a调b调c。继续采样,当我们采样的次数足够多的时候,我们就可以以这些样本为基础,计算每个方法的CPU占比。
这里需要说明的是,对于每一个调用栈,只有栈顶的方法才是在CPU上执行的方法,而调用方是不在CPU之上的。举个例子,对于第一个样本来说,我们可以看到是a调用b所以b是占用CPU的方法,而a不是。由此我们可以看到,在所有的六个样本里面,一共有三个c方法在调用栈的最顶部,所以说方法c的CPU占比是百分之五十。方法b出现了两次,所以它的CPU占比十六分之二,约等于百分之三十三。同样的方法a在六次里面只有一次在栈顶,所以方法a是六分之一,约为百分之十七。
我们拿到这些每个方法的CPU占比数据之后,我们就可以得出方法c是在CPU上执行时间最多的方法。当我们做Java应用性能优化的时候,只需要对方法c进行重点优化,就可以取得不错的效果。
我们可以通过以下几类工具拿到每个java线程的调用栈:
第一类是基础命令行工具,比如jstack、jcmd、kill-3等等。这类工具只能在安全点进行采样,结果可能存在偏差。安全点就是当虚拟机需要对所有的线程进行栈的遍历的时候,需要把所有的业务线程全部暂停。如果是这样的话,就有可能会导致在安全点去采样得到的调用栈可能是不准确的,这个问题一般在极端情况下才会出现,大多数情况下还是可以用的。第二个问题是对于这类命令行工具,它输出的是文本,阅读效率会比较低。如果java进程里面的线程比较多,文本就会很长,很难分析。
第二类是图形化工具,比如java Mission Control、 Visual VM等等。这类工具可以自动周期性的抓取所有现场的调用栈。并且支持以图形可视化的方式来方便阅读。但是这类工具也同样存在安全点的问题。而且如果JAVA进程中的线程比较多,爬取所有线程的调用栈是一个开销比较大的事情。
第三类是JMX编程接口。这类接口一般需要自己写代码去调用JMX接口,可以自己做一些设计和实现,可以对你想做的事情做一些定制,但是同样的这类工具也存在安全点问题。
最后一类是高级剖析工具,比如Java Flight Recorder,async-profiler等等。这类工具没有安全的问题,结果是很准确的。这类工具通常是在问题发生的时候由人工去采样,如果问题发生在深夜,没有人值守,可能会错过现场,这可能是这类工具最大的缺点了。
下面我们讲一下内存申请热点的剖析。内存申请热点指java代码创建对象的内存分配压力比较大,引起频繁GC暂停应用性能下降,影响用户体验,甚至导致业务不可用。
如何去分析呢?我们首先可以通过Heap Dump来确认是否存在内存泄露。如果存在内存泄露,那么针对性的优化就可以。如果不是内存泄露,那就是java进程的内存分配压力很大。这个时候如果做GC的调优,可能可以解决部分问题,比如把串行GC改成并行GC。本质上我们需要找出内存分配的热点,并进行优化,降低内存分配的压力。可用的工具有:
第一类是图形化工具,比如Visual VM,这类工具会自动修改进程内已经加载的类的字节码,插入用于统计的字节码。因为存在字节码的改写,所以有可能会引起编译退优化,导致应用性能更加下降。同时因为插入了统计代码,所以它的开销也会比较大。
第二类是高级剖析工具,比如Java Flight Recorder、async-profiler等等。这类工具不需要修改已经加载的类的字节码,所以不会引起特优化,而且开销也会更低一些。
以上就是我们为大家整理的性能测试中的CPU和内存申请热点剖析工具和方法,欢迎评论区交流讨论。