对象创建源码追踪:从 new 指令到 JVM 内部实现

对象创建源码追踪:从 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 < endTLAB 足够直接返回地址


八、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 全程断点看 根集合扫描,欢迎点个关注不迷路!

相关推荐
彩妙不是菜喵11 分钟前
C++ 中 nullptr 的使用与实践:从陷阱到最佳实践
开发语言·jvm·c++
L.EscaRC14 分钟前
Spring Boot 自定义组件深度解析
java·spring boot·后端
pengzhuofan21 分钟前
IntelliJ IDEA 常用快捷键
java·ide·intellij-idea
ANGLAL23 分钟前
17.MyBatis动态SQL语法整理
java·sql·mybatis
SheepHappy37 分钟前
MyBatis-Plus 源码阅读(二)代码生成器原理深度剖析
java·源码阅读
雨白1 小时前
重识 Java IO、NIO 与 OkIO
android·java
light_in_hand1 小时前
内存区域划分——垃圾回收
java·jvm·算法
金銀銅鐵1 小时前
[Java] JDK 9 新变化之 Convenience Factory Methods for Collections
java·后端
用户7406696136251 小时前
入门并理解Java模块化系统(JPMS)
java
金銀銅鐵2 小时前
[Java] 用 Swing 生成一个最大公约数计算器
java·后端