检测java内存泄露_MAT 检测 Java内存泄露检测

一、Java内存泄露例子

Vector v = new Vector( 10 );

for ( int i = 1 ;i < 100 ; i ++ ){

Object obj = new Object();

v.add(obj);

obj = null ;

}

在这个例子中,代码栈中存在Vector 对象的引用 v 和 Object 对象的引用 obj 。在 For 循环中,我们不断的生成新的对象,然后将其添加到 Vector 对象中,之后将 obj  引用置空。问题是当 obj  引用被置空后,如果发生 GC ,我们创建的 Object 对象是否能够被 GC 回收呢?答案是否定的。因为, GC 在跟踪代码栈中的引用时,会发现 v 引用,而继续往下跟踪,就会发现 v 引用指向的内存空间中又存在指向 Object 对象的引用。也就是说尽管 obj  引用已经被置空,但是 Object 对象仍然存在其他的引用,是可以被访问到的,所以 GC 无法将其释放掉。如果在此循环之后, Object 对象对程序已经没有任何作用,那么我们就认为此 Java 程序发生了内存泄漏。

在不涉及复杂数据结构的一般情况下,Java 的内存泄露表现为一个内存对象的生命周期超出了程序需要它的时间长度。我们有时也将其称为“对象游离”。

public class FileSearch{

private byte [] content;

private File mFile;

public FileSearch(File file){

mFile = file;

}

public boolean hasString(String str){

int size = getFileSize(mFile);

content = new byte [size];

loadFile(mFile, content);

String s = new String(content);

return s.contains(str);

}

}

在这段代码中,FileSearch 类中有一个函数 hasString ,用来判断文档中是否含有指定的字符串。流程是先将mFile 加载到内存中,然后进行判断。但是,这里的问题是,将 content 声明为了实例变量,而不是本地变量。于是,在此函数返回之后,内存中仍然存在整个文件的数据。而很明显,这些数据我们后续是不再需要的,这就造成了内存的无故浪费。

案例1

代码

public static void main(String[] array) throws InterruptedException {

List list = new ArrayList();

int i =0;

while(true)

{

list.add(new MyObj("Str"+i,i++));

}

}

class MyObj

{

public String value;

public int i;

public byte[] b;

public MyObj(String value, int i) {

this.value = value;

this.i = i;

b=new byte[1024*1024];

}

}

命令:

C:\Windows\System32>jps -l

20592

21092 sun.tools.jps.Jps

11024

16268 jconsole.MemoryTest

C:\Windows\System32>jmap -dump:file=16268.bin  16268

Dumping heap to C:\Windows\System32\16268.bin ...

Heap dump file created

将文件16268导入 Memory Analysis Tool

e5cc4f59c737ac5bea7f44d97ab05313.png

cdf5831b12c7fb8d89edfce48a4cc4be.png

d4c187b0c6daf398534ee28abdd2f3c5.png

分配了大量的MyObj 对象。结合代码去查找问题,很快就能找到问题。

案例2

MySQL jdbc 5.1.6里,默认情况下,如果一个Connection永远不掉用close,即使你每一个Statement, ResultSet都调用了close,仍然会有内存泄漏,换句话说,Statement的close没有把自己的资源释放干净,Statement会在对应的Connection里有缓存  在我们的项目中采用了数据库链接池的技术,我们的数据库链接应用完成后不是马上调用close方法关闭掉,而是返回给了数据库链接池管理。所以链接池中的活动connettion对象中实际上持有了Statement的引用,造成内存泄露。

12ac83ba4d95f5219f813bbaf3acc35b.png

通过图中,MAT给出了关于本次检测有两处占用内存较多的疑似泄露,下面是详细说明

e23ec3012f4a724154d2d1c11a9c7648.png

先从26%的嫌疑人suspect2看起,报告很直观的给出系统中有13个关于数据库链接的引用对象占用了59.83%的内存资源,我们还可以点击details查看一下详细情况

0999b95839f8c54d8c74e1248ac7a8f2.png可以看到这13个数据库链接引用其实就是数据库链接池的数据库链接对象。而数据库链接对象本身仅占用100K左右内存,因此不可能达到26M的内存占用量,所以基本可以断定是数据库链接占用了原查询过程中的一些结果集等对象的引用,造成内存泄露问题。

案例3、

quartz 内存溢出

通过MAT打开dump出来的内存文件,打开后如下图:

c7044209da57aeb0bfd655863b2cd723.png

从上图可以看到它的大部分功能。1. Histogram可以列出内存中的对象,对象的个数以及大小。2. Dominator Tree可以列出那个线程,以及线程下面的那些对象占用的空间。3.Top consumers通过图形列出最大的object。4.Leak Suspects通过MA自动分析泄漏的原因。

Histogram如下图:

Objects:类的对象的数量。

Shallow size:就是对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。

Retained size:是该对象自己的shallow size,加上从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和。

我们发现ThreadLocal和bingo.persister.dao.Daos类的对象占用了很多空间。

d4d1484e0bac5c30e733b0f42fd546fb.png

Dominator Tree如下图:

我们发现quartz的定时器的工作线程(10个)占了很多的内存空间

087a61b5433bd0e2bed4cb716a3e4f23.png

Top consumers如下图:

这里显示了内存中最大的对象有哪些,他们对应的类是哪些,类加载器classloader是哪些。

有些时候,我们在这里就可以看到代码泄露的位置。

ab91b0f6b923956cc2a7abde825b1a9a.png

Leak Suspects如下图:

从那个饼图,该图深色区域被怀疑有内存泄漏,可以发现整个heap才250M内存,深色区域就占了34%。后面的描述,告诉我们quartz线程占用了大量内存,并指出system class loader加载的"java.lang.ThreadLocal"实例的内存中聚集(消耗空间),并建议用关键字"java.lang.ThreadLocal$ThreadLocalMap$Entry[]"进行检查。所以,MAT通过简单的报告就说明了问题所在。

825e6612e523798834e1be408c7ad68d.png

通过Leak Suspects的Problem Suspect 1点击【Details »】,

如下图如下图所示的上下文菜单中选择 List objects -> with outgoning references, 查看ThreadLocal都应用了些什么对象。

bffb9ee10e55bd3c74697f7e589dde16.png

现在看到ThreadLocal中引用的对象如下图:

是dao对象

ps:该dao对象包含一个轻量级的ORM关系内容,所以Retained size比较大。

7a7e49b707d046a934a51addde69fdee.png

下面继续查看dao的gc ROOT

如下图所示的上下文菜单中选择 Path To GC Roots -> exclude weak references, 过滤掉弱引用,因为在这里弱引用不是引起问题的关键。

adf20f49634419eed571e8def5fd2d89.png

从下图中,可以看到在org.quartz.simpl.SimpleThreadPool中保存了daos的引用。所以可以得出是是因为定时器在运行的过程中持有大量的Daos对象应起了内存泄露。为什么会有那么多的Daos呢,Daos不是一个无状态的单例的、可以重用的吗?继续查看spring配置文件发现Daos的bean配置成scope="prototype",导致定时任务又是每次调用都生产新的Daos实例。由于是Daos是无状态的,修改为单例的,问题解决。

3fb457121e308b8272785a35e1510b20.png

以上是通过MAT分析Tomcat应用程序,找到内存泄露的原因,并解决。

案例4、持久代溢出

public class OOMPermTest {

public static void main(String[] args){

oom();

}

private static void oom(){

Object[] array = new Object[10000000];

for(int i=0; i<10000000; i++){

String d = String.valueOf(i).intern();

array[i]=d;

}

}

intern() 将撑破持久代。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值