2.1 JVM对象创建

对象创建

本文基于 Hotspot 虚拟机,其他 JVM 行为可能会有区别

本文中的 Eden 区泛指各 GC 中新对象的分配区域,如 G1 的 Eden Region、ZGC 的分配 Page 等

本文行为均基于默认行为,某些 JVM 参数会导致行为不同

对象创建大致可以分为如下几个步骤

复制代码
元数据检查 -> 内存分配 -> 设置对象头 -> 对象内容初始化

详解

通过 new 指令方式创建类实例的流程图如下所示
内存分配



充足
不足

剩余空间小于浪费阈值
剩余空间大于浪费阈值
遇到 new 指令
类元数据已存在?
执行类加载

Loading - Linking - Init
启用 TLAB?
TLAB 余额充足?
TLAB 内分配

无锁指针碰撞
对象大小

vs TLAB剩余空间?
CAS 竞争申请新 TLAB

然后分配
CAS 竞争堆内存

直接分配
空间清零

TLAB预清零 / 分配后清零
设置对象头

MarkWord + KlassPtr
new 指令完成

对象引用入栈
继续执行字节码
invokespecial

触发构造方法
对象真正可用

元数据检查

检查当前命令能否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否被加载解析和初始化过,如果没有的话会先进行类加载过程。

在元数据中会拿到对象所需内存的大小

内存分配

堆内存格局和 gc 强相关,此处尽量不涉及具体的内存格局细节

堆不是线程独占内存,同时内部对象会频繁的被 gc 清理,因此内存分配大致来说有两个问题,一是分配到哪块内存,二是怎么保证分配的并发安全

指针碰撞

在绝大部分 gc 中,Eden 区都是一块至少局部规整的内存。换句话说有某个地址作为分界点,使用过的内存在一边,空闲的内存在另一边。那么分配内存就只是把分界地址的指针往空闲方向挪动一段与对象所需内存大小相等的距离,这种分配方式就是指针碰撞。

空闲列表

CMS 是非压缩 GC,无法保证堆局部规整,需要维护一个列表记录内存的占用情况来辅助分配对象,这种分配方式就是空闲列表(实际上还是有机制保证局部规整的,但是 CMS 都废弃了就不深入了)

CAS

简单来说就是通过硬件的 CAS 原子操作与自旋实现并发安全,下图是碰撞指针的 CAS 分配示例
CAS 原子操作
准备工作


利用最新 Top

直接重算
开始分配
读取/更新快照

snapshot = 当前 Top
计算期望新指针

new_top = snapshot + size
比较:

内存 Top == snapshot ?
匹配:

Top 更新为 new_top
不匹配:

Top 未修改

获取最新 Top
分配成功

返回 snapshot 地址

TLAB

TLAB(Thread Local Allocation Buffer),简单来说就是给线程预分配一块内存,当新对象可以直接放到 TLAB 中时就可以直接无锁分配。

TLAB 本身的内存申请也是通过 CAS 实现的。

空间清零

Java 语言规范规定的所有字段在未赋值前,必须有默认值,因此对象字段区域需要进行清零操作

这个步骤在流程图上标记在了设置对象头之前,但是实际上不同分配方式该步骤执行时机不同

  1. 堆上直接分配:同步清零
  2. TLAB 分配:TLAB 申请时批量清零整个 TLAB 块

设置对象头

设置对象的元数据

做完这个步骤后在 vm 层面对象就创建完了,只是对象内容还没有进行初始化

对象内容初始化

执行 <init>() 方法初始化对象内容

其他创建方式

除了标准构造(new 或者 反射)外,其他构造方式(克隆、反序列化、Unsafe)流程在在 vm 层面流程极其类似(clone略特殊,他分配内存时不进行空间清零步骤而是直接拷贝内存),只是对象内容初始化方式不同

创建方式 字节码/API 是否调用 <init> 内存初始化方式 内容初始化方式 备注
new 关键字 new 清零 构造方法 最标准的创建方式
反射 Class.newInstance Constructor.newInstance 清零 构造方法 最终调用 new 流程,但在 JVM 层面有安全检查开销
克隆 clone() 内存复制 - 内存初始化完内容也就初始化完了
反序列化 ObjectInputStream.readObject 视情况 清零 序列化流 通常调用第一个不可序列化父类的无参构造
Unsafe Unsafe.allocateInstance 清零 -
相关推荐
vx_Biye_Design20 小时前
【关注可免费领取源码】云计算及其应用网络教学系统--毕设附源码35183
java·spring·spring cloud·servlet·eclipse·云计算·课程设计
码农阿豪1 天前
Nacos 日志与 Raft 数据清理指南:如何安全释放磁盘空间
java·安全·nacos
直有两条腿1 天前
【大模型】Langchain4j
java·langchain
love530love1 天前
Scoop 完整迁移指南:从 C 盘到 D 盘的无缝切换
java·服务器·前端·人工智能·windows·scoop
消失的旧时光-19431 天前
C++ 多线程与并发系统取向(二)—— 资源保护:std::mutex 与 RAII(类比 Java synchronized)
java·开发语言·c++·并发
莫寒清1 天前
ThreadLocal
java·面试
学习是生活的调味剂1 天前
spring bean循环依赖问题分析
java·后端·spring
Coder_Boy_1 天前
Java(Spring AI)传统项目智能化改造——商业化真实案例(含完整核心代码+落地指南)
java·人工智能·spring boot·spring·微服务
五阿哥永琪1 天前
1. 为什么java不能用is开头来做布尔值的参数名,会出现反序列化异常。
java·开发语言
chilavert3181 天前
技术演进中的开发沉思-371:final 关键字(中)
java·前端·数据库