Java内存模型

针对的是HotSpot虚拟机

运行时数据区域

程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,存储指向下一条指令的地址,可以看作是当前线程所执行字节码的行号指示器

  • 线程私有,创建线程时创建相应的程序计数器

  • 执行Java方法,记录的是正在执行的VM字节码指令的地址

  • 执行本地(Native)方法,程序计数器为空(Undefined)

  • 唯一一个在JVM规范中不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡

Java虚拟机栈(Java栈)

Java栈由一系列栈帧(Stack Frame)组成,因此Java栈也叫做帧栈

  • 线程私有,描述的是Java方法执行的线程内存模型

  • 每个方法被执行时,JVM都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、常量池指针、动态链接、方法返回值等等

  • 每一个方法被调用直到执行结束,就对应一个栈帧在虚拟机栈中从入栈到出栈的过程(因此在栈中通常不存在垃圾回收问题)

  • 会抛出StackOverFlowErrorOutOfMemoryError两个异常

局部变量表

局部变量表存放了编译期可知的各种基本数据类型引用类型,这些数据类型在局部变量表中以局部变量槽(Slot)来表示

每个slot存放32位的数据,因此long、double占两个槽位

局部变量表所需的内存空间在编译期间完成分配,进入一个方法时,该方法在栈帧中分配多大的局部变量空间是完全确定的

本地方法栈(C栈)

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用非常相似,区别在于:

  • 虚拟机栈为虚拟机执行Java方法(字节码)服务;

  • 本地方法栈,用来支持Native方法执行

Java堆(Heap)

一个JVM只有一个堆内存(线程共享),在虚拟机启动时创建

此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存

Java堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap)

从垃圾回收的角度(更好回收与分配内存),由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等

如下分别为JDK 7与JDK 8的区域。

方法区

方法区(Method Area)用于存储已被虚拟机加载的类信息(类的结构信息)、常量(运行时常量池)、静态变量、即时编译器编译后的代码等数据

  • 线程共享
  • Java虚拟机规范将方法区描述为堆的一个逻辑部分,但为了区分方法区又被称为非堆(Non-heap)
  • 通常和元空间(JDK 8之前“永久代”)关联在一起,但具体的跟JVM的实现和版本有关。元空间并不在虚拟机里面,而是直接使用本地内存

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分

在class文件中就存在常量池表,用于存放编译期间生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中

对象创建全过程

对象创建过程

step1:类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程

step2:分配内存

类加载检查通过后,接下来虚拟机将为新生对象分配内存

对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来

step3:初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)

这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用

step4:设置对象头

初始化零值后,JVM对对象进行必要的设置(设置对象头相关信息)

step5:执行init方法

执行完以上步骤:从虚拟机角度而言,一个新对象已经产生了,但从Java程序角度而言,对象还没有进行初始化

因此需要执行init方法对对象进行初始化

对象的内存布局

对象在内存中存储的布局分为三部分∶对象头、实例数据和对齐填充

  • 对象头,包含两个部分︰
    (1)MarkWord:存储对象自身的运行数据,如∶HashCode、GC分代年龄、锁状态标志(synchronized对象锁机制中的Monitor等信息就存储在这儿)等
    (2)类型指针:对象指向它的类元数据的指针,虚拟机通过这个指针确定这个对象是哪个类的实例

  • 实例数据
    真正存放对象实例数据的地方,即程序所定义的各个类型的字段内容

  • 对齐填充
    这部分不一定存在,也没有什么特别含义,仅仅是占位符。HotSpot要求对象起始地址都是8字节的整数倍,如果不是,就对齐填充

对象的访问定位

在JVM规范中只规定了栈中的reference类型是一个指向对象的引用,但没有规定这个引用具体如何去定位、访问堆中对象的具体位置因此对象的访问方式取决于JVM的实现,目前主流的有:使用句柄、使用指针两种方式

  • 使用句柄:Java堆中会划分出一块内存来做为句柄池,reference中存储句柄的地址,句柄中存储对象的实例数据和类元数据的地址
  • 使用指针(Hotpot使用):Java堆中会存放访问类元数据的地址,reference存储的就直接是对象的地址

参考:

https://snailclimb.gitee.io/javaguide/#/docs/java/jvm/Java%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F

《深入理解Java虚拟机》

https://github.com/doocs/jvm/blob/main/docs/01-jvm-memory-structure.md

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2019-2021 子夜
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信