zoukankan      html  css  js  c++  java
  • JVM基础知识1--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函数库直接分配堆外内存。

    例子:

    Object obj = new Object(); 
    

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

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

    2,实战:OutOfMemoryError异常

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

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

     

    JAVA堆溢出(-Xms表示堆内存的初始值,-Xmx表示堆内存的最大值。)

    //-Xms20m -Xmx20m 
    public class HeapOOm {
        static class OOmObject{
        }
        public static void main(String[] args) {
            List<OOmObject> oomList = new ArrayList<OOmObject>();
            while(true){
                oomList.add(new OOmObject());//不断生成新对象
            }
        }
    }

     上例通过不断生成新对象,导致内存溢出。运行结果如下

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

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

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

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

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

      

     下面这个示例,尝试使用多线程方式得到OutOfMemeoryError的结果,因为栈是线程私有的,线程多也会方法区溢出。

    //-Xss2M 
    public class JavaVMStackOOM {
        private void dontStop(){
            while (true) {
            }
        }
        public void stackLeakByThread(){
            int i = 0;
            while(true){
                System.out.println(i++);
                Thread thread = new Thread(new Runnable() {
                    public void run() {
                        dontStop();
                    }
                });
                thread.start();
            }
        }
        public static void main(String[] args) {
            JavaVMStackOOM oom = new JavaVMStackOOM();
            oom.stackLeakByThread();
        }
    }

      运行时常量池溢出

    //-XX:PermSize=10M -XX:MaxPermSize=10M 
    public class RuntimeConstantPoolOOM {
        public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
            int i = 0;
            while(true){
                System.out.println(i);
                list.add(String.valueOf(i++).intern());
            }
        }
    }

     运行结果

     String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;

     用new String("xxxx") 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。

    关于JAVA字符串常量池可以看下面这个介绍,很详细

    http://blog.csdn.net/longtenggdf/article/details/4606225

     方法区溢出:

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

    import java.lang.reflect.Method;
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    //-XX:PermSize=10M -XX:MaxPermSize=10M 
    public class JavaMethodAreaOOM {
        public static void main(String[] args) {
            while(true){
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMObject.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
                        return proxy.invokeSuper(obj, args);
                    }
                });
                enhancer.create();
            }
        }
        static class OOMObject{
        }
    }

      本机直接内存溢出

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

    import java.lang.reflect.Field;
    import sun.misc.*;
    //-Xmx20M -XX:MaxDirectMemorySize=10M 
    public class DirectMemoryOOM {
        private static final int _1MB = 1024*1024;
        public static void main(String[] args) {
            Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        }
    }
  • 相关阅读:
    [LeetCode] Power of Three 判断3的次方数
    [LeetCode] 322. Coin Change 硬币找零
    [LeetCode] 321. Create Maximum Number 创建最大数
    ITK 3.20.1 VS2010 Configuration 配置
    VTK 5.10.1 VS2010 Configuration 配置
    FLTK 1.3.3 MinGW 4.9.1 Configuration 配置
    FLTK 1.1.10 VS2010 Configuration 配置
    Inheritance, Association, Aggregation, and Composition 类的继承,关联,聚合和组合的区别
    [LeetCode] Bulb Switcher 灯泡开关
    [LeetCode] Maximum Product of Word Lengths 单词长度的最大积
  • 原文地址:https://www.cnblogs.com/pingh/p/3479499.html
Copyright © 2011-2022 走看看