JVM运行时内存区域

我们以HotSpot虚拟机为前提下展开,因为目前使用最多的还是HotSpot虚拟机。

Java虚拟机在执行Java程序时,会将分配给JVM的内存划分为几个不同的区域。有些区域在JVM启动之后就存在,直到关闭JVM进程;有些区域则依赖于用户线程,随着用户线程的生命周期一同创建和销毁。

从Java1.8开始,JVM内存区域划分如图所示,从图中我们可以看出:

JVM内存区域_1.png

  • JVM堆中的数据是共享的,是占用内存最大的一块区域,是垃圾收集器管理的内存区域。
  • 可以执行字节码的模块叫作执行引擎。
  • 执行引擎在线程切换时依靠的就是程序计数器。
  • JVM 的内存划分与多线程是息息相关的。像我们程序中运行时用到的栈,以及本地方法栈,它们的维度都是线程。
  • 本地内存包含元数据区和一些直接内存。

虚拟机栈

Java虚拟机栈是基于线程的。哪怕你只有一个main() 方法,也是以线程的方式运行的。在线程的生命周期中,参与计算的数据会频繁地入栈和出栈,栈的生命周期是和线程一样的。

栈里的每条数据,就是栈帧。在每个Java 方法被调用的时候,都会创建一个栈帧,并入栈。一旦完成相应的调用,则出栈。所有的栈帧都出栈后,线程也就结束了。每个栈帧,都包含四个区域:

  • 局部变量表
  • 操作数栈
  • 动态连接
  • 返回地址

我们的应用程序,就是在不断操作这些内存空间中完成的。

JVM内存区域_2.png

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

JVM内存区域_3.png

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

本地方法栈

本地方法栈是和虚拟机栈非常相似的一个区域,唯一的区别是虚拟机栈是为Java方法服务,而本地方法栈是为本地方法(Java Native Method)服务的。

与虚拟机栈一样,本地方法栈也会在同样情况下会抛出相同异常。

程序计数器

程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。这里面存的,就是当前线程执行的进度。各个线程都有自己的程序计数器,所以这块是线程私有的。并且程序计数器也是唯一不会发生内存溢出的区域。

程序计数器与虚拟机栈配合完成计算操作。程序计数器还存储了当前正在运行的流程,包括正在执行的指令、跳转、分支、循环、异常处理等。

JVM内存区域_4.png

堆是JVM上最大的内存区域,我们申请的几乎所有的对象,都是在这里存储的(还存储着数组、字符串常量池、静态变量)。我们常说的垃圾回收,操作的对象就是堆。

这里讲两个问题:
1)一个对象创建的时候,到底是在堆上分配,还是在栈上分配呢?

这和两个方面有关:对象的类型和在Java类中存在的位置。
Java的对象可以分为基本数据类型和普通对象。
对于普通对象来说,JVM 会首先在堆上创建对象,然后在其他地方使用的其实是它的引用。比如,把这个引用保存在虚拟机栈的局部变量表中。
对于基本数据类型来说(byte、short、int、long、float、double、char),有两种情况。
我们上面提到,每个线程拥有一个虚拟机栈。当你在方法体内声明了基本数据类型的对象,它就会在栈上直接分配。其他情况,都是在堆上分配。
注意,像int[]数组这样的内容,是在堆上分配的。数组并不是基本数据类型。
这就是 JVM 的基本的内存分配策略。

2)堆是所有线程共享的,如果是多个线程访问,会涉及并发安全问题。

HotSpot虚拟机中采用`TLAB`的方法进行内存分配的。
可以参考:https://juejin.cn/post/6844903903939526664

如果在堆中没有内存完成实例分配,并且堆也无法再拓展时,Java虚拟机会抛出OOM(OutOfMemoryError)异常。

方法区

方法区是JVM定义的规范概念,用于存储类信息(类级别的结构信息)、运行时常量池、字节码等数据,具体放在哪儿,不同的实现可以放在不同的地方。

JVM内存区域_5.png

1.7及之前版本的方法区

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

JVM内存区域_6.png

可以看到它是和堆内存连在一起的,其实就是从堆内存中特意拿出一块区域,把它命名为方法区(实现是堆分代思想中的永久代)。
所以既然本质上还是堆的一部分,就是OOM的风险。

1.8及之后版本的方法区

在Java1.8中,HotSpot虚拟机已经将永久代移除了,取而代之的就是元空间。
元空间的本质和永久代类似,都是对JVM规范中方法区的实现。
元空间不在与堆是连续的物理内存,而是改为使用本地内存(Native memory)。元空间使用本地内存也就意味着只要本地内存足够,就不会出现OOM的错误。

JVM内存区域_7.png


推荐文章:https://blog.csdn.net/qq_45737068/article/details/107149922

Last modification:November 22, 2020
如果觉得我的文章对你有用,请随意赞赏