使用gperftools和btrace来分析java的堆外内存泄露
安装gperftools(google performance tools)来分析java的堆外(超出-Xmx所设置的heap size的部分)内存泄露
如果是堆内泄漏,则最大内存使用不会超过-Xmx所设置的大小,基本上使用jmap之类的都直接可以查到问题。
安装:
$ sudo yum -y install graphviz gv
$ sudo yum -y install gcc make
$ sudo yum -y install gcc gcc-c++
$ tar -zxvf libunwind-1.4.0.tar.gz
$ ./configure
$ make
$ sudo make install
$ ll /usr/local/lib/
$ cd ..
$ wget https://github.com/gperftools/gperftools/releases/download/gperftools-2.8/gperftools-2.8.tar.gz
$ tar -zxvf gperftools-2.8.tar.gz
$ cd gperftools-2.8
$ ./configure
$ make
$ sudo make install
$ ll /usr/local/lib/
$ ll /usr/local/bin/
$ sudo vi /etc/ld.so.conf.d/gperftools.conf
/usr/local/lib
$ sudo ldconfig
$ sudo ldconfig -p | grep libtcmalloc
$ sudo ldconfig -p | grep libunwind
使用:
perftools是通过Linux的LD_PRELOAD达到java应用程序运行时,当调用malloc时换用它的libtcmalloc.so,因此需要在运行程序之前设置变量:
$ vi /data/wildfly/bin/standalone.sh
…
export LD_PRELOAD=/usr/local/lib/libtcmalloc.so
export HEAPPROFILE=/data/wildfly/standalone/log/java //这个必须是wildfly可读写的目录,最好就是wildfly的log目录。
在while true; do 上面增加以上两行。
…
启动Java程序之后,会按照在HEAPPROFILE定义的格式(最后一个斜杠的前面为目录,后面为文件名前缀)生成heap文件,运行如下命令,产生文本格式的报告:
$ /data/wildfly/bin/standalone.sh
另开一个shell,稍等即可查看到:
$ ll /data/wildfly/standalone/log/java*
-rw-rw-r– 1 coadmin coadmin 1048566 Oct 24 11:58 /data/wildfly/standalone/log/java.0001.heap
-rw-rw-r– 1 coadmin coadmin 1048567 Oct 24 11:59 /data/wildfly/standalone/log/java.0002.heap
-rw-rw-r– 1 coadmin coadmin 1048562 Oct 24 12:00 /data/wildfly/standalone/log/java.0003.heap
-rw-rw-r– 1 coadmin coadmin 1048561 Oct 24 12:01 /data/wildfly/standalone/log/java.0004.heap
分析:
$ pprof –text /usr/java/jdk1.8.0_112/bin/java /data/wildfly/standalone/log/java.0001.heap
Using local file /usr/java/jdk1.8.0_112/bin/java.
Using local file /data/wildfly/standalone/log/java.0008.heap.
Total: 116.4 MB
50.3 43.2% 43.2% 50.3 43.2% os::malloc@91cb30
45.8 39.3% 82.6% 45.8 39.3% init
14.0 12.0% 94.6% 14.0 12.0% updatewindow
3.1 2.6% 97.2% 3.1 2.6% inflateInit2_
2.9 2.5% 99.7% 2.9 2.5% readCEN
0.1 0.1% 99.8% 3.1 2.7% ZIP_Put_In_Cache0
0.1 0.1% 99.8% 0.1 0.1% __GI__dl_allocate_tls
0.1 0.0% 99.9% 0.1 0.0% newEntry
0.0 0.0% 99.9% 0.0 0.0% __GI___strdup
0.0 0.0% 100.0% 0.0 0.0% __alloc_dir
0.0 0.0% 100.0% 3.1 2.7% Java_java_util_zip_Inflater_init
0.0 0.0% 100.0% 0.0 0.0% _dl_new_object
0.0 0.0% 100.0% 0.0 0.0% read_alias_file
输出一共六列的解释:
第1列: Number of profiling samples in this function
第2列: Percentage of profiling samples in this function
第3列: Percentage of profiling samples in the functions printed so far
第4列: Number of profiling samples in this function and its callees
第5列: Percentage of profiling samples in this function and its callees
第6列: Function name
上面显示列出来的问题,因为os::malloc@91cb30是内核分配内存的函数,不是java类,是被调用的,init和updatewindow看起来也是系统底层函数,是被调用的。
inflateInit2_和readCEN和最可疑,网上查找,这两都和java.util.zip.Inflater有关。
使用BTrace定位到调用java.util.zip.Inflater代码的位置
安装:
$ mkdir btrace
$ tar -zxvf btrace-2.0.2-bin.tar.gz -C btrace
$ export JAVA_HOME=/usr/java/jdk1.8.0_112
$ export JRE_HOME=${JAVA_HOME}/jre
$ export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
$ export BTRACE_HOME=/home/coadmin/btrace
$ export PATH=$PATH:${JAVA_HOME}/bin:${BTRACE_HOME}/bin
测试下:
$ btrace -h
写一个用来查找java.util.zip.Inflater类调用关系的源代码:
$ vi BtracerInflater.java
import static org.openjdk.btrace.core.BTraceUtils.*;
import org.openjdk.btrace.core.annotations.*;
import java.nio.ByteBuffer;
import java.lang.Thread;
@BTrace
public class BtracerInflater{
@OnMethod(
clazz=”java.util.zip.Inflater”,
method=”/.*/”
)
public static void onMethod(){
println(“Who call java.util.zip.Inflater’s methods :”);
jstack();
}
}
执行:
先找到需要查找的java进程:
$ ps axf | grep java
假设进程号为30810,使用如下命令定位调用关系,一旦调用了java.util.zip.Inflater的任何方法,都会打印出栈信息,主要关注<init>方法和ended方法即可:
$ btrace 30810 BtracerInflater.java
实际执行的命令是:
/usr/java/jdk1.8.0_112/bin/java -cp /home/coadmin/btrace/libs/btrace-client.jar:/usr/java/jdk1.8.0_112/lib/tools.jar:/usr/share/lib/java/dtrace.jar org.openjdk.btrace.client.Main 30810 BtracerInflater.java