• 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];
        }
    }
  • 相关阅读:
    【POJ 1655】Balancing Act
    【POJ 2631】 Roads in the North
    【洛谷 1002】 过河卒
    【洛谷 3178】树上操作
    【洛谷 1060】开心的金明
    【洛谷 2709】小B的询问
    【洛谷 1972】HH的项链
    JavaSpark-sparkSQL
    java8下spark-streaming结合kafka编程(spark 2.3 kafka 0.10)
    Kafka 使用Java实现数据的生产和消费demo
  • 原文地址:https://www.cnblogs.com/pingh/p/3479499.html
Copyright © 2020-2023  润新知