VM学习记录(1)-虚拟机内存区域划分以及内存溢出

文章目录

      • 一、运行时数据区内存划分
        • 1.1、图示
        • 1.2、线程私有的内存区域
        • 1.3、线程共享的内存区域
        • 1.4、直接内存
      • 二、各内存区域的溢出异常
        • 2.0、虚拟机启动参数
        • 2.1、虚拟机栈溢出
        • 2.2、方法区和运行时常量池溢出
        • 2.3、Java堆溢出

Java虚拟机在执行Java程序的过程中会把它管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途以及创建和销毁的时间,有些区域会随着虚拟机进程的启动而存在,有的区域会依赖线程的创建和结束而建立和销毁。理解Java虚拟机内存区域的划分,会对对象的分配、内存的分配、堆栈溢出异常有进一步的了解。

【知识脑图】:http://naotu.baidu.com/file/0d12d244f1312a22495aa43e23844ead?token=3dbbf34935d1a82d
VM学习记录(1)-虚拟机内存区域划分以及内存溢出_第1张图片

一、运行时数据区内存划分

1.1、图示

虚拟机的运行时数据主要可以划分为如下几个主要的内存区域,绿色部分为线程共享区域,白色部分为线程私有区域,其他部分会继续在下文继续解读。
VM学习记录(1)-虚拟机内存区域划分以及内存溢出_第2张图片

1.2、线程私有的内存区域

线程私有的内存区域指的是每当一个线程创建时会开辟一块相应的内存空间,每个线程的私有空间数据不会共享。

  • 1、程序计数器
    程序计数器是一块很小的内存空间,它可以被看做是当前线程所执行的字节码行号指示器,线程需要执行的跳转、循环、中断等操作都需要依赖程序计数器去实现

  • 2、虚拟机栈
    虚拟机栈是线程私有的,与线程的生命周期相同(同生共死)。每个线程中的方法被创建时就会创建一个栈帧,用以存储局部变量表,方法出口信息,操作数栈等数据,每一个方法被调用直至执行完的过程都对应着一个栈帧的入栈和出栈的过程。
    开发者常说的“引用存储在栈中、实例存储在堆中“,其中的栈指的就是虚拟机栈中的局部变量表部分,而堆指的是Java堆。

  • 3、本地方法栈
    本地方法栈和虚拟机栈发挥的作用十分相似,不同的地方在于本地方法栈在线程调用本地方法时才会使用到。

1.3、线程共享的内存区域

线程共享的内存区域指的是方法区和Java堆区,这两个区域中的数据可以被不同的线程所访问到

  • 1、Java堆
    Java堆即平常说的堆区,它是虚拟机所管理的内存中最大的一块区域,Java堆区存在的唯一目的就是存放对象的实例,几乎所有的对象实例已经数组都会在堆上分配。
    由于Java堆是垃圾回收的主要区域,所以又被成为GC堆;由于现在的垃圾收集器都使用了分代回收算法,所以堆内存还能细分为新生代和老年代。(再细分可以分为Eden空间、Survivor空间等)。

  • 2、方法区
    方法区和Java堆一样是被所有线程所共享的区域,它用于存储虚拟机加载的类信息、常量和静态变量等信息;方法区有时又被称为永生代,因为这个区域不会频繁地发生垃圾回收。
    运行时常量池是方法区的一部分。Class文件中出了含有类的版本、字段和方法外,还有常量池,它用于存放编译器生成的各种字面量和符号引用,这部分内容会在类加载后进入运行时常量池中存放。

1.4、直接内存

在Java虚拟机中没有定义一块内存区域,它被称为直接内存,不属于虚拟机运行时数据区。在JDK1.4中新加入的NIO类引入了一种基于通道的I/O方式,它可以使用Native方法去直接调用直接内存,避免了在Java堆和Native堆之间频繁的复制数据,提高了运行效率。

二、各内存区域的溢出异常

由于各内存区域都存在内存的进出,故会存在内存的溢出异常,当然,程序计数器除外,因为它所占用的内存空间十分地小,并且不存在内存的出入,所以在Java虚拟机中没有对这块区域定义任何的内存异常。

2.0、虚拟机启动参数

Java虚拟机有许多启动参数可供我们配置,例如Java堆内存大小,虚拟机栈内存大小等,可以通过java -X命令查看:

arong@arong-ubuntu:~$ java -X
    -Xmixed           混合模式执行 (默认)
    -Xint             仅解释模式执行
    -Xbootclasspath:<: 分隔的目录和 zip/jar 文件>
                      设置搜索路径以引导类和资源
    -Xbootclasspath/a:<: 分隔的目录和 zip/jar 文件>
                      附加在引导类路径末尾
    -Xbootclasspath/p:<: 分隔的目录和 zip/jar 文件>
                      置于引导类路径之前
    -Xdiag            显示附加诊断消息
    -Xnoclassgc       禁用类垃圾收集
    -Xincgc           启用增量垃圾收集
    -Xloggc:<file>    将 GC 状态记录在文件中 (带时间戳)
    -Xbatch           禁用后台编译
    -Xms<size>        设置初始 Java 堆大小
    -Xmx<size>        设置最大 Java 堆大小
    -Xss<size>        设置 Java 线程堆栈大小
    -Xprof            输出 cpu 配置文件数据
    -Xfuture          启用最严格的检查, 预期将来的默认值
    -Xrs              减少 Java/VM 对操作系统信号的使用 (请参阅文档)
    -Xcheck:jni       对 JNI 函数执行其他检查
    -Xshare:off       不尝试使用共享类数据
    -Xshare:auto      在可能的情况下使用共享类数据 (默认)
    -Xshare:on        要求使用共享类数据, 否则将失败。
    -XshowSettings    显示所有设置并继续
    -XshowSettings:all
                      显示所有设置并继续
    -XshowSettings:vm 显示所有与 vm 相关的设置并继续
    -XshowSettings:properties
                      显示所有属性设置并继续
    -XshowSettings:locale
                      显示所有与区域设置相关的设置并继续

-X 选项是非标准选项, 如有更改, 恕不另行通知。

2.1、虚拟机栈溢出

虚拟机栈中被规定了两种异常:StackOverflowError以及OutOfMemoryError。

  • StackOverflowError

当线程请求的栈深度大于虚拟机所允许的深度时,无法将栈帧入栈,这时将抛出StackoverflowError异常,即当一个线程反复地调用方法时(每创建和结束一个方法对应着栈帧入栈和出栈的过程)可能会抛出这个异常,这在递归调用时会出现:

public class VMStackError {
    int stackLength = 1;

    public static void main(String[] args) {
        VMStackError vmStackError = new VMStackError();
        vmStackError.stackLeak();
    }

    public void stackLeak() {
        System.out.println(stackLength);
        //栈帧数量
        stackLength++;
        //递归调用
        stackLeak();
    }
}

运行main方法后抛出StackOverflowError异常,可以看到默认的配置下,当一个线程占用的虚拟机栈栈深度是10000左右,当达到这个限制时就会无法入栈。
VM学习记录(1)-虚拟机内存区域划分以及内存溢出_第3张图片
现在使用虚拟机启动参数-Xss228k指定虚拟机栈内存为228k,运行main方法,发现只能存在1500左右个栈帧,说明启动参数生效了。

VM学习记录(1)-虚拟机内存区域划分以及内存溢出_第4张图片

  • OutOfMemoryError
    这个异常发生在虚拟机在扩展栈时无法申请到足够的内存空间,也就是启动的线程过多,无法为每个线程都申请到一个虚拟机栈。

2.2、方法区和运行时常量池溢出

由于运行时常量池属于方法区的一部分,所以可以将两者放在一起测试。我们可以使用参数-XX:MaxPermSize来限制方法区的大小从而现在运行时常量池的大小,为了占用运行时常量池的内存,我们可以使用String.intern()方法,为没有在运行时常量池值的对象添加进去。

public class MethodAreaLeak {
    private static int i = 0;
    //-XX:MaxPermSize128k
    //-XX:PermSize128k
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        while (true) {
            list.add(String.valueOf(i).intern());
        }
    }
}

在这里插入图片描述

2.3、Java堆溢出

Java堆存放着绝大部分的对象实例,我们可以通过-Xms-Xmx参数去控制Java堆内存的初始化大小和最大值

public class JavaHeap {
    //-Xms1024k
    //-Xmx1024k
    public static void main(String[] args) {
        ArrayList<Object> list = new ArrayList<>();
        while (true) {
            list.add(new JavaHeap());
        }
    }
}

运行main方法,出现Java堆内存溢出
在这里插入图片描述

你可能感兴趣的:(VM学习记录(1)-虚拟机内存区域划分以及内存溢出)