聊聊JVM的内存结构

分为了5大块:方法区、堆、程序计数器、虚拟机栈、本地方法栈
JVM的内存结构 - 图1

讲下你这图上每个区域的内容

「程序计数器」

假设线程数大于CPU数,就很有可能有「线程切换」现象,切换意味着「中断」和「恢复」,那自然就需要有一块区域来保存「当前线程的执行信息」
程序计数器就是用于记录各个线程执行的字节码的地址(分支、循环、跳转、异常、线程恢复等都依赖于计数器)

「虚拟机栈」

每个线程在创建的时候都会创建一个「虚拟机栈」,每次方法调用都会创建一个「栈帧」
每个「栈帧」会包含几块内容:

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

JVM的内存结构 - 图2

「本地方法栈」

本地方法栈跟虚拟机栈的功能类似,虚拟机栈用于管理 Java 函数的调用,而本地方法栈则用于管理本地方法的调用。这里的「本地方法」指的是「非Java方法」,一般本地方法是使用C语言实现的。

「方法区」

是 JVM 中规范的一部分
HotSpot虚拟机在「JDK8前」用「永久代」实现了「方法区」,而很多其他厂商的虚拟机其实是没有「永久代」的概念的
在JDK8中,已经用「元空间」来替代了「永久代」作为「方法区」的实现
方法区主要是用来存放已被虚拟机加载的「类相关信息」:类信息、常量池

  • 类信息又包括了类的版本静态字段静态方法接口父类等信息。
  • 常量池又可以分「静态常量池」和「运行时常量池」
    • 静态常量池主要存储的是「字面量」以及「符号引用」等信息,静态常量池也包括了我们说的「字符串常量池」。
    • 「运行时常量池」存储的是「类加载」时生成的「直接引用」等信息

从逻辑分区的角度而言「常量池」是属于「方法区」,但从物理分区的角度上,常量池是属于堆的
但自从在「JDK7」以后,就已经把常量池(运行时常量池和静态常量池)转移到了「堆」内存中进行存储
JVM的内存结构 - 图3

「JDK8」已经把「方法区」的实现从「永久代」变成「元空间」,有什么区别?

最主要的区别就是:「元空间」存储不在虚拟机中,而是使用本地内存,JVM 不会再出现方法区的内存溢出,以往「永久代」经常因为内存不够用导致跑出OOM异常。
按JDK8版本,总结起来其实就相当于:「类信息」是存储在「元空间」的(也有人把「类信息」这块叫做「类信息常量池」,主要是叫法不同,意思到位就好)
「常量池」用JDK7开始,从「物理存储」角度上就在「堆中」,这是没有变化的。
JVM的内存结构 - 图4

「堆」

「堆」是线程共享的区域,几乎类的实例和数组分配的内存都来自于它
「堆」被划分为「新生代」和「老年代」,「新生代」又被进一步划分为 Eden 和 Survivor 区,最后 Survivor 由 From Survivor 和 To Survivor 组成

image.png

讲讲JVM内存结构和Java内存模型有啥区别

他们俩没有啥直接关联
Java 内存模型是跟「并发」相关的,它是为了屏蔽底层细节而提出的规范,希望在上层(Java层面上)在操作内存时在不同的平台上也有相同的效果

  • JVM的内存结构 - 图6
  • 线程本地内存
  • 主内存

jvm 内存结构(又称为运行时数据区域),它描述着当我们的class文件加载至虚拟机后,各个分区的「逻辑结构」是如何的,每个分区承担着什么作用。

  • 主要有五部分组成:虚拟机栈、本地方法栈、程序计数器、方法区和堆。其中方法区和堆是线程共享的。虚拟机栈、本地方法栈以及程序计数器是线程隔离的)

JVM的内存结构 - 图7