Java虚拟机学习4、内存溢出

堆溢出

Java堆唯一的作用就是存储对象实例,只要保证不断创建对象并且对象不被回收,那么对象数量达到最大堆容量限制后就会产生内存溢出异常了。所以测试的时候把堆的大小固定住并且让堆不可扩展即可。测试代码如下

 1 package com.xrq.test;
 2
 3 import java.util.ArrayList;
 4 import java.util.List;
 5
 6 /**
 7  * 测试内容:堆溢出
 8  *
 9  * 虚拟机参数:-Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
10  */
11 public class HeapOverflowTest
12 {
13     public static void main(String[] args)
14     {
15         List<HeapOverflowTest> list = new ArrayList<HeapOverflowTest>();
16         while (true)
17         {
18             list.add(new HeapOverflowTest());
19         }
20     }
21 }

运行结果

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid8876.hprof ...
Heap dump file created [15782068 bytes in 0.217 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2760)
    at java.util.Arrays.copyOf(Arrays.java:2734)
    at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
    at java.util.ArrayList.add(ArrayList.java:351)
    at com.xrq.test.HeapOverflowTest.main(HeapOverflowTest.java:18)

这种异常很常见,也很好发现,因为都提示了“Java heap space”了,定位问题的话,根据异常堆栈分析就好了,行号都有指示。解决方案的话,可以调大堆的大小或者从代码上检视是否存在某些对象生命周期过长、持有状态时间过长的情况,长时间少程序运行期间的内存消耗。

另外,要理解内存溢出和内存泄露两个概念

内存溢出Memory Overflow

内存溢出指的是程序在申请内存的时候,没有足够大的空间可以分配了

内存泄露Memory Leak

内存泄露指的是程序在申请内存之后,没有办法释放掉已经申请到内存

栈溢出

Java虚拟机规范中描述了如果线程请求的栈深度太深(换句话说方法调用的深度太深),就会产生栈溢出了。那么,我们只要写一个无限调用自己的方法,自然就会出现方法调用的深度太深的场景了。测试代码如下

 1 package com.xrq.test;
 2
 3 /**
 4  * 测试内容:栈溢出测试(递归调用导致栈深度不断增加)
 5  *
 6  * 虚拟机参数:-Xss128k
 7  */
 8 public class StackOverflowTest
 9 {
10     private int stackLength = 1;
11
12     public void stackLeak()
13     {
14         stackLength++;
15         stackLeak();
16     }
17
18     public static void main(String[] args) throws Throwable
19     {
20         StackOverflowTest stackOverflow = new StackOverflowTest();
21         try
22         {
23             stackOverflow.stackLeak();
24         }
25         catch (Throwable e)
26         {
27             System.out.println("stack length:" + stackOverflow.stackLength);
28             throw e;
29         }
30     }
31 }

运行结果

stack length:1006
Exception in thread "main" java.lang.StackOverflowError
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:14)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)
    at com.xrq.test.StackOverflowTest.stackLeak(StackOverflowTest.java:15)  ...

后面都是一样的,忽略。通过不断创建线程的方式可以产生OutOfMemoryError,因为每个线程都有自己的栈空间。不过这个操作有危险就不做了,原因是Windows平台下,Java的线程是直接映射到操作系统的内核线程上的,如果写个死循环无限产生线程,那么可能会造成操作系统的假死。

上面无限产生线程的场景,从另外一个角度说,就是为每个线程的栈分配的内存空间越大,反而越容易产生内存溢出。其实这也很好理解,操作系统分配给进程的内存是有限制的,比如32位的Windows限制为2GB。虚拟机提供了了参数来控制Java堆和方法区这两部分内存的最大值,剩余内存为2GB-最大堆容量-最大方法区容量,程序计数器很小就忽略了,虚拟机进程本身的耗费也不算,剩下的内存就是栈的了。每个线程分配到的栈容量越大,可建立的线程数自然就越少,建立线程时就越容易把剩下的内存耗尽。

StackOverFlowError这个异常,有错误堆栈可以阅读,比较好定位。而且如果使用虚拟机默认参数,栈深度在大多数情况下,达到1000~2000完全没有问题,正常方法的调用这个深度应该是完全够了。但是如果建立过多线程导致的OutOfMemoryError,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减小最大堆容量和减小栈容量来换取更多的线程了。

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

运行时常量池也是方法区的一部分,所以这两个区域一起看就可以了。这个区域的OutOfMemoryError可以利用String.intern()方法来产生。这是一个Native方法,意思是如果常量池中有一个String对象的字符串就返回池中的这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中去,并且返回此String对象的引用。测试代码如下

 1 package com.xrq.test;
 2
 3 import java.util.ArrayList;
 4 import java.util.List;
 5
 6 /**
 7  * 测试内容:常量池溢出(这个例子也可以说明运行时常量池为方法区的一部分)
 8  *
 9  * 虚拟机参数-XX:PermSize=10M -XX:MaxPermSize=10M
10  */
11 public class ConstantPoolOverflowTest
12 {
13     public static void main(String[] args)
14     {
15         List<String> list = new ArrayList<String>();
16         int i = 0;
17         while (true)
18         {
19             list.add(String.valueOf(i++).intern());
20         }
21     }
22 }

运行结果

Exception in thread "Reference Handler" Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)
    at com.xrq.test.ConstantPoolOverflowTest.main(ConstantPoolOverflowTest.java:19)
java.lang.OutOfMemoryError: PermGen space
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:123)

之前有讲过,对于HotSpot而言,方法区=永久代,这里看到OutOfMemoryError的区域是“PermGen space”,即永久代,那其实也就是方法区溢出了。注意一下JDK1.7下是不会有这个异常的,while循环将一直下去,因为JDK1.7之后溢出了永久代并采用Native Memory来实现方法区的规划了

时间: 2024-10-11 22:39:49

Java虚拟机学习4、内存溢出的相关文章

Java虚拟机6:内存溢出和内存泄露、并行和并发、Minor GC和Full GC、Client模式和Server模式的区别

http://www.cnblogs.com/xrq730/p/4839245.html 前言 之前的文章尤其是讲解GC的时候提到了很多的概念,比如内存溢出和内存泄露.并行与并发.Client模式和Server模式.Minor GC和Full GC,本文详细讲解下这些概念的区别. 内存溢出和内存泄露的区别 1.内存溢出 内存溢出指的是程序在申请内存的时候,没有足够大的空间可以分配了. 2.内存泄露 内存泄露指的是程序在申请内存之后,没有办法释放掉已经申请到内存,它始终占用着内存,即被分配的对象可

Java虚拟机4:内存溢出

http://www.cnblogs.com/xrq730/p/4833713.html 堆溢出 Java堆唯一的作用就是存储对象实例,只要保证不断创建对象并且对象不被回收,那么对象数量达到最大堆容量限制后就会产生内存溢出异常了.所以测试的时候把堆的大小固定住并且让堆不可扩展即可.测试代码如下 package com.xrq.test; import java.util.ArrayList; import java.util.List; /** * 测试内容:堆溢出 * * 虚拟机参数:-Xms

java虚拟机学习-JVM内存管理:深入Java内存区域与OOM(3)

概述 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又是执行最基础工作的劳动人民——拥有每一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任. 对于Java程序员来说,不需要在为每一个new操作去写配对的delete/free,不容易出现内容泄漏和内存溢出错误,看起来由JVM管理内存一切都很美好.不过,也正是因为Java程序员把内存控制的

Java虚拟机学习 - 体系结构 内存模型(1)

一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代" ."非堆",  它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小. 运行时常量池:是方法区的一部分,Class文件中除了有类的版本.字段.方法.接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加

java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项技术当做Java语言的伴生产物.事实上GC的历史远远比Java来得久远,在1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言.当Lisp还在胚胎时期,人们就在思考GC需要完成的3件事情:哪些内存需要回收?什么时候回收?怎么样回收? 经过半个世纪的发展,目前的内存分配策略

Java虚拟机学习 - 体系结构 内存模型

一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代" ."非堆",  它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小. 运行时常量池:是方法区的一部分,Class文件中除了有类的版本.字段.方法.接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加

Java虚拟机学习 - 体系结构 内存模型(转载)

一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代” .“非堆”,  它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小. 运行时常量池:是方法区的一部分,Class文件中除了有类的版本.字段.方法.接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中

java虚拟机学习-触摸java常量池(13)

java虚拟机学习-深入理解JVM(1) java虚拟机学习-慢慢琢磨JVM(2) java虚拟机学习-慢慢琢磨JVM(2-1)ClassLoader的工作机制 java虚拟机学习-JVM内存管理:深入Java内存区域与OOM(3) java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4) java虚拟机学习-JVM调优总结(5) java虚拟机学习-JVM调优总结(6) java虚拟机学习-JVM调优总结-基本垃圾回收算法(7) java虚拟机学习-JVM调优总结-垃圾回收面临的

深入理解java虚拟机学习 笔记 第二章 java 内存区域和内存溢出异常

2.2 运行时区域 java虚拟机划分成若干个不同的数据区域, 1.程序计数器,字节码解释器工作时就是通过改变计数器的值来取吓一跳需要执行的字节码命令 了解 String.intern()方法 作用: 如果字符串常量池中一个包含了一个等于此String对象的字符串,则返回代表池中的这个字符串的String对象,否则将此对象包含的字符串添加到常量池中,并返回此String对象的引用. 实战 将堆的最小值 -Xms参数与最大值-Xmx参数设置为一样即可避免堆自动扩展  通过参数 -XX:+HeapD

《深入Java虚拟机学习笔记》- 第7章 类型的生命周期

一.类型生命周期的开始 如图所示 初始化时机 所有Java虚拟机实现必须在每个类或接口首次主动使用时初始化: 以下几种情形符合主动使用的要求: 当创建某个类的新实例时(或者通过在字节码中执行new指令,或者通过不明确的创建.反射.克隆和反序列化): 当调用某个类的静态方法时(即在字节码中执行invokestatic指令): 当使用某个类或接口的静态字段,或者对该字段赋值时(用final修饰的静态字段除外,它被初始化为一个编译时常量表达式): 当调用Java API中的某些反射方法: 当初始化某个