Skip to content

JVM要点笔记

来源

  • 内容来自《深入理解Java虚拟机》。
  • 这里只是要点笔记。

运行时数据区域

运行时数据区域

程序计数器

  • 是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。

  • 线程私有。

  • 线程执行本地方法时,计数器值为空。

  • 不存在OutOfMemoryError

Java虚拟机栈

  • 线程私有,生命周期与线程相同。
  • 描述Java方法执行的线程内存模型,每个方法执行时,Java虚拟机都会同步创建栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
  • 线程请求栈深大于虚拟机所运行的深度,抛出StackOverflowError
  • 若Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError。(HotSpot不支持栈容量动态扩展,故不存在因虚拟机栈无法扩展导致的OutOfMemoryError,只存在线程申请栈空间失败时导致的OutOfMemoryError。)

本地方法栈

  • 本地方法栈为虚拟机使用到的本地方法服务。
  • 线程私有。
  • 栈深度溢出抛出StackOverflowError。栈扩展失败抛出OutOfMemoryError
  • Hotspot虚拟机直接将本地方法栈和虚拟机栈合二为一。

Java堆

  • 线程共享的内存区域。
  • 几乎(逃逸分析技术,栈上分配、标量替换优化)所有的对象实例在Java堆里分配。
  • 线程共享的Java堆可以划分出多个线程私有的分配缓冲区(Thread Local Allocation BufferTLAB)。
  • 没有内存完成实例分配且堆无法再扩展(-Xmx-Xms设定),抛出OutOfMemoryError

方法区

  • 线程共享的内存区域。
  • 存储虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
  • 方法区的垃圾回收针对的是常量池的回收和类型的卸载。
  • 方法区无法满足内存分配需求,抛出OutOfMemoryError

方法区、永久代、元空间的关系?

  • 永久代和元空间都是方法区的实现方式。

  • Jdk8前,HotSpot使用永久代实现方法区,使用的堆内存,为了方法区的垃圾回收跟Java堆的机制保持一致,省去方法区内存管理的额外工作。

  • Jdk8后,HotSpot跟JRockit对齐,采用元空间实现方法区,使用的本地内存,减少了内存溢出的问题。

运行时常量池

  • 方法区的一部分。
  • 存放类文件中的常量池表的内容(编译期生成的各种字面量与符号引用)。

直接内存

  • 不是虚拟机运行时数据区的一部分。
  • NIO使用的Java虚拟机堆外分配的内存区域。
  • 没有内存满足动态扩展(-XX:MaxDirectMemorySize)时,抛出OutOfMemoryError

直接内存和本地内存的区别?

  • Java直接内存和本地内存不是完全相同的概念。

  • Java直接内存是通过Java NIO库中的ByteBuffer类创建的,它们是在Java虚拟机堆外分配的内存区域。

  • 本地内存是指操作系统分配的内存区域,它可以通过JavaJNIJava Native Interface)访问。

  • 都可以被用于提高性能,但是它们在实现和使用上有一些不同。

对象

对象的创建

主要包括类加载、分配内存、初始化、引用赋值等过程。

类加载

Java 中,对象的创建必须先加载类。当 JVM 第一次遇到某个类的实例化请求时,就会通过类加载器加载该类。

对象(普通Java对象)的创建通常(复制、反序列化除外)通过new关键字创建。虚拟机中:

  • 检查字节码指令new的参数是否在常量池中定位类的符号引用。
  • 检查符号引用代表的类是否被加载、解析和初始化过。
  • 没有的情况下,进行相应的类加载过程。

分配内存

在类加载完成后,JVM 会在堆中分配内存空间来存储该对象。分配内存的方式有两种:指针碰撞和空闲列表。在指针碰撞的方式中,堆被划分为两个区域,一边是已经被占用的内存,另一边是未被占用的内存。在空闲列表的方式中,JVM 会维护一个列表来记录哪些内存块是可用的。

初始化

在分配完内存后,JVM 会对对象进行初始化。初始化过程包括为对象的成员变量赋初值、执行构造方法等操作。

引用赋值

在对象初始化完成后,JVM 会将对象的引用返回给程序员。程序员可以将该引用赋值给变量,从而实现对对象的引用。

对象的内存布局

对象结构

对象头

运行时数据MarkWord
  • 哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向锁线程ID、偏向时间戳等。

  • HotSpot虚拟机对象头的MarkWord

存储内容标志位状态
对象哈希码、对象分代年龄01未锁定
指向锁记录的指针00轻量级锁定
指向重量级锁的指针10膨胀(重量级锁定)
空,不需要记录信息11GC标记
偏向线程ID、偏向时间戳、对象分代年龄01可偏向
  • 未开启压缩指针情况下,32位虚拟机占4个字节,64位虚拟机占8个字节。
  • 开启压缩指针情况下,32位虚拟机占4个字节,64位虚拟机占4个字节。
类型指针
  • 对象指向它类型元数据的指针,JVM通过该指针确定该对象属于哪个类的实例。(直接指针访问对象的情况下)
  • 不是所有的虚拟机实现都必须在对象数据上保留类型指针。(句柄访问对象的情况下)
  • 未开启压缩指针情况下,32位虚拟机占4个字节,64位虚拟机占8个字节。
  • 开启压缩指针情况下,32位虚拟机占4个字节,64位虚拟机占4个字节。
数组长度
  • 对象是Java数组情况下,用于记录数组长度的数据。
  • 占4个字节。

实例数据

存储对象的字段信息

对齐填充

如何计算一个Java对象占用多少内存?

对象的访问定位

句柄访问

句柄访问

  • reference存储的对象的句柄地址。
  • 句柄包含对象实例数据这类型数据各自具体地址。

直接指针访问

直接指针访问

  • reference存储对象地址。

  • 直接指针访问好处速度快。

  • HotSpot采用直接指针进行对象访问。