• 第2章 Java内存区域与内存溢出异常


    学习目标:明白虚拟机里面的内存是如何划分的,哪部分区域,什么样的代码和操作可能会导致内存溢出异常。

     

    2.2 运行时数据区域

     

    2.2.1 程序计数器

    是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。
     

    2.2.2 java 虚拟机栈

        每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表,操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
        局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成分配
        其中的两种异常情况:1、如果线程请求的栈深度大于虚拟机所允许的深度,将抛出stackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
     

    2.2.4 Java堆

        他是Java虚拟机所管理的内容中最大的一块。被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例与数组都在这里分配内存。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存完成实例分配,并且堆也无法在扩展时,将会抛出OutOfMemoryError异常。
     

    2.2.5 方法区

        与Java堆一样是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。但是这部分的回收确实是有必要的。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
     
        使用句柄访问方式的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。
        使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销。
     

    2.4.1 Java堆溢出

        Java堆用于存储对象实例,我们只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量到达最大堆的容量限制后产生内存溢出异常。
        如果不存在泄露,换句话说就是内存中的对象确实都还必须存活着,那就应当检查虚拟机的堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长,持有状态时间过长到的情况,尝试减少程序运行期的内存消耗。
     
    Java堆内存溢出异常测试
    /*
     * vm Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
     * 
     */
    import java.util.ArrayList;
    import java.util.List;
     
    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
            at java.util.Arrays.copyOf(Unknown Source)
            at java.util.Arrays.copyOf(Unknown Source)
            at java.util.ArrayList.grow(Unknown Source)
            at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
            at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
            at java.util.ArrayList.add(Unknown Source)
            at HeapOOM$OOMobject.main(HeapOOM.java:11)
     
    虚拟机栈和本地方法栈OOM测试
    /*
     * 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){
                System.out.println("stack length:" + oom.stackLength);
                throw e;
            }
        }
     
    }
     
    运行结果:
    stack length:1000
    Exception in thread "main" java.lang.StackOverflowError
        at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:9)
        at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
        at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
        at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
        at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
        at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)
    -Xoss参数(设置本地方法栈大小)        -Xss参数(设置栈容量)
     
    运行时常量池导致的内存溢出异常
    /*VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
    */
    import java.util.ArrayList;
    import java.util.List;
     
    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){
                list.add(String.valueOf(i++).intern());
            }
        }
    }
     
    运行结果:
    程序运行了20分钟,都没有结果
     
     
     
     
  • 相关阅读:
    (转)Linux端口nmap和netstat命令
    (转)Linux下增加交换分区的大小
    (转)centos7安装telnet服务
    (转)VmWare下安装CentOS7图文安装教程
    (转)Linux系统安装时分区的选择
    (转)硬盘分区备忘(主分区,扩展分区和逻辑分区)以及Linux硬盘分区工具parted 介绍
    (转).tar.gz文件和.rpm文件的区别
    (转)Linux(Centos)之安装Java JDK及注意事项
    ibatis中integer类型
    ibatis时间比较大小
  • 原文地址:https://www.cnblogs.com/zhoujunok/p/11652969.html
Copyright © 2020-2023  润新知