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

一、Java 虚拟机运行时数据区域:

1、方法区(Method Area)

2、虚拟机栈(VM Stack)

3、本地方法栈(Native Method Stack)

4、堆(Heap)

5、程序计数器(Program Couter Register)

 

程序计数器(Program Couter Register):较小的内存空间,它的作用可以看做是当前线程所执行的字节码指示器。每个线程都有独立的程序计数器。(线程私有)

虚拟机栈(VM Stack):Java方法执行的内存模型,每个方法被执行的时都会创建一个栈帧,存储局部变量,操作栈,动态链接,方法出口等,每个方法被调用至执行完成的过程对应一个栈帧在虚拟机栈从如栈到出栈的过程,常说的Java内存分为堆和栈(这种方法比较粗糙),这里的栈就是指虚拟机栈(线程私有,与线程生命周期相同)

本地方法栈(Native Method Stack):与虚拟机栈发挥的作用非常相似,区别在于虚拟机栈为虚拟机执行Java方法(字节码)服务。而本地方法栈是为虚拟机使用到的Native方法服务。(虚拟机)

堆(Heap):java虚拟机管理的内存中最大的一块,被所有线程共享,存放对象实例(所有线程共享)

方法区(Method Area):存储虚拟机加载的类信息,常量,静态变量(线程共享)

 

----------------------------------------------------------------------------------------------------------

直接内存(Direct Memory):并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。

在JDK中NIO引入一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它直接使用Native函数库直接分配堆外内存,然后存储在Java堆里面的DirectByteBuffer对象作为内存引用,这样能显著提高性能,避免在Java堆和Native堆中来回复制数据。

 

如何访问对象?

Object obj = new Object();

其中Object obj会存储在虚拟机栈的本地变量表中,作为一个reference类型数据出现,而new Object()存储在Java堆中。

句柄访问对象:Java堆中划出一块内存作为句柄池,reference存储对象的句柄地址。如图:

 

直接使用指针访问对象:reference中直接存储对象地址,如图:

Java虚拟机使用指针直接访问对象(第二种访问方式)。

 

二、内存溢出

Java堆溢出:对象数量达到最大堆内存容量后产生内存溢出异常

设置Java堆大小:选择run configurations——>(x)=Arguments——>VM argmuments:
-verbose:gv -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

将堆的最小值-Xms参数与最大值-Xmx参数设置为一样即可避免堆自动扩展

Java堆内存溢出异常测试代码:

/** VM Args:-Xms20m -Xms20m -XX:+HeapDumpOnOutofMemoryError */
public
class HeapOOM { static class OOMObject{ } public static void main(String[] args) { List<OOMObject> list = new ArrayList<OOMObject>(); while(true){ list.add(new OOMObject()); } } }

 

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

要解决这个异常,一般通过内存影像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照分析,分析是内存泄露(GC无法回收),还是内存溢出。

 

虚拟机栈和本地方法栈溢出:-Xoss 参数(设置本地方法栈大小),-Xss 参数(设置虚拟机栈大小)

虚拟机栈和本地方法栈溢出会产生两种异常:

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

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

虚拟机栈和本地方法栈溢出测试代码:

/** VM Args:-Xss128k */
public
class JavaVMStackSOF { private int stackLength = 1; public void stackLeak(){ stackLength++; stackLeak(); } public static void main(String[] args) { JavaVMStackSOF oom = new JavaVMStackSOF(); try{ oom.stackLeak(); }catch(Throwable e){ e.printStackTrace(); System.out.println("stack length:" + oom.stackLength); } } }

运行结果:

java.lang.StackOverflowError

at aaa.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:9)

stack length:9430

 

运行时常量池溢出:由于常量池分配在方法区内,通过-XX:PermSize和-XX:MaxPermSize限制方法区大小。间接限制常量池的容量

运行时常量池溢出测试代码:

/**
 * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
 */
public class RuntimeConstantPoolOOM {

    public static void main(String[] args) {
        //使用List保持着常量池引用,避免Full GC回收常量池行为
        List<String> list = new ArrayList<String>();
        //10MB的PermSize 在integer范围内足够产生OOM了
        int i = 0;
        while(true){
            //String.intern()这个Native方法:如果池中已经包含一个等于此String对象的
            //字符串,则返回代表池中这个字符串的String对象,否则,将此String对象包含的字符串
            //添加到常量池中,并且返回此String对象的引用
            list.add(String.valueOf(i++).intern());
        }

    }

}

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at aaa.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:21)

 

方法区溢出:方法区存放Class的相关信息,如类名,访问修饰符,常量池,字段描述,方法描述等。

 

本机直接内存溢出:DirectMemory容量可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值(-Xmx指定)一样。

本机直接内存溢出测试代码:

/**
 * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
 */
public class DirectMemoryOOM {
    
    private static final int _1MB = 1024 * 1024;

    public static void main(String[] args) {
        Field unsafaField = Unsafe.class.getDeclaredFields()[0];
        unsafaField.setAccessible(true);
        Unsafe unsafe = (Unsafe)unsafaField.get(null);
        while(true){
            unsafe.allocateMemory(_1MB)
        }
    }

}

运行结果:

Exception in thread "main" java.land.OutOfMemoryError

posted @ 2013-09-04 11:51  独行码夫  阅读(1182)  评论(0编辑  收藏  举报