JVM 自动内存管理

一、运行时数据区域详解

Java 虚拟机在运行 Java 程序时,会将所管理的内存划分为多个不同的数据区域,各区域有着独特的用途、创建和销毁时间。

  1. 程序计数器:作为线程私有的较小内存空间,它是当前线程执行字节码的行号指示器。字节码解释器通过改变其值选取下一条指令,以实现分支、循环等基础功能。每个线程拥有独立的程序计数器,互不干扰。当线程执行 Java 方法时,计数器记录字节码指令地址;执行本地方法时,计数器值为空。该区域是唯一不会出现 OutOfMemoryError 情况的区域 。
  2. Java 虚拟机栈:同样是线程私有,生命周期与线程相同。它描述了 Java 方法执行的线程内存模型,每个方法执行时都会创建一个栈帧,用于存储局部变量表、操作数栈等信息。方法的调用到执行完毕,对应着栈帧在虚拟机栈中的入栈到出栈过程。局部变量表存放基本数据类型、对象引用和 returnAddress 类型数据,其所需内存空间在编译期完成分配。当线程请求栈深度超过虚拟机允许深度时,会抛出 StackOverflowError 异常;若栈容量可动态扩展且扩展时无法申请到足够内存,则抛出 OutOfMemoryError 异常(HotSpot 虚拟机栈容量不可动态扩展)。
  3. 本地方法栈:为虚拟机使用的本地方法服务,功能与虚拟机栈相似。《Java 虚拟机规范》对其实现没有强制规定,部分虚拟机(如 HotSpot)会将它与虚拟机栈合二为一。与虚拟机栈一样,本地方法栈在栈深度溢出或扩展失败时,会分别抛出 StackOverflowError 和 OutOfMemoryError 异常。
  4. Java 堆:是虚拟机管理内存中最大的一块,被所有线程共享,在虚拟机启动时创建,主要用于存放对象实例。虽然理论上所有对象实例及数组都应在堆上分配,但随着技术发展,栈上分配等优化手段使其并非绝对。Java 堆是垃圾收集器管理的区域,部分资料称其为 "GC 堆" 。从回收内存角度,基于分代收集理论,会有 "新生代""老年代" 等概念,但这只是部分垃圾收集器的特性,并非虚拟机固有内存布局。从分配内存角度,可划分出线程私有的分配缓冲区(TLAB)以提升对象分配效率。Java 堆可固定大小或可扩展(通过 - Xmx 和 - Xms 设定),当无法完成实例分配且堆不能扩展时,会抛出 OutOfMemoryError 异常 。
  5. 方法区:是各个线程共享的内存区域,用于存储已加载的类型信息、常量、静态变量等数据,被视为堆的逻辑部分,也称为 "非堆"。在 JDK 8 以前,HotSpot 虚拟机使用永久代实现方法区,导致一些问题,如内存溢出风险较高等。JDK 8 后,改用元空间代替永久代,将类型信息等移到元空间。方法区内存回收主要针对常量池和类型卸载,回收效果通常不太理想,但有时是必要的。当方法区无法满足内存分配需求时,会抛出 OutOfMemoryError 异常。
  6. 运行时常量池:作为方法区的一部分,存放编译期生成的字面量与符号引用,类加载后存于其中。运行时常量池具有动态性,运行期间可添加新常量,如 String 类的 intern () 方法就利用了这一特性。它受方法区内存限制,内存不足时会抛出 OutOfMemoryError 异常。
  7. 直接内存:不属于虚拟机运行时数据区,也非《Java 虚拟机规范》定义的区域。JDK 1.4 引入 NIO 类后,可通过 Native 函数库分配堆外内存,通过 DirectByteBuffer 对象操作,能提高某些场景的性能。其分配不受 Java 堆大小限制,但受本机总内存和处理器寻址空间限制,若各内存区域总和超过物理内存限制,可能导致 OutOfMemoryError 异常。

二、HotSpot 虚拟机对象剖析

以最常用的 HotSpot 虚拟机和 Java 堆为例,深入了解对象在其中的创建、布局和访问过程。

  1. 对象的创建:假设Java堆中内存是绝对规整的,所有被使用过的内存都被放在一 边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那 个指针向空闲空间方向挪动一段与对象大小相等的距离,这种分配方式称为"指针碰撞"(Bump The Pointer)。但如果Java堆中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,那 就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分 配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称 为"空闲列表"(Free List)。当 Java 虚拟机遇到 new 指令时,首先检查指令参数能否在常量池中定位到类的符号引用,并确认该类是否已加载、解析和初始化。若未完成,需先执行类加载过程。类加载检查通过后,为对象分配内存,分配方式取决于 Java 堆是否规整,而堆的规整程度由垃圾收集器是否具备空间压缩整理能力决定。分配内存时,还需解决并发安全问题,可通过同步处理或使用本地线程分配缓冲(TLAB)来实现。内存分配完成后,将内存空间初始化为零值,设置对象头信息,最后执行构造函数<init>() 方法完成对象初始化 。
  2. 对象的内存布局:在 HotSpot 虚拟机中,对象在堆内存的存储布局由对象头、实例数据和对齐填充三部分组成。对象头包含用于存储对象运行时数据的 Mark Word(如哈希码、GC 分代年龄等)和类型指针(确定对象所属类),若为数组对象,还包含记录数组长度的数据。实例数据存储程序中定义的字段内容,存储顺序受虚拟机分配策略和字段定义顺序影响。对齐填充是为了满足对象起始地址为 8 字节整数倍的要求,起占位作用。
  3. 对象的访问定位:Java 程序通过栈上的 reference 数据操作堆上对象,主流访问方式有句柄和直接指针两种。使用句柄访问时,Java 堆会划分句柄池,reference 存储句柄地址,句柄包含对象实例数据与类型数据地址;使用直接指针访问时,Java 堆中对象内存布局需考虑放置访问类型数据的信息,reference 直接存储对象地址。
相关推荐
此木|西贝22 分钟前
【设计模式】享元模式
java·设计模式·享元模式
এ᭄画画的北北1 小时前
力扣-234.回文链表
算法·leetcode·链表
李少兄1 小时前
解决Spring Boot多模块自动配置失效问题
java·spring boot·后端
bxlj_jcj2 小时前
JVM性能优化之年轻代参数设置
java·性能优化
八股文领域大手子2 小时前
深入理解缓存淘汰策略:LRU 与 LFU 算法详解及 Java 实现
java·数据库·算法·缓存·mybatis·哈希算法
不当菜虚困2 小时前
JAVA设计模式——(八)单例模式
java·单例模式·设计模式
m0_740154672 小时前
Maven概述
java·maven
__lost2 小时前
C++ 解决一个简单的图论问题 —— 最小生成树(以 Prim 算法为例)
算法·图论·最小生成树·prim算法
吗喽对你问好2 小时前
Java位运算符大全
java·开发语言·位运算