Java字节码创建对象指令

12 篇文章 1 订阅

接上节。

示例代码:

package com.lkl.jvmDemo;

public class HelloByteCode {
    public static void main(String[] args) {
        HelloByteCode obj = new HelloByteCode();
    }
}

查看字节码清单javap -c -verbose HelloByteCode

Classfile /XXX/com/lkl/jvmDemo/HelloByteCode.class
  Last modified 2023-10-29; size 304 bytes
  MD5 checksum 565e4ca34e83f69df37c1f35c971375f
  Compiled from "HelloByteCode.java"
public class com.lkl.jvmDemo.HelloByteCode
  minor version: 0
  major version: 65
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // com/lkl/jvmDemo/HelloByteCode
   #8 = Utf8               com/lkl/jvmDemo/HelloByteCode
   #9 = Methodref          #7.#3          // com/lkl/jvmDemo/HelloByteCode."<init>":()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               SourceFile
  #15 = Utf8               HelloByteCode.java
{
  public com.lkl.jvmDemo.HelloByteCode();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #7                  // class com/lkl/jvmDemo/HelloByteCode
         3: dup
         4: invokespecial #9                  // Method "<init>":()V
         7: astore_1
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
}

方法体中的字节码解读

    	 0: new           #7                  // class com/lkl/jvmDemo/HelloByteCode
         3: dup
         4: invokespecial #9                  // Method "<init>":()V
         7: astore_1
         8: return

main方法体中那些字节码指令前面的数字是什么意思,说是序号吧但又不太像,因为他们之间的间隔不相等。

间隔不相等的原因是, 有一部分操作码会附带有操作数, 也会占用字节码数组中的空间

new 就会占用三个槽位: 一个用于存放操作码指令自身,两个用于存放操作数。因此,下一条指令 dup 的索引从 3 开始。

这个方法体变成可视化数组,那么看起来应该是这样的:

image-20231029160412128

每个操作码/指令都有对应的十六进制(HEX)表示形式, 如果换成十六进制来表示,则方法体可表示为HEX字符串。例如上面的方法体百世成十六进制如下所示:

image-20231029160439918

上述02改为07(对应字节码中#7),支持十六进制的编辑器中打开 class 文件,可以在其中找到对应的字符串:

image-20231029160747543

对象初始化指令:new 指令, init 以及 clinit 简介

new是 Java 编程语言中的一个关键字, 但其实在字节码中,也有一个指令叫做 new。 创建类的实例时, 编译器会生成类似下面这样的操作码:

    	 0: new           #7                  // class com/lkl/jvmDemo/HelloByteCode
         3: dup
         4: invokespecial #9                  // Method "<init>":()V

同时看到 new, dupinvokespecial 指令在一起时,那么一定是在创建类的实例对象!

为什么是三条指令而不是一条呢?这是因为:

  • new 指令只是创建对象,但没有调用构造函数
  • invokespecial 指令用来调用某些特殊方法的,当然这里调用的是构造函数。
  • dup 指令用于复制栈顶的值。

由于构造函数调用不会返回值,所以如果没有 dup 指令, 在对象上调用方法并初始化之后,操作数栈就会是空的,在初始化之后就会出问题, 接下来的代码就无法对其进行处理。

这就是为什么要事先复制引用的原因,为的是在构造函数返回之后,可以将对象实例赋值给局部变量或某个字段。因此,接下来的那条指令一般是以下几种:

  • astore {N} or astore_{N} – 赋值给局部变量,其中 {N} 是局部变量表中的位置。
  • putfield – 将值赋给实例字段
  • putstatic – 将值赋给静态字段

调用构造函数的时候,其实还会执行另一个类似的方法 <init> ,甚至在执行构造函数之前就执行了。

还有一个可能执行的方法是该类的静态初始化方法 <clinit>, 但 <clinit> 并不能被直接调用,而是由这些指令触发的: new, getstatic, putstatic or invokestatic

也就是说,如果创建某个类的新实例, 访问静态字段或者调用静态方法,就会触发该类的静态初始化方法【如果尚未初始化】。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不进大厂不改名二号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值