JVM之Java字节码详解😉,告诉你什么是咖啡北鼻☕

2023年 9月 14日 41.4k 0

字节码介绍

经过javac命令编译生成的二进制文件(包括但不限于.java文件),称为字节码文件,字节码也可能是从网络传输过来的一段二进制流。

一个可运行的(被JVM认可)字节码必须是符合字节码指令规范的,这样才能保证JVM跨平台的特性。

字节码的组成结构

魔数

标识文件类型的依据,通过文件的前四个字节来记录文件的类型。

好比身份证里的性别位,不管

Q:有了文件后缀,为什么还有记录文件类型呢?

A:文件后缀可以被随意更改,png修改为jpg等,而二进制文件修改起来不容易。

在Java的.class文件中,前四个字节存储的分别是,咖啡北鼻(谐音)

0 1 2 3
CA FE BA BE

而其它的常见文件类型存储的头4字节如下:

  • JPEG:FF D8 FF
  • PNG:89 50 4E 47
  • GIF:47 49 46 38
  • TIFF:49 49 2A 00

文件版本

字节码的第5~6记录的是次要版本号,7~8位记录是主要版本号

也就是.class文件编译时的jdk版本号,.class文件记录的是16进制。以下是常见版本号的对应关系

发布版本号 内部版本号(十六进制) 内部版本号(十进制)
1.5 31 49
1.6 32 50
1.7 33 51
1.8 34 52

class文件案例如下:

image-20230831150807127

如果class文件的版本号高于运行时jvm支持的版本号,则会抛出UnSupportedClassVersionError异常。

常量池

常量池存在的意义是什么?

我认为常量池内部存储了一些已知的字面量信息,像字符串,final数据,提前将这些数据存储好可以避免在程序运行时频繁创建重复对象。提高程序的运行效率。

还有就是常量池以槽位的方式进行存储,实际上被很对地方进行符号引用,这样程序在使用的时候不需要实际创建对象,就能获取到对象。

常量池包含3部分内容:字面量、符号引用、常量池类型数据表

常量池记录了定义这个类所有的信息,比如各个变量的名称,类型,值等信息,类信息,方法信息,字段信息,引用信息等。

image-20230831155844077

image-20230831161501828

访问标志

简单点来讲就是保存:

  • 修饰符:publicprivateprotected abstractfinal

修饰符可以修饰类、类属性、方法,所以字节码的访问标志就是保存这些东西,说明了类、字段、方法是否可以访问。

image-20230831165418024

以下是类和字段的所有访问标志信息:

实际上不需要记住所有的标志,了解后有个概念就可以。

表格由Chat-GPT生成

访问标志 十六进制值 说明
ACC_PUBLIC 字段 方法 0x0001 声明为public,可以从任何包访问
ACC_PRIVATE 字段 方法 0x0002 声明为private,只能在自己的类中访问
ACC_PROTECTED 字段 方法 0x0004 声明为protected,自己、子类及同一个包中类可以访问
ACC_STATIC 字段 方法 0x0008 声明为static,静态字段或方法
ACC_FINAL 字段 方法 0x0010 声明为final,不可被继承、覆盖或重新赋值
ACC_SUPER 0x0020 允许子类调用父类的方法
ACC_SYNCHRONIZED 方法 0x0020 声明方法为synchronized,线程安全
ACC_VOLATILE 字段 0x0040 声明字段为volatile,保证线程可见性
ACC_BRIDGE 方法 0x0040 指示方法为桥接方法(由编译器生成)
ACC_TRANSIENT 字段 0x0080 声明字段为transient,不会被序列化
ACC_VARARGS 方法 0x0080 声明方法接受可变数量的参数
ACC_NATIVE 方法 0x0100 声明方法为native,调用本地C/C++代码
ACC_INTERFACE 0x0200 声明为interface,接口类型
ACC_ABSTRACT 方法 0x0400 声明为abstract,抽象类或方法
ACC_STRICT 方法 0x0800 声明方法为strictfp,浮点模式严格
ACC_SYNTHETIC 字段 方法 0x1000 标记为synthetic,不由源代码直接产生
ACC_ANNOTATION 0x2000 声明为annotation,注解类型
ACC_ENUM 字段 0x4000 声明为enum,枚举类型

类/父类索引与接口索引集合

用于保存和记录类的父类,以及实现的接口等信息。

image-20230831161331573

因为java规定,只能单继承,但可以实现多个接口,所以接口会有多个,而父类信息就只会有单个

我们在jclasslib中的接口选项中就可以看到具体实现的接口信息:

image-20230831164853592

字段表集合

用于描述接口或类中的字段信息

image-20230831170457386

方法表集合

描述接口或类中的实例(Instance)方法和静态方法。

记录的方法的入参,出参信息。

image-20230831170803672

方法是在类加载阶段就会执行的方法,主要就是对静态的变量进行初始化,他是在编译时自动生成的

属性表

image-20230831172325035

记录的源文件的信息,和类的注解信息

完整的字节码

源代码

package cn.yufire.aqs;

/**
 * @author Yu
 * @date 2023-01-07 0007 12:03:12
 * javap 反编译 栈帧测试
 */
public class ZhanZhen {

    // main入栈
    public static void main(String[] args) {
        // method1() 入栈
        System.out.println(method1());
    }

	
    public static int method1() {
// 返回给main() method2() 入栈
        return method2();
    }

    public static int method2() {
 // 返回给method1() method3() 入栈
        return 5 + method3();
    }

    public static int method3() {
        // 局部变量表
        int a = 21;
        int b = 44;
   	  // 返回给method2()
        return a + b;
    }
}

源代码对应的字节码

Classfile /D:/WorkSpace/Project/IDEA/Java-Learing/target/classes/cn/yufire/aqs/ZhanZhen.class
  Last modified 2023-1-7; size 770 bytes
  MD5 checksum d74a90facb1bbfa0c2963dae1626b9f5
  Compiled from "ZhanZhen.java"
public class cn.yufire.aqs.ZhanZhen
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool: // 常量池
   #1 = Methodref          #8.#29         // java/lang/Object."":()V
   #2 = Fieldref           #30.#31        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #7.#32         // cn/yufire/aqs/ZhanZhen.method1:()I
   #4 = Methodref          #33.#34        // java/io/PrintStream.println:(I)V
   #5 = Methodref          #7.#35         // cn/yufire/aqs/ZhanZhen.method2:()I
   #6 = Methodref          #7.#36         // cn/yufire/aqs/ZhanZhen.method3:()I
   #7 = Class              #37            // cn/yufire/aqs/ZhanZhen
   #8 = Class              #38            // java/lang/Object
   #9 = Utf8               
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Lcn/yufire/aqs/ZhanZhen;
  #16 = Utf8               main
  #17 = Utf8               ([Ljava/lang/String;)V
  #18 = Utf8               args
  #19 = Utf8               [Ljava/lang/String;
  #20 = Utf8               method1
  #21 = Utf8               ()I
  #22 = Utf8               method2
  #23 = Utf8               method3
  #24 = Utf8               a
  #25 = Utf8               I
  #26 = Utf8               b
  #27 = Utf8               SourceFile
  #28 = Utf8               ZhanZhen.java
  #29 = NameAndType        #9:#10         // "":()V
  #30 = Class              #39            // java/lang/System
  #31 = NameAndType        #40:#41        // out:Ljava/io/PrintStream;
  #32 = NameAndType        #20:#21        // method1:()I
  #33 = Class              #42            // java/io/PrintStream
  #34 = NameAndType        #43:#44        // println:(I)V
  #35 = NameAndType        #22:#21        // method2:()I
  #36 = NameAndType        #23:#21        // method3:()I
  #37 = Utf8               cn/yufire/aqs/ZhanZhen
  #38 = Utf8               java/lang/Object
  #39 = Utf8               java/lang/System
  #40 = Utf8               out
  #41 = Utf8               Ljava/io/PrintStream;
  #42 = Utf8               java/io/PrintStream
  #43 = Utf8               println
  #44 = Utf8               (I)V
{ // 类信息和方法信息
  public cn.yufire.aqs.ZhanZhen();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcn/yufire/aqs/ZhanZhen;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
        						// 这个地方的#2、#3、#4是动态链接,对应的就是常量池的#2、3、4
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: invokestatic  #3                  // Method method1:()I
         6: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
         9: return
      LineNumberTable:
        line 11: 0
        line 12: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  args   [Ljava/lang/String;

  public static int method1();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0  //stack:操作数栈的最大深度、locals:局部变量表的长度、args_size:方法接收参数的个数(this参数)
         0: invokestatic  #5                  // Method method2:()I
         3: ireturn
      LineNumberTable:
        line 16: 0

  public static int method2();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_STATIC
    Code: 
      stack=2, locals=0, args_size=0    //stack:操作数栈的最大深度、locals:局部变量表的长度、args_size:方法接收参数的个数(this参数)
         0: iconst_5
         1: invokestatic  #6                  // Method method3:()I
         4: iadd
         5: ireturn
      LineNumberTable:
        line 20: 0

  public static int method3();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=0 //stack:操作数栈的最大深度、locals:局部变量表的长度、args_size:方法接收参数的个数(this参数)
		偏移量:操作码			操作数
			0: bipush        21
         2: istore_0
         3: bipush        44
         5: istore_1
         6: iload_0
         7: iload_1
         8: iadd
         9: ireturn
		//行号表:指明字节码指令的偏移量与Java源代码中代码的行号的一一对应关系                                     
      LineNumberTable:
        line 24: 0
        line 25: 3
        line 27: 6
		//局部变量表:描述内部局部变量的相关信息                                     
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            3       7     0     a   I
            6       4     1     b   I
}

字节码指令

字节码指令是JVM规定的运行时指令,通俗的来讲就是JVM能看懂的语言,通过这些指令JVM可以得知要进行什么操作,要干什么。有了这些指令代码才能够正常运行。

  • 字节码指令与OS无关,由JVM统一处理和执行。
  • 字节码指令总数不超过256个。也就是说字节码指令的不会大于256个。

字节码指令格式

使用了两个常见的指令作为案例,实际上指令个数远远不止2个。

  • invokevirtual #8 //cp_info#8:Java/lang/StringBuilder.append
    • invokevirtual 字节码指令
    • #8 符号引用,引用保存在常量池内的#8号槽位
      • 拿到引用信息后就可以得知要执行什么方法了
  • new #6 //cp_info#6:
    • new 字节码指令
    • #6 创建一个#6槽位的引用对象
    • 也就是创建一个StringBuilder对象,自动保存在堆内存中。

实际的字节码信息如下

左侧为代码,右侧为代码对应的字节码指令

image-20230901104414859

字节码指令分类

字节码指令可以分为以下几大类:

image-20230901104707683

完整的字节码指令图如下:

高清版下载地址:传送门

字节码指令图

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论