java内存溢出
虚拟机运行时涉及到几个运行时区域,其中除了程序计数器外,其他几个都会产生内存泄漏,也就是OutOfMemoryError异常的可能。下面将分别介绍举例说明各区域的溢出状态
Java堆溢出
堆是存储对象的,所以只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收器清除对象。
1 public class HeapOOM{ 2 static class OOMObject{ 3 } 4 public static void main(String[] args){ 5 List<OOMObject> list = new ArrayList<OOMObject>(); 6 while(){ 7 list.add(new OOMObject()); 8 } 9 } 10 }
其中可以设置参数-XX:+HeapDumpOnOutOfMemoryError在虚拟机再出现内存溢出时,可以Dump出当前的内存堆转储快照以便事后分析。
要解决堆出现的异常,一般手段是先通过内存映像分析工具(Eclipse Memory Analyzer)对Dump出来的堆转储快照进行分析。首先要分清内存中的对象是否是必要的,也就是要分清到底是出现内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
内存溢出 Memory Overflow,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露 Memory Leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
内存泄漏最终还会导致内存溢出的错误,即out of memory
如果是内存泄漏,可根据工具查看泄漏对象到GC Roots的引用链。于是就可以找到泄漏对象是通过怎样的路径与GC Roots相关联并导致垃圾回收器无法自动的回收他们。
如果不是泄漏,就是对象的所需内存太大,看看物理内存是否可以调大,也就是检查虚拟机参数(-Xmx与-Xms)
虚拟机栈溢出和本地方法栈溢出
虚拟机栈中主要存放:用栈帧存放局部变量表、操作数栈,动态链接、方法出口信息等
局部变量表:主要存放基本数据类型,对象的引用和指向下一条字节码指令的地址
Java虚拟机规范中描述了两种异常:
1.如果线程的请求栈的深度大于虚拟机所允许的最大深度,将抛出StackOverflowError
2.如果虚拟机在扩展栈时无法申请到足够的内存空间,将抛出OutOfMemoryError
参数-Xss是调整栈的容量
列1 StackOverflowError
1 public class JavaStack{ 2 private int stackLength = 1; 3 public void stackLeak(){ 4 stackLength++; 5 stackLaek(); 6 } 7 public static void main(String[] args){ 8 JavaStack oo = new JavaStack(); 9 oo.stackLaek(); 10 } 11 }
虚拟机栈在单线程的情况下,无论是栈帧太大还是虚拟机栈的容量太小,当内存无法分配的时候,虚拟机都是抛出StackOverflowError
在多线程的情况下,通过不断的创建线程的方式可以产生内存溢出的可能。其原因是操作系统分配给每个进程的内存的大小是有限的。例如在32为系统中,虚拟机提供了参数来控制Java堆和方法区的这两个部分内存的最大值。每个线程分配到的栈容量越大,可以建立的线程数就越少,建立线程时就越容易把剩下的内存耗尽。
方法区和运行时常量池溢出
主要存储的是已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。也就是Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述。对于这些区域的测试的基本思路是运行时产生大量的类去填满方法区。
运行时常量池:用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区运行时常量池中存放
-XX:PermSize和-XX:MaxPermSize限制方法区的大小