模拟JVM内存溢出

来源:互联网 发布:如何下载excel2010软件 编辑:程序博客网 时间:2024/05/21 22:48

一、Java堆溢出

1、配置参数:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

上面的参数限定了堆的大小为20M不可扩展,XX:+HeapDumpOnOutOfMemoryError用来生成dump快照文件,当内存溢出时,可以用来分析。JDK版本为JDK1.7,开发工具为myEclipse,参数通过myeclipse的run configrations 的arguments选项来配置

public class HeapOOM {static class OOMObject{}public static void main(String[] args) {List<OOMObject> list = new ArrayList<HeapOOM.OOMObject>();while(true){list.add(new OOMObject());}}}
运行结果:

java.lang.OutOfMemoryError: Java heap spaceDumping heap to java_pid5772.hprof ...Heap dump file created [27893534 bytes in 0.101 secs]Exception in thread "main" java.lang.OutOfMemoryError: Java heap space


二、虚拟机栈和本地方法栈溢出

方案一:指定了每条现程的堆栈大小,-Xss 128k

public class JavaStackSOF {private int stackLength = 1;public void stackLeak(){stackLength ++;stackLeak();}public static void main(String[] args) throws Throwable {JavaStackSOF javaStackSOF = new JavaStackSOF();try{javaStackSOF.stackLeak();}catch (Throwable e) {System.out.println("stacklength="+javaStackSOF.stackLength);throw e;}}}

运行结果

stacklength=993Exception in thread "main" java.lang.StackOverflowError    at JavaStackSOF.stackLeak(JavaStackSOF.java:9)    at JavaStackSOF.stackLeak(JavaStackSOF.java:12)    at JavaStackSOF.stackLeak(JavaStackSOF.java:12)    at JavaStackSOF.stackLeak(JavaStackSOF.java:12)    at JavaStackSOF.stackLeak(JavaStackSOF.java:12)    at JavaStackSOF.stackLeak(JavaStackSOF.java:12)


方案二:指定了每条现程的堆栈大小,-Xss 128k,同时增加栈中的局部变量

public class JavaStackSOF {private int stackLength = 1;private int tempData = 1;int temp = 2;public void stackLeak(){stackLength ++;tempData++;//增加栈中的局部变量temp++;stackLeak();}public static void main(String[] args) throws Throwable {JavaStackSOF javaStackSOF = new JavaStackSOF();try{javaStackSOF.stackLeak();}catch (Throwable e) {System.out.println("stacklength="+javaStackSOF.stackLength);throw e;}}}

运行结果:

stacklength=996//栈的深度Exception in thread "main" java.lang.StackOverflowError    at JavaStackSOF.stackLeak(JavaStackSOF.java:9)    at JavaStackSOF.stackLeak(JavaStackSOF.java:12)    at JavaStackSOF.stackLeak(JavaStackSOF.java:12)    at JavaStackSOF.stackLeak(JavaStackSOF.java:12)    at JavaStackSOF.stackLeak(JavaStackSOF.java:12)


方案三:增大栈的容量-Xss3m,局部变量与方案一一样。

运行结果:

stacklength=128252//栈的深度Exception in thread "main" java.lang.StackOverflowError    at JavaStackSOF.stackLeak(JavaStackSOF.java:12)    at JavaStackSOF.stackLeak(JavaStackSOF.java:12)


在Java虚拟机中描述了2种异常:

1、如果线程请求的栈深度(每个方法压入栈中时,会创建一个栈帧,栈帧中包含有局部变量,栈帧决定栈的深度)大于虚拟机所允许的最大深度,将抛出StackOverflowError。

2、如果线程在扩展栈时无法申请到足够的空间,会抛出outofmemoryError(栈一般可以动态扩展,扩展时由于内存太小了,无法申请到足够的空间)。


其实这两种情况存在重叠的情况,当栈空间无法分配的时候,不知道是内存不够了,还是已使用的空间已达到最大。

在上述的实验中,无法产生OutOfMemoryError结果,一直都是StackOverflowError异常,并且局部变量越多,栈的深度越小。一般情况下,虚拟机默认参数,栈的深度是1000~2000,对于一般的方法调用足够了。

有一种情况会导致栈OOM

public class JavaStackOOM {private void dontStop(){while(true){}}public void stackLeakByThead(){while(true){Thread thread = new Thread(new Runnable() {public void run() {dontStop();}});thread.start();}}public static void main(String[] args) {JavaStackOOM javaStackOOM = new JavaStackOOM();javaStackOOM.stackLeakByThead();}}

上面这段代码虽然会导致OOM,但是这个跟栈空间是否充足没有关系,这个是由于栈是线程私有的,不断的用新线程去访问一个方法,会导致内存的不足,并且栈分配的内存越大越容易出现。在运行这段代码需要注意,无限产生线程很容易导致电脑卡死。

在建立过多的线程导致的OOM,在不减少线程的数量或者跟换64位虚拟机的情况下,只能通过减小最大堆,减少栈容量来换取更多的线程。

Java栈能分配的内存大小=系统限制的内存大小(如32位系统为2G) - Xmx(最大堆)-MaxPermSize(最大方法区),程序计数器消耗内存很小忽略,如果虚拟机进程本身耗费的内存不算在内,剩下的就由虚拟机栈和本地方法区瓜分了。


三、方法区和运行时常量池溢出

运行常量池溢出:参数:-XX:PermSize=10M -XXMaxPermSize=10M

在1.6之前(包括1.6)方法区采用永久代的实现方式来进行管理,导致方法区如果数据过多会导致OOM异常,在1.7之后逐步去永久代。

public static void main(String[] args) {List<String> list = new ArrayList<String>();int i= 0;while(true){    list.add(String.valueOf(i++).intern());//intern:检查常量池中是否有该字段,没有则复制该字段到常量池中。}}
实验结果:

在1.6、1.7环境下分别执行都未能发生OOM异常,程序一直在执行,cpu占用90%以上。理论上,在JDK1.6环境下,会出现OOM异常,在1.7环境中会一直运行下去。


延伸1:

String str1 = new StringBuilder("计算机").append("软件").toString();System.out.println(str1.intern() == str1);String str2 = new StringBuilder("ja").append("va").toString();System.out.println(str2.intern() == str2);
实验结果:

在JDK1.6环境下,结果是false,false,而在JDK1.7中,结果是:true,false

分析:JDK1.6,intern会把首次遇到的字符串复制到常量池中(永久代)去,返回的是常量池中的引用地址,而stringbuilder 创建的对象在堆中,所以引用显然是不同的。

而在JDK1.7中,则不会复制对象到常量池中,只会把堆中的地址记录到常量池中,而对于java字符串比较的结果为false,是因为在创建"计算机软件"之前,字符串常量池中已经有了"Java"的引用了。


延伸2:

public void test2(){String str1 = new StringBuilder("计算机").append("软件").toString();String str2 = "计算机软件";String str3 = "计算机软件";System.out.println(str1 == str2);System.out.println(str1.equals(str2));System.out.println(str3 == str2);}

实验结果:false true true

java代码在编译阶段,会在类文件的常量池中存放"计算机","软件","计算机软件"这几个字符,并且相同的字符只有一份,然后在JVM加载阶段,类文件中的常量池(也叫静态常量池)会加载到方法区的运行时常量池。然后在代码运行时执行new操作,如果常量池没有该字符或者字符引用,会在常量池中存储该字符或者字符引用,

显然str1 与str2的引用地址是不相等的,但是它们的内容是相等的,都是"计算机软件",str2与str3都是指向同一个对象,所以引用也是相等的。


方法区溢出:

方法区是存放Class的相关信息,如类名,常量池,字段描述,方法描述等。可以通过不停的加载类来实现方法区的溢出。由于操作太麻烦,没有验证。









原创粉丝点击