对象创建源码追踪:从 new 指令到 JVM 内部实现
(JDK 8 视角 + 模板解释器 + 分配策略 + 全程断点,新手也能跟做)
关键词:new 指令、模板解释器、分配策略、对象头、JDK 8、全程断点
阅读时长:25 min
环境:OpenJDK 8u342 + CLion + gdb + 断点调试
适合:3~8 年 Java 开发、想知道「new Object() 到底怎么落地」、面试「对象创建几步」标准答案
一、0 基础速记:4 句话背下对象创建全流程
| 阶段 | 地点 | 一句话 |
|---|---|---|
| 字节码 | new |
常量池索引→类元数据→分配→初始化 |
| 解释器 | templateTable_x86.cpp |
字节码→汇编模板→call_VM |
| 分配 | CollectedHeap::obj_allocate |
TLAB 快分配→慢分配→清零→对象头 |
| 对象头 | markOop.hpp |
mark word + klass pointer = 16 B(JDK 8 压缩指针) |
口诀:「字节码指路,解释器翻译,分配器拿地,对象头立户」
二、实验环境:Docker 一键启动「调试机」
bash
docker run -it --name=new-8 \
-v /tmp/openjdk:/openjdk \
openjdk:8u342-jdk-alpine bash
# 安装调试工具
apk add gdb g++ make cmake git vim
# 下载带调试符号的 JDK 8 源码
cd /openjdk
git clone --depth=1 -b jdk8u342-b07 https://github.com/openjdk/jdk8u.git
全程 容器内操作 ,Windows/WSL 同理。
三、路线地图:从 new 到对象头的 7 个驿站(JDK 8 视角)
new Object()
↓ ① 字节码:new #index
↓ ② 解释器:templateTable_x86.cpp::_new()
↓ ③ 运行时:InterpreterRuntime::_new()
↓ ④ 分配器:CollectedHeap::obj_allocate()
↓ ⑤ TLAB:ThreadLocalAllocBuffer::allocate()
↓ ⑥ 对象头:markOopDesc::set_mark()
↓ ⑦ 初始化:instanceKlass::allocate_instance()
记住 7 个文件 ,按图索骥断点。
四、Step 1:字节码→模板解释器(new 指令)
① Java 源码
java
public class NewDemo {
public static void main(String[] args) {
Object obj = new Object();
}
}
② 查看字节码(JDK 8 javap)
bash
javac NewDemo.java
javap -c NewDemo
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
关注 new #2 → 常量池索引 2 → 后续旅程起点。
五、Step 2:模板解释器→运行时(_new 方法)
① 入口文件
hotspot/src/cpu/x86/vm/templateTable_x86.cpp
cpp
void TemplateTable::_new() {
__ get_unsigned_2_byte_index(rbx, rcx); // 读常量池索引
__ call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::_new));
}
call_VM = 跳入 C++ 运行时 ,rax 返回对象地址
② 断点调试(gdb)
bash
gdb /openjdk/jdk8u342/build/linux-x86_64-normal-server-slowdebug/jdk/bin/java
(gdb) b TemplateTable::_new
(gdb) run -XX:+UseSerialGC NewDemo
断点命中 → 单步进入 call_VM → 进入 InterpreterRuntime::_new
六、Step 3:运行时→分配器(InterpreterRuntime::_new)
① 源码文件
hotspot/src/share/vm/interpreter/interpreterRuntime.cpp
cpp
IRT_ENTRY(jobject, InterpreterRuntime::_new(JavaThread* thread, ConstantPool* pool, int index))
Klass* k = pool->klass_at(index, CHECK_NULL);
instanceKlassHandle klass(thread, k);
oop obj = klass->allocate_instance(CHECK_NULL);
return obj;
IRT_END
klass_at = 解析常量池 ,allocate_instance = 真正分配
② 断点轨迹
gdb
(gdb) b InterpreterRuntime::_new
(gdb) continue
(gdb) p *klass->name()
$1 = {
_body = 0x7ffff7b12345 "java/lang/Object"
}
确认 正在创建 java.lang.Object
七、Step 4:分配器→TLAB(CollectedHeap::obj_allocate)
① 入口文件
hotspot/src/share/vm/gc/shared/collectedHeap.inline.hpp
cpp
inline oop CollectedHeap::obj_allocate(KlassHandle klass, int size, TRAPS) {
HeapWord* obj = common_mem_allocate_init(size, false, CHECK_NULL);
post_allocation_setup_common(klass, obj, size);
return (oop)obj;
}
② TLAB 快速路径
hotspot/src/share/vm/gc/shared/allocator.cpp
cpp
HeapWord* MemAllocator::allocate() const {
if (UseTLAB) {
HeapWord* result = _thread->tlab().allocate(size);
if (result != NULL) return result;
}
return allocate_outside_tlab(size); // 慢路径
}
TLAB 分配 = 指针碰撞(bump-the-pointer) ,无锁
③ 断点看 TLAB
gdb
(gdb) b ThreadLocalAllocBuffer::allocate
(gdb) p _top
$2 = (HeapWord *) 0x7ffff7c00000
(gdb) p _end
$3 = (HeapWord *) 0x7ffff7c01000
(gdb) p size
$4 = 2 // 2 个 HeapWord = 16 B(压缩指针)
top + size < end → TLAB 足够 ,直接返回地址
八、Step 5:对象头构造(markOopDesc)
① 对象布局(JDK 8 压缩指针)
| 区域 | 大小 |
|---|---|
| mark word | 8 B |
| klass pointer | 4 B |
| 对齐 | 4 B |
| 合计 | 16 B |
② 源码文件
hotspot/src/share/vm/oops/markOop.hpp
cpp
inline markOop markOopDesc::prototype() {
return (markOop) (no_hash_in_place | no_lock_in_place);
}
③ 断点看对象头
gdb
(gdb) b markOopDesc::set_mark
(gdb) p/x prototype()
$5 = 0x1
mark word = 0x1(无锁、无哈希)
九、Step 6:全程断点动画(gif 文字版)
new Object()
→ new #2
→ templateTable_x86::_new (汇编)
→ InterpreterRuntime::_new (C++)
→ TLAB::allocate (指针碰撞)
→ markOopDesc::set_mark (对象头=0x1)
→ instanceKlass::init_mark (初始化)
← 返回 rax = 0x7ffff7c00000
rax 即对象地址 ,后续 invokespecial 完成构造。
十、实战:自己改源码------让 new 打印 Hello World
① 修改 InterpreterRuntime::_new
cpp
IRT_ENTRY(jobject, InterpreterRuntime::_new(JavaThread* thread, ...)){
printf("[HotSpot] new %s\n", klass->name()->as_C_string());
...
}
② 重新编译 & 运行
bash
make CONF=linux-x86_64-normal-server-slowdebug
/openjdk/jdk8u342/build/linux-x86_64-normal-server-slowdebug/jdk/bin/java NewDemo
输出:
[HotSpot] new java/lang/Object
源码级玩法 ,面试「改源码」实锤。
十一、统一模板:JDK 8 源码阅读 3 步法(面试直接用)
| 步骤 | 文件 | 函数 |
|---|---|---|
| 字节码→汇编 | templateTable_x86.cpp |
_new() |
| 分配→TLAB | allocator.cpp |
TLAB::allocate() |
| 对象头 | markOop.hpp |
set_mark() |
记住 3 个文件 ,按图索骥断点。
十二、面试 6 连问(背就行)
Q1 new Object() 在源码哪?
A:templateTable_x86.cpp 的 _new() → **InterpreterRuntime::_new`
Q2 TLAB 分配是锁吗?
A:无锁 ,指针碰撞 ,线程本地
Q3 对象头多大?
A:JDK 8 压缩指针:mark word 8B + klass 4B + 对齐 4B = 16B
Q4 如何调试 HotSpot new?
A:slowdebug 编译 + gdb b TemplateTable::_new
Q5 分配失败怎么办?
A:TLAB 不够→慢分配→CommonGC→可能 Young GC
Q6 一句话背下 new 全程?
「字节码指路,解释器翻译,TLAB 拿地,对象头立户」
十三、小结:一张思维导图(收藏即可)
new Object()
├─ 字节码 ──► new #2
├─ 解释器 ──► templateTable_x86::_new
├─ 运行时 ──► InterpreterRuntime::_new
├─ 分配器 ──► TLAB::allocate
├─ 对象头 ──► markOopDesc::set_mark
└─ 初始化 ──► instanceKlass::allocate_instance
没有看不懂的 new,只有没打断点的源码!
十四、下集预告
《GC roots 如何扫描?从 Safepoint 到 Thread Snapshot》
将带你 JDK 8 + gdb + SIGSTOP 全程断点看 根集合扫描,欢迎点个关注不迷路!