Java内存溢出问题的定位过程

相信通过写java程序讨生活的人对内存溢出并不陌生,如下文字的出现更是让人恼火:
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: PermGen space
尤其当应用服务器(Java容器)出现上述情况更是让人有一种天塌下来的感觉。

好的编码实践可能会大大降低内存溢出的产生。
本文并不是写如何规避内存溢出,但是我还是要介绍一下如何能够尽量规避内存溢出:
1. 编码规范认真执行。找几个资深程序猿(或者整个项目组讨论后)写一个Java编码规范,让项目组成员尽量遵守。一目了然的代码更容易定位问题,当然也更能让人写出好的代码。
2. 单元测试要覆盖所有分支与边界条件。不要拿某种情况不会出现做借口。有句老话说常在河边站哪有不湿鞋(学名墨菲定律)。
3. 代码审查要走。代码写完了,找资深程序猿扫扫代码没有坏处。
4. 有条件的项目组要充分利用测试人员的能动性。
5. 如果项目的期望较高,就把上面的尽量、可能等词汇改成一定要。

以上五条建议对非性命攸关型项目足够了。


下面说正题:

对于java.lang.OutOfMemoryError: PermGen space 这种情况更多的是靠程序猿的经验来解决:

PermGen space的全称是Permanent Generation space,是指内存的永久保存区域, 这块内存主要是被JVM存放Class和Meta信息的,Class在被Load时就会被放到PermGen space中, 它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对 PermGen space进行清理,所以如果你的应用中有很多CLASS的话,就很可能出现PermGen space错误。

通过上面的描述就可以得出:如果要加载的class与jar文件大小超过-XX:MaxPermSize就有可能会产生java.lang.OutOfMemoryError: PermGen space 。

换句话说-XX:MaxPermSize的大小要超过class与jar的大小。通常-XX:MaxPermSize为-Xmx的1/8。



对于java.lang.OutOfMemoryError: Java heap space 可能定位的过程就需要折腾一翻了:

虽然各种java虚拟机的实现机制不一,但是heap space内存溢出产生的原因相同:那就是堆内存不够虚拟机分配了。

我对java虚拟机的实现不感兴趣,对各种虚拟机的内存分配机制与gc的交互关系也不了解。但是我大致认为内存分配机制与gc是有联系的,也就是说内存不够分配时gc肯定也释放不了堆内存。从这一点出发,我们就需要找为什么gc释放不了堆内存。通常来说释放不了是因为内存还在使用。对于java对象产生的堆内存占用,只要其不再被继续引用gc是能够顺利回收的(对于通过本地方法调用,即JNI调用产生内存泄露的情况暂不考虑)。

问题的关键就找到了,当产生heap space内存溢出时,堆内存中对象数量过多的就可能是问题的根源了。例外的情况是,程序确实需要那么多内存,这时就要考虑增大堆内存。

例外的情况在本文中就不再多说了,下面介绍jdk自带的两个可视化工具来定位问题。

jdk/bin/jconsole.exe jdk/bin/jvisualvm.exe

jconsole.exe可以查看本地以及远程主机上的java虚拟机的当前状况,这对服务器健康检查情况非常有用。如下图:
[img]http://dl2.iteye.com/upload/attachment/0091/9011/526008ac-853a-3309-9d31-cc4190ebf6f5.jpg[/img]


jvisualvm.exe可以用来查看分析内存转储文件;也可以用其做java虚拟机当前状况查看,但是jvisualvm.exe的侵入性非常强,一旦使用会严重影响应用性能。如下图:
[img]http://dl2.iteye.com/upload/attachment/0091/9013/d076aabd-b6b2-39cf-a7b5-ffc582f95273.jpg[/img]


下面写些代码来演示一下内存溢出的产生,堆转储文件的生成,堆内存的分析。

首先创建数据持有对象类:

package com.zas.jvm.om;

/**
* 数据对象
* @author zas
*/
public class DataObject {
//数据对象ID
private String id;
//数据对象内容
private String des;

public DataObject(String id, String des) {
super();
this.id = id;
this.des = des;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getDes() {
return des;
}

public void setDes(String des) {
this.des = des;
}

@Override
public String toString() {
return "DataObject [id=" + id + ", des=" + des + "]";
}
/**
* @param args
*/
public static void main(String[] args) {

}

}


溢出演示代码

package com.zas.jvm.om;

import java.util.ArrayList;
import java.util.List;

public class OutMemeryTest {

List<DataObject> list = new ArrayList<DataObject>();

public void testOm(){
for (int i = 0; i < 100000; i++) {
DataObject data = new DataObject("id&"+i, "des:"+i);
list.add(data);
}
}
/**
* @param args
*/
public static void main(String[] args) {
OutMemeryTest omt = new OutMemeryTest();
for (int i = 0; i < 2; i++) {
omt.testOm();
}
System.out.println("DOne!");
try {
Thread.sleep(100000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}



运行参数设置如下:-Xms64m -Xmx64m -XX:PermSize=8m -XX:MaxPermSize=8m
-XX:-HeapDumpOnOutOfMemoryError
见下图:

[img]http://dl2.iteye.com/upload/attachment/0091/9020/a2e277bc-6b46-3c78-8645-3359cc3192a7.jpg[/img]


jvisualvm分析效果图:
[img]http://dl2.iteye.com/upload/attachment/0091/9022/2f64116e-3cbd-32ea-be30-be0ea00dc6b7.jpg[/img]

从上图结合代码明显得出:com.zas.jvm.om.DataObject这个类的对象出了问题。


以上是一个演示问题产生及定位过程,生产环境的问题千奇百怪需要具体问题具体分析。

当堆内存巨大时可能要调整jdk\lib\visualvm\etc\visualvm.conf文件中的-xms -xmx大小来导入转储文件。

生产环境为linux的较多,可以借助jdk自带的jmap来转储堆内存文件来分析。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值