Java 字节码与对象创建过程详解(AnimalHandler 示例)

示例 Java 代码

1 public class AnimalHandler {
2     public static void main(String[] args) {
3         AnimalHandler handler = new AnimalHandler();
4     }
5 }

对应码(javap -c AnimalHandler 查看)

 1 Compiled from "AnimalHandler.java"
 2 public class AnimalHandler {
 3   public AnimalHandler();
 4     Code:
 5        0: aload_0
 6        1: invokespecial #1 // 调用父类 Object 的构造器
 7        4: return
 8 
 9   public static void main(java.lang.String[]);
10     Code:
11        0: new           #2  // 创建 AnimalHandler 对象的“未初始化对象”并压入操作数栈
12        3: dup               // 复制引用,为调用构造器和赋值准备
13        4: invokespecial #1  // 调用构造函数 AnimalHandler()
14        7: astore_1          // 将构造完成的对象引用存入本地变量表(handler)
15        8: return
16 }

Java 对象创建过程解(AnimalHandler 示例)

下面AnimalHandler main 方法例,结合 JVM 方法区、堆、结构,逐步解析对象创建过程。

1. 指令逐条解释

  • aload_0局部变量索引 引用载到操作顶。这里局部变量 对应当前对象引用 this。

  • invokespecial #1常量索引 处的方法;根据注释,类 java/lang/Object 构造方法 <init>()Vinvokespecial 用于实例初始方法(构造函数)类/私有方法指令弹出操作对象引用(this),执行构造逻辑,然后返回。

  • return结束当前方法(构造器),返回控制权(构造返回值)。对应 JVM 指令 b1方法返回,操作

上述构造函数于 Java 中的 public AnimalHandler() { super(); }其中一个 aload_0 载 this然后 invokespecial 用 Object 构造完成初始化。

  • new #2创建一个新的对象引用,不调构造函数。#2 指向常量中的 AnimalHandler 引用。执行 new 后,JVM 堆上为 AnimalHandler 对象分配内存(初始状态),对象引用推入操作注意:new 负责内存分配,尚未任何 <init> 构造

  • dup复制引用顶。此时操作相同对象引用引用暂时相互独立指向分配对象。需要副本:一个用于构造器,一个用于存储。

  • invokespecial #1索引为 构造函数,这里指向 AnimalHandler() 构造方法(的 <init>)。指令弹出一个对象引用执行构造器,对象“初始化”(其中构造器),执行返回。由于之前使用了 dup操作保留一个对象引用。

  • astore_1对象引用存入当前方法(main局部变量索引 中。此时 handler 变量(局部变量 1)指向创建初始完成的 AnimalHandler 实例

  • return结束 main 方法(返回 void)。指令使 main 方法栈,程序结束

2. JVM 内存结构示意

JVM 内存主要分为 方法区 (Method Area)堆 (Heap) 栈 (Stack)执行上述时,这些区域分别承担不同角色:

  • 方法存放数据、常量池、静态变量例如,方法AnimalHandler Object 信息,常量包含构造方法引用索引(#1=Object.<init>#2=AnimalHandler)。

  • 堆 (Heap)存放所有实例对象。new 指令堆上AnimalHandler 分配空间对象随后初始化。所有线共享。

  • 栈 (Stack)线自己Java 每次方法都会创建一个帧,包含局部变量操作main 方法中,局部变量args (索引0) handler (索引1),操作用于执行 newinvokespecial 指令过程中的数据传递。

3. 对象创建初始内存流程

对象创建new 指令开始,构造执行结束变量为止,主要经历以下阶段(参考文字码):

    1.new #2 执行

  • JVM 堆上分配内存一个新的 AnimalHandler 实例,此时对象尚未经过任何初始化(所有使用默认值)。

  • JVM 对象引用(称为“初始对象引用”)推入 main 方法操作

  • 此时内存状态示例(-> 表示引用流向):

1 堆: [AnimalHandler 对象(未初始化)] 
2 栈(操作数栈): [ objectRef ]  ← 指向堆中新分配的对象
3 方法区: [AnimalHandler 类信息, 常量池 #2 ...]

           2. dup 执行

  • 操作objectRef 复制,变为相同引用。

 

  • 这样为了invokespecial 最终都能保留一个引用。
  • 内存状态示例:
1 堆: [AnimalHandler 对象(未初始化)]
2 栈(操作数栈): [ objectRef, objectRef ]

           3. invokespecial #1 构造

  • invokespecial 弹出一个对象引用,AnimalHandler() 构造方法执行初始代码。构造内部首先执行super()Object 构造
  • 此时 JVM 暂时进入新的帧(<init> 方法帧),弹出引用作为 this局部变量 0)传递构造器。构造对象进行初始化。完成后构造返回。
  • 返回时,一个(弹出的)对象引用依然留在操作顶。此时对象完成初始化。
  • 内存状态示例:
1 堆: [AnimalHandler 对象(已初始化:父类 Object 构造器已执行)]
2 栈(操作数栈): [ objectRef ]  
3   (注意:此时只有一个引用保留,指向初始化后的对象)
  • 这里关键是:new 只是分配内存,真正初始<init> 完成invokespecial 确保对象构造逻辑执行。

           4. astore_1 存储引用

  • 执行 astore_1 时,对象引用弹出,存入局部变量索引 1 (handler)
  • 后,操作空,局部变量 1 保存创建 AnimalHandler 实例引用。
  • 最终内存状态示例:
1 堆: [AnimalHandler 对象(已初始化)]
2 栈(操作数栈): [ ] 
3 栈(局部变量): [0: args, 1: handler → 指向该对象] 

上,整个过程是:new 堆上分配对象得到引用 → dup 复制引用 → invokespecial 构造初始对象 → astore 引用局部变量。数据如上所示。

4. dup 必要性new 不调构造原因

  • 为什么要用 dup 正如前述,创建对象模式newdupinvokespecialastoreinvokespecial 构造消耗(弹出)操作对象引用,我们最终需要一个引用存储。没有 dup 情况下,执行invokespecial 操作空,失去对象引用因此必须dup 一份引用,保证构造依然一个引用可供 astore 使用
  • 为什么 new 本身不调构造器? JVM 设计中,new 指令负责堆上开辟内存返回一个“初始对象”引用,不会自动执行构造函数只有随后<init>通过 invokespecial时,对象真正初始化。这种设计使得对象分配初始清晰分离:new 引用,<init> 完成初始因此,需要invokespecial 构造器。

以上过程保证Java 代码new AnimalHandler() 完整执行上述序列:分配初始对象,通过 <init> 执行构造器,再把引用变量。

 

posted @ 2025-05-10 09:39  红tea  阅读(16)  评论(0)    收藏  举报