JVM运行时区域
条评论JVM运行时内存区域
我们以HotSpot虚拟机为前提下展开,因为目前使用最多的还是HotSpot虚拟机。
Java虚拟机在执行Java程序时,会将分配给JVM的内存划分为几个不同的区域。有些区域在JVM启动之后就存在,直到关闭JVM进程;有些区域则依赖于用户线程,随着用户线程的生命周期一同创建和销毁。
从Java1.8开始,JVM内存区域划分如图所示,从图中我们可以看出:

- JVM堆中的数据是共享的,是占用内存最大的一块区域,是垃圾收集器管理的内存区域。
- 可以执行字节码的模块叫作执行引擎。
- 执行引擎在线程切换时依靠的就是程序计数器。
- JVM 的内存划分与多线程是息息相关的。像我们程序中运行时用到的栈,以及本地方法栈,它们的维度都是线程。
- 本地内存包含元数据区和一些直接内存。
虚拟机栈
Java虚拟机栈是基于线程的。哪怕你只有一个main() 方法,也是以线程的方式运行的。在线程的生命周期中,参与计算的数据会频繁地入栈和出栈,栈的生命周期是和线程一样的。
栈里的每条数据,就是栈帧。在每个Java 方法被调用的时候,都会创建一个栈帧,并入栈。一旦完成相应的调用,则出栈。所有的栈帧都出栈后,线程也就结束了。每个栈帧,都包含四个区域:
- 局部变量表
- 操作数栈
- 动态连接
- 返回地址
我们的应用程序,就是在不断操作这些内存空间中完成的。

这里有一个比较特殊的数据类型叫作 returnAdress。因为这种类型只存在于字节码层面,所以我们平常打交道的比较少。对于 JVM 来说,程序就是存储在方法区的字节码指令,而 returnAddress 类型的值就是指向特定指令内存地址的指针。

如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。
本地方法栈
本地方法栈是和虚拟机栈非常相似的一个区域,唯一的区别是虚拟机栈是为Java方法服务,而本地方法栈是为本地方法(Java Native Method)服务的。
与虚拟机栈一样,本地方法栈也会在同样情况下会抛出相同异常。
程序计数器
程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。这里面存的,就是当前线程执行的进度。各个线程都有自己的程序计数器,所以这块是线程私有的。并且程序计数器也是唯一不会发生内存溢出的区域。
程序计数器与虚拟机栈配合完成计算操作。程序计数器还存储了当前正在运行的流程,包括正在执行的指令、跳转、分支、循环、异常处理等。
堆

堆是JVM上最大的内存区域,我们申请的几乎所有的对象,都是在这里存储的(还存储着数组、字符串常量池、静态变量)。我们常说的垃圾回收,操作的对象就是堆。
这里讲两个问题:
1)一个对象创建的时候,到底是在堆上分配,还是在栈上分配呢?
1 | 这和两个方面有关:对象的类型和在Java类中存在的位置。 |
2)堆是所有线程共享的,如果是多个线程访问,会涉及并发安全问题。
1 | HotSpot虚拟机中采用`TLAB`的方法进行内存分配的。 |
如果在堆中没有内存完成实例分配,并且堆也无法再拓展时,Java虚拟机会抛出OOM(OutOfMemoryError)异常。
方法区
方法区是JVM定义的规范概念,用于存储类信息(类级别的结构信息)、运行时常量池、字节码等数据,具体放在哪儿,不同的实现可以放在不同的地方。

1.7及之前版本的方法区
这里说一下Java1.7及之前的运行内存区域是长这样的:

可以看到它是和堆内存连在一起的,其实就是从堆内存中特意拿出一块区域,把它命名为方法区(实现是堆分代思想中的永久代)。
所以既然本质上还是堆的一部分,就是OOM的风险。
1.8及之后版本的方法区
在Java1.8中,HotSpot虚拟机已经将永久代移除了,取而代之的就是元空间。
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。
元空间不在与堆是连续的物理内存,而是改为使用本地内存(Native memory)。元空间使用本地内存也就意味着只要本地内存足够,就不会出现OOM的错误。

文章作者:米兰
原始链接:https://blog.milanchen.site/posts/jvm-runtime-area.html
版权声明:转载请声明出处