美文网首页Java 杂谈
Java虚拟机 - 内存区域

Java虚拟机 - 内存区域

作者: AaricChen | 来源:发表于2019-08-05 22:40 被阅读78次

    做有关 Java 程序的开发一定知道Java的内存自动管理,不用再像 C/C++ 程序那样手动控制(分配和释放)内存。开发人员可以把更多的精力放到业务的开发上面,但是这种内存的自动管理功能有利也有弊,当程序出现内存泄漏或内存溢出的问题时,如果不熟悉 Java 虚拟机(JVM - Java Virtual Machine)的内存模型,往往很难排查问题的根源。

    掌握 Java 虚拟机的相关知识可以说是中级程序员必备的一项技能。另一方面,Java 虚拟机作为面试官的必问题目,熟悉相关的知识也是不错的充电和储备。

    本文包括了 Java 虚拟机的内存区域和有关异常的内容。

    运行时数据区

    JVM 在运行的时候会把祂管理的内存划分为几个不同的数据区域,这些区域都有各自的用于,以及创建和销毁时间。

    根据《Java 虚拟机规范》的定义,JVM 管理的内存将包括以下几个区域

    JVM 内存区域

    程序计数器

    程序计数器是一块较小的内存空间,可以看作是当前 线程 所执行的字节码的 行号 指示器。这句话不太好懂,个人理解,程序计数器其实就是用来记录 每个线程 执行到什么地方了,程序计数器是 线程私有 的,及内个线程拥有自己的内存空间,如上图所示,程序计数器属于 线程隔离数据区

    • 如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址
    • 如果线程正在执行的是一个本地(Native)方法,这个计数器的值则为 未定义(Undefined)

    Java 虚拟机栈

    和程序计数器一样,Java 虚拟机栈也是 线程私有 的,它的生命周期与线程相同,随着线程的创建而创建,销毁而销毁。
    虚拟机栈描述的是 Java方法 执行的内存模型:每个方法在执行的同时都会创建一个 栈帧(Stack Frame) 用于存储 局部变量表、操作数栈、动态链接、方法出口 等信息。每个方法从调用到执行完成的过程,就对应一个 栈帧虚拟机栈 中入栈和出栈的过程。

    该内存区域会抛出两种异常:

    • 如果线程请求的栈深度大于虚拟机允许的深度,将抛出 StackOverflowError 异常。
    • 如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,将抛出 OutOfMemoryError 异常。

    下面通过代码来演示

    public class StackOverflowTest {
        public static void main(String[] args) {
            new Test().stackOverflow();
        }
        private static class Test {
            public void stackOverflow() {
                stackOverflow();
            }
        }
    }
    

    运行以上代码将会得到 java.lang.StackOverflowError

    本地方法栈

    本地方法栈 与 Java 虚拟机栈非常相似,只不过不是执行的 Java 方法,而是 本地方法。最常见的 JVM 实现 HotSpot,将 Java虚拟机栈 和 本地方法栈 合二为一,即为同一内存区域。

    Java 堆

    Java 堆
    • 堆(Heap) 应该是大家接触的最多一个内存区域,也是 JVM 中管理内存最大的一块。
    • 堆 是 线程共享 的内存区域,即每个线程都可以访问到该内存区域。
    • 堆 的唯一作用就是用来存放 Java 程序中的 对象(实例)
    • 堆 也是 垃圾收集器 管理的主要区域,有时也会被称为 GC堆
    • 堆还可以划分为:新生代老年代
    • 新生代还可以划分为:Eden 空间、From Survivor 空间 和 To Survivor 空间
    • 堆 中还可以划分出多个 线程私有 的分配缓冲区(Thread Local Allocation Buffer,TLAB)
    • 划分空间的作用时为了更有效的进行垃圾回收,不论怎样划分,堆 内存空间里面存储的都是 对象(实例)
    • 如果堆中没有内存再分配新的对象时,会抛出 OutOfMemoryError 异常

    下面通过代码来演示

    public class OutOfMemoryTest {
        public static void main(String[] args) {
            List<String> strings = new ArrayList<>();
            while (true) {
                strings.add("");
            }
        }
    }
    

    运行以上代码将会得到

    Java 堆异常
    注意:异常错误信息中显示了内存溢出的区域为:Java heap space 即 Java 堆内存空间

    方法区

    方法区
    • 与 堆 一样 方法区 也是 线程共享 的内存区域
    • 该区域存储 已加载的类信息、常亮、静态变量、即时编译器(JIT)编译后的代码 等数据
    • 当方法区中没有内存再分配新的对象时,会抛出 OutOfMemoryError 异常
    • 方法区中有一部分是 运行时常量池,该区域用于存放编译时期生成的各种字面量和符号引用,这部分内容将在类加载以后存放于方法区的 运行时常量池

    HotSpot 虚拟机

    上面介绍了 JVM 运行时数据区,我们通过下面的图再回顾一下

    JVM运行时数据区

    这些都是 JVM 规范所定义的,接下来说一说业界用的最多的 JVM 实现 -- HotSpot 的相关内容

    关于如何查看使用的是什么 JVM 可以使用如下命令

    java -version
    

    如在 Windows 系统下会打印出以下内容

    $ java -version
    java version "1.8.0_191"
    Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
    Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
    

    在 Linux 系统下会打印出以下内容

    $ java -version
    openjdk version "1.8.0_171"
    OpenJDK Runtime Environment (build 1.8.0_171-b10)
    OpenJDK 64-Bit Server VM (build 25.171-b10, mixed mode)
    

    资料来源:周志明 ——《深入理解 Java 虚拟机》

    相关文章

      网友评论

        本文标题:Java虚拟机 - 内存区域

        本文链接:https://www.haomeiwen.com/subject/wpbvdctx.html