JVM规范规定,除了程序计数器,虚拟机其他内存区域均会发生内存溢出的可能,OutOfMemoryError(OOM)
原文地址:http://www.begincode.net/blog/62 我的网站,欢迎大家多提意见
本文目的:
1、通过代码人为造成OOM,让大家跟了解JVM运行时各区存储的内容。
2、通过demo让大家实际开发过程中,能够根据异常判断是那个内存区域发生的溢出,
3、让大家了解到什么样的代码会产生OOM,开发中能够尽量规避。
前提:
先和大家介绍一下eclipse如何设置JVM参数,Xms 最小堆内存,Xmx最大堆内存,
HeapDumpOnOutOfMemoryError:发生内存溢出时生成堆转储快照
一、JAVA堆内存溢出
代码实例,来自<<深入理解JVM虚拟机>>
public class TestOOM {
static class OOMObject{
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while(true){
list.add(new OOMObject());
}
}
}
运行结果如下,发生内存溢出,在OutOfMemoryError后面会跟着 Java heap space 提示是堆内存溢出,
同时生成 java_pid10308.hprof 文件
该文件可以用分析工具MAT进行分析,安装链接http://www.begincode.net/blog/45 也可以用其他工具,
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid10308.hprof ...
Heap dump file created [27883798 bytes in 0.497 secs]
通过工具可以分析是内存泄露还是内存溢出,如果不存在泄露,那就是存在着很多对象实例无法回收,可以通过分析查看是那些对象,是否必须存活,如果必须存活在考虑适当调整JVM堆参数,如Xmx Xms等在以后的文章中会在此介绍。
二、虚拟机栈和本地方栈溢出(栈溢出)
1、虚拟机栈
虚拟机栈溢出,理论上分为两种,一种是线程请求的栈深度大于虚拟机允许的最大深度,另外一种是申请的空间不足。
因为虚拟机栈记录的是局部变量(方法变量)和函数调用栈针,则产生堆栈溢出一般是函数调用深度,如递归类或者
方法A调用方法B,方法B调用方法C这样调用深度超过了虚拟机允许的最大深度,
JVM参数设置为如下,Xss128k Xss是堆栈大小
-Xms20m -Xmx20m -Xss128k -XX:+HeapDumpOnOutOfMemoryError
demo如下
public class TestOOM {
static int stackIndex = 0;
public void testStackOverFlow(){
stackIndex++;
testStackOverFlow();
}
public static void main(String[] args) throws Throwable {
try{
TestOOM oom = new TestOOM();
oom.testStackOverFlow();
}catch(Throwable e){
System.out.println("堆栈深度(迭代次数):"+stackIndex);
throw e;
}
}
}
执行结果,堆栈深度达到983,堆栈溢出,并会给出具体是类,方法,发生的错误
堆栈深度(迭代次数):983
Exception in thread "main" java.lang.StackOverflowError
at zgwx.TestOOM.testStackOverFlow(TestOOM.java:9)
at zgwx.TestOOM.testStackOverFlow(TestOOM.java:10)
public void testStackOverFlow(){
stackIndex++;
int str = 12;
int str2 = 12;
int str3 = 12;
int str4 = 12;
int str5 = 12;
int str6 = 12;
int str7 = 12;
int str8 = 12;
testStackOverFlow();
}
执行结果如下,迭代次数明显减少,说明局部变量占用了堆栈的空间
堆栈深度(迭代次数):579
Exception in thread "main" java.lang.StackOverflowError
at zgwx.TestOOM.testStackOverFlow(TestOOM.java:9)
at zgwx.TestOOM.testStackOverFlow(TestOOM.java:18)
2、本地方法栈,本地方法栈可以通过创建线程的方式来实现溢出,因为java线程是映射到操作系统内核上,所以线程会调用本地方法,造成本地方法栈的溢出
本机环境没测试出来,机器卡死了,大家可以自己试试尽量多创建线程,就会撑爆本地方法栈
下面是找来的例子,没见到效果,都死卡死机告终
public class Test {
private void runAlways(){
while(true){
}
}
public void createThread(){
while(true){
Thread thread = new Thread(new Runnable(){
public void run() {
runAlways();
}
});
thread.start();
}
}
public static void main(String[] args) {
Test test = new Test();
test.createThread();
}
}
三、直接本机内存溢出
本机内存溢出可以无限制申请空间即可,用nio的申请缓冲区方式
public static void main(String[] args) {
List list = new ArrayList();
while(true){
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024*2048);
list.add(byteBuffer);
}
}
执行结果如下,OutOfMemoryError后面的提示,Direct buffer memory 说明了直接缓冲区,
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:658)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
四、方法区(永久代)溢出
方法区存储的是加载的类信息,常量,我们就循环创建字符串常量,让方法区溢出
为了尽快溢出,jvm参数设置永久代参数5M: -XX:PermSize=5m -XX:MaxPermSize=5m
代码如下:
public static void main(String[] args) {
List list = new ArrayList();
int i = 0;
while(true){
list.add((String.valueOf(i++)).intern());
}
}
运行结果如下:OutOfMemoryError后面提示 PermGen space ;改代码在jdk6及以下版本能够报出如下异常,jdk7高版本及以上部分jvm已经逐渐开始取消永久代的原因,只会报出堆内存异常
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at com.TestSocket.main(TestSocket.java:12)