JAVA内存区域与内存溢出异常

 

1,运行时数据区域

根据JAVA虚拟机规范的规定:JAVA虚拟机所管理的内存将会包括以下几个运行时数据区域

 

 

程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器,通过改变计数器的值来选取下一条需要执行的字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能。每条线程都需要一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存,也是唯一不会出现OutOfMemoryError情况的区域。

 

JAVA虚拟机栈(Java Virtual Machine Stacks)也是线程私有,它的生命周期与线程相同,用来描述JAVA方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口等。每一个方法从被调用到执行完成的过程,也就一个栈帧在虚拟机栈从入栈到出栈的过程。

    在JAVA虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。如果虚拟机可以动态扩展,当扩展到无法申请到足够的内存时,会抛出OutOfMemoryError异常。

本地方法栈(Native Method Stacks)与上述的虚拟机栈非常类似,只是虚拟机栈为执行JAVA方法服务,而本地方法栈为虚拟机使用到的Native方法服务。

 

 JAVA堆(Java Heap)是Java虚拟机所管理内存中最大的一块,被所有线程共享,在虚拟机启动时创建,此内存区域的唯一目的就是为了存放对象实例。JAVA堆是垃圾回收器管理的主要区域。如果堆中没有足够的内存完成实例分配,并且堆无法扩展时,将会抛出OutOfMemoryError异常。

 

方法区(Method Area)方法区也被称为“持久代”,此内存区域与堆一样,也是线程共享的。它用于存储已被虚拟机加载的类(java.lang.Class)信息、常量、静态变量、即时编译器编译后的代码等数据。垃圾回收行为在这个区域是比较少见的,并且可以选择不回收。

当此区域无法满足内存分配需求时,将抛出OutOfMemoryError异常。

 

运行时常量池(Runtime Constant Pool)是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池。当常量池无法再分配到内存时,也会抛出OutOfMemoryError异常。

 

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是JAVA虚拟机规范中定义的内存区域,但是这部分内存也频繁被使用,并且也可能抛出OutOfMemoryError异常。如NIO可以使用Native函数库直接分配堆外内存。

 

2,对象访问

介绍完JAVA运行时数据区域后,再看看JAVA对象访问是如何进行的。

即使是最简单的访问,也会涉及JAVA栈、JAVA堆、方法区三个最重要的内存区域,如下代码

Java代码 
  1. Object obj = new Object();  

假设这段代码出现在方法体中,那"Object obj"这部分的语义将会反映到JAVA栈的局部变量表中,作为一个reference类型的数据出现,因此就存在虚拟机栈中。 而"new Object();"这部分的语义将会反映到JAVA中,形成一个存储了Object类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存。另外,在JAVA堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。

  由于reference类型在JAVA虚拟机规范中只规定了一个指向对象的引用,并没有规定用哪种方式去实现,因此不同的虚拟机实现的方式会有所不同,主流的访问方式有两种:使用句柄与使用指针:

 

句柄:JAVA堆中会划分出一块内存来作为字柄池,reference存放的是对象的句柄地址,而句柄中包含了对象实例和类型数据各自的具体地址信息: 如下图所示 :

 

指针:如果使用指针访问方式,JAVA堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中直接存储的就是对象地址

这两种方式各有优势,使用句柄访问最大的好处就是reference存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要被修改。使用指针的好处就是速度快,节省一次指针定位的时间开销。Sun HotSpot虚拟机使用第二种方式进行对象访问。

 

3,实战:OutOfMemoryError异常

 在JAVA虚拟机规范描述中:除了程序计数器外,其它几个内存区域都有发生OutOfMemoryError异常的可能,本节通过若干实例来验证异常发生的场景。

注意:每个示例代码的开头都会注明虚拟机启动参数的设置,具体设置方法如下图:

 

3.1:JAVA堆溢出:

Java代码 
  1. package com.chapter1;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. /** 
  7.  * -Xms20m -Xmx20m  
  8.  */  
  9. public class HeapOOm {  
  10.     static class OOmObject{  
  11.     }  
  12.     public static void main(String[] args) {  
  13.         List<OOmObject> oomList = new ArrayList<OOmObject>();  
  14.         while(true){  
  15.             oomList.add(new OOmObject());//不断生成新对象  
  16.         }  
  17.     }  
  18. }  

 

运行结果:

 

-Xms表示堆内存的最小值,-Xmx表示堆内存的最大值。
上例通过不断生成新对象,导致内存溢出。

 

3.2虚拟机栈和本地方法栈溢出

如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常

如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常

一般在单线程程序情况下无法产生OutOfMemoryError异常

 

Java代码 
  1. package com.chapter1;  
  2.   
  3. /** 
  4.  * -Xss128k  
  5.  */  
  6. public class JavaVMStackSOF {  
  7.     private int stackLength = 1;  
  8.     public void stackLeak(){  
  9.         stackLength ++;  
  10.         stackLeak();  
  11.     }  
  12.     public static void main(String[] args) throws Throwable {  
  13.         JavaVMStackSOF oom = new JavaVMStackSOF();  
  14.         try {  
  15.             oom.stackLeak();  
  16.         } catch (Exception e) {  
  17.             System.out.println("stack lenght:"+oom.stackLength);  
  18.             throw e;  
  19.         }  
  20.     }  
  21. }  

 

运行结果:

 

下面这个示例,尝试使用多线程方式得到OutOfMemeoryError的结果, -Xss参数是用于设置每个线程的堆栈大小。

Java代码 
  1. package com.chapter1;  
  2.   
  3. /** 
  4.  *-Xss2M  
  5.  */  
  6. public class JavaVMStackOOM {  
  7.     private void dontStop(){  
  8.         while (true) {  
  9.         }  
  10.     }  
  11.     public void stackLeakByThread(){  
  12.         int i = 0;  
  13.         while(true){  
  14.             System.out.println(i++);  
  15.             Thread thread = new Thread(new Runnable() {  
  16.                 public void run() {  
  17.                     dontStop();  
  18.                 }  
  19.             });  
  20.             thread.start();  
  21.         }  
  22.     }  
  23.     public static void main(String[] args) {  
  24.         JavaVMStackOOM oom = new JavaVMStackOOM();  
  25.         oom.stackLeakByThread();  
  26.     }  
  27. }  

 

 

3.3运行时常量池溢出(也是方法区的一部分)

Java代码 
  1. package com.chapter1;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. /** 
  7.  *-XX:PermSize=10M -XX:MaxPermSize=10M  
  8.  */  
  9. public class RuntimeConstantPoolOOM {  
  10.     public static void main(String[] args) {  
  11.         List<String> list = new ArrayList<String>();  
  12.         int i = 0;  
  13.         while(true){  
  14.             System.out.println(i);  
  15.             list.add(String.valueOf(i++).intern());  
  16.         }  
  17.     }  
  18. }  

 运行结果:

3.4方法区溢出:

方法区用于存放Class的相关信息,如类名,访问修饰符,常量池,字段描述,方法描述等。对于这个区域的测试,大概思路是运行时产生大量的类去填满方法区,直到溢出,本例使用CGLib直接操作字节码,生成大量动态类

Java代码 
  1. package com.chapter1;  
  2.   
  3. import java.lang.reflect.Method;  
  4. import net.sf.cglib.proxy.Enhancer;  
  5. import net.sf.cglib.proxy.MethodInterceptor;  
  6. import net.sf.cglib.proxy.MethodProxy;  
  7.   
  8. /** 
  9.  *-XX:PermSize=10M -XX:MaxPermSize=10M  
  10.  */  
  11. public class JavaMethodAreaOOM {  
  12.     public static void main(String[] args) {  
  13.         while(true){  
  14.             Enhancer enhancer = new Enhancer();  
  15.             enhancer.setSuperclass(OOMObject.class);  
  16.             enhancer.setUseCache(false);  
  17.             enhancer.setCallback(new MethodInterceptor() {  
  18.                 public Object intercept(Object obj, Method method, Object[] args,  
  19.                         MethodProxy proxy) throws Throwable {  
  20.                     return proxy.invokeSuper(obj, args);  
  21.                 }  
  22.             });  
  23.             enhancer.create();  
  24.         }  
  25.     }  
  26.     static class OOMObject{  
  27.     }  
  28. }  

 由于CGLib的原因,本例在我电脑上并未调试通过。

3.5 本机直接内存溢出

DirectMemory容量可以通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与JAVA堆的最大值一样,

Java代码 
  1. package com.chapter1;  
  2.   
  3. import java.lang.reflect.Field;  
  4. import sun.misc.*;  
  5. /** 
  6.  *-Xmx20M -XX:MaxDirectMemorySize=10M  
  7.  */  
  8. public class DirectMemoryOOM {  
  9.     private static final int _1MB = 1024*1024;  
  10.     public static void main(String[] args) {  
  11.         Field unsafeField = Unsafe.class.getDeclaredFields()[0];  
  12.     }  
  13. }  

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值