常见的Java问题排查方法(2)

发表于:2013-04-10来源:homeAboutPhotosBlueDavy之技术b作者:bluedavy点击数: 标签:java
如top -H看到的消耗cpu的线程是不断变化的,就比较麻烦了,有个同学写了个脚本自动的去通过top -H看到的消耗cpu的线程找到对应的Java线程堆栈,在这种情

  如top -H看到的消耗cpu的线程是不断变化的,就比较麻烦了,有个同学写了个脚本自动的去通过top -H看到的消耗cpu的线程找到对应的Java线程堆栈,在这种情况下可以用这个脚本去试试,如果看到的线程堆栈确实是比较耗cpu的动作,则基本可以定位到。

  如仍然看不出,则可以尝试多jstack看看,然后多看看是否经常有一些耗cpu的动作在不同的线程不断的出现。

  如可使用perf,则可用perf top看看cpu消耗的热点,不过默认的版本上只能看到jit后的代码,因此可能会比较难对应到具体的代码,这里有一个基于perf排查的Java应用cpu us诡异现象的case。

  总结来说,cpu us消耗高的问题排查还是有一定复杂性,例如之前我碰到过反序列化的对象比较大,请求又非常频繁,导致cpu us消耗增高了很多,但当时的机器内核版本不够,不支持perf,从jstack等等上都看不出什么,后来是由于从业务监控的变化上才排查出问题。

  内存问题

  尽管JVM是自动管理内存的分配和回收的,但Java程序员们还是会经常碰到各种各样的内存问题。

  最常见的第一个问题是java.lang.OutOfMemoryError,估计写Java的同学都碰到过。

  在日志中可能会看到java.lang.OutOfMemoryError: Unable to create new native thread,可以先统计下目前的线程数(例如ps -eLf | grep java -c),然后可以看看ulimit -u的限制值是多少,如线程数已经达到限制值,如限制值可调整,则可通过调整限制值来解决;如不能调限制值,或者创建的线程已经很多了,那就需要看看线程都是哪里创建出来的,同样可通过btrace来查出是哪里创建的,脚本类似如下:

  ?

1
2
3
4
5
6
7
8
9
10
11
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.*;
@BTrace public class Trace{
   @OnMethod(
       clazz="java.lang.Thread",
       method="start"
   )
   public static void traceExecute(){
        jstack(); 
   }
}

  在找到是哪里创建造成了后,之后就可以想办法解决了,例如这种情况下常见的有可能是用了Executors.newCachedThreadPool这种来创建了一个没限制大小的线程池。

  还有一种可能是ulimit -u的限制还没到,内存也空闲,但仍然创建不了,这有可能是由于在2.6.18/32内核上kernel.pid_max默认的32768造成的,这个值其实直接限制了最多能创建的线程数就是32768(即使ulimit -u的值比这大也没用)。

  java.lang.OutOfMemoryError: Heap Size或GC overhead limit exceeded也是常见的现象,在出现了这两种现象的情况下,最重要的是dump出内存,一种方法是通过在启动参数上增加-XX:+HeapDumpOnOutOfMemoryError,另一种方法是在当出现OOM时,通过jmap -dump获取到内存dump,在获取到内存dump文件后,可通过MAT进行分析,但通常来说仅仅靠MAT可能还不能直接定位到具体应用代码中哪个部分造成的问题,例如MAT有可能看到是某个线程创建了很大的ArrayList,但这样是不足以解决问题的,所以通常还需要借助btrace来定位到具体的代码,可以看看这两个OOM排查的case。

原文转自:http://bluedavy.me/?p=445