HelloWorld字节码分析

452 阅读18分钟

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

案例

/**
 * @Author blackcat
 * @version: 1.0
 * @description:hello world 示例
 */
public class HelloWorld {

    public static void main(String[] args) { 
        System.out.println("hello world"); 
    }
}

生成的二进制

image-20210220134847130.png

ClassFile 结构

​ Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地 排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎 全部是程序运行的必要数据,没有空隙存在。当遇到需要占用8位字节以上空间的数据项 时,则会按照高位在前的方式分割成若干个8位字节进行存储。根据Java虚拟机规范的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表

无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个 字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8 编码构成字符串值。 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地 以“_info”结尾,表用于描述有层次关系的复合结构的数据。


class ClassFile {
 	u4 magic;  //魔数 0xCAFEBABE
 	u2 minor_version;  //次版本号
 	u2 major_version;  //主版本号
 	u2 constant_pool_count; //常量池计数器
 	cp_info constant_pool[constant_pool_count-1];//常量池
 	u2 access_flags;//访问标记
 	u2 this_class;  //类索引
 	u2 super_class; //父类索引
 	u2 interfaces_count; //接口计数器
 	u2 interfaces[interfaces_count];//接口表
 	u2 fields_count;//字段计数器
 	field_info fields[fields_count];//字段表
 	u2 methods_count; //方法计数器
 	method_info methods[methods_count];//方法表
 	u2 attributes_count;//属性计数器
 	attribute_info attributes[attributes_count];//属性表
}

魔数(magic):

ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09

**minor_version:**0

ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09

**major_version:**52

ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09

**constant_pool_count:**34(#0 不计数)

ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09

cp_info constant_pool;

通用格式:


cp_info {
	u1 tag;
	u1 info[];
}

常量池项目

类型标志(tag 区分类型)描述
CONSTANT_Utf8_info1UTF-8 编码的字符串
CONSTANT_Integer_info3整性字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或则接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_Fieldref_info9字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中的方法符号引用
CONSTANT_NameAndType_info12字段或者方法的部分符号引用
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_MethodType_info16标识方法类型
CONSTANT_InvokeDynamic_info18表示一个动态方法的调用点

常量池#1

ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09

CONSTANT_Methodref_info 结构

项目类型描述
tagu1值为10
indexu2指向声明方法的类描述符CONSTANT_Class_info的索引项
indexu2指向名称及类型描述符 CONSTANT_NameAndType_info的索引项

CONSTANT_Methodref_info #6 .# 20

常量池#2

ca fe ba be 00 00 00 34 00 22 0a 00 06 00 14 09

00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07

CONSTANT_Fieldref_info

项目类型描述
tagu1值为9
indexu2指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项
indexu2指向字段描述符 CONSTANT_NameAndType_info的索引项

CONSTANT_Fieldref_info #21.#22

常量池#3

00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07

CONSTANT_String_info

项目类型描述
tagu1值为8
indexu2指向字符串字面量的索引

CONSTANT_String_info #23

常量池#4

00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07

CONSTANT_Methodref_info #24 .# 25

常量池#5 — 常量池#6

00 15 00 16 08 00 17 0a 00 18 00 19 07 00 1a 07

00 1b 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29

CONSTANT_Class_info

项目类型描述
tagu1值为7
indexu2指向全限定名常量项的索引

#5 CONSTANT_Class_info #26

#6 CONSTANT_Class_info #27

常量池#7 — 常量池#19

CONSTANT_Utf8_info

项目类型描述
tagu1值为1
lengthu2UTF-8编码的字符串占用的字节数
byteu1长度为length的UTF-8编码的字符串

00 1b 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29

#7 CONSTANT_Utf8_info

00 1b 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29

56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e

#8 CONSTANT_Utf8_info ()V

56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e

#9 CONSTANT_Utf8_info Code

56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e

75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63

#10 CONSTANT_Utf8_info LineNumberTable

75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63

61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01

#11 CONSTANT_Utf8_info LocalVariableTable

61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01

00 04 74 68 69 73 01 00 23 4c 63 6f 6d 2f 62 6c

#12 CONSTANT_Utf8_info this

00 04 74 68 69 73 01 00 23 4c 63 6f 6d 2f 62 6c

61 63 6b 2f 63 61 74 2f 6a 76 6d 2f 63 68 30 31

2f 48 65 6c 6c 6f 57 6f 72 6c 64 3d 01 00 04 6d

#13 CONSTANT_Utf8_info Lcom/black/cat/jvm/ch01/HelloWorld;

2f 48 65 6c 6c 6f 57 6f 72 6c 64 3d 01 00 04 6d

61 69 6e 01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61

#14 CONSTANT_Utf8_info main

61 69 6e 01 00 16 28 5b 4c 6a 61 76 61 2f 6c 61

6e 67 2f 53 74 72 69 6e 67 3b 29 56 01 00 04 61

#15 CONSTANT_Utf8_info ([Ljava/lang/String;)V

6e 67 2f 53 74 72 69 6e 67 3b 29 56 01 00 04 61

72 67 73 01 00 13 5b 4c 6a 61 76 61 2f 6c 61 6e

#16 CONSTANT_Utf8_info args

72 67 73 01 00 13 5b 4c 6a 61 76 61 2f 6c 61 6e

67 2f 53 74 72 69 6e 67 3b 01 00 0a 53 6f 75 72

#17 CONSTANT_Utf8_info [Ljava/lang/String;

67 2f 53 74 72 69 6e 67 3b 01 00 0a 53 6f 75 72

63 65 46 69 6c 65 01 00 0f 48 65 6c 6c 6f 57 6f

#18 CONSTANT_Utf8_info SourceFile

63 65 46 69 6c 65 01 00 0f 48 65 6c 6c 6f 57 6f

72 6c 64 2e 6a 61 76 61 0c 00 07 00 08 07 00 1c

#19 CONSTANT_Utf8_info HelloWorld.java

常量池#20

72 6c 64 2e 6a 61 76 61 0c 00 07 00 08 07 00 1c

CONSTANT_NameAndType_info

项目类型描述
tagu1值为12
indexu2指向该字段或方法名称常量项的索引
indexu2指向该字段或方法描述符常量项的索引

NameAndType #7:#8 // "":()V

常量池#21

72 6c 64 2e 6a 61 76 61 0c 00 07 00 08 07 00 1c

#21 CONSTANT_Class_info #28

常量池#22

0c 00 1d 00 1e 01 00 0b 68 65 6c 6c 6f 20 77 6f

#22 NameAndType #29:#30 // out:Ljava/io/PrintStream;

常量池#23

0c 00 1d 00 1e 01 00 0b 68 65 6c 6c 6f 20 77 6f

72 6c 64 07 00 1f 0c 00 20 00 21 01 00 21 63 6f

#23 CONSTANT_Utf8_info HelloWorld.java

常量池#24

72 6c 64 07 00 1f 0c 00 20 00 21 01 00 21 63 6f

#24 CONSTANT_Class_info #31

常量池#25

72 6c 64 07 00 1f 0c 00 20 00 21 01 00 21 63 6f

#25 NameAndType #32:#33

常量池#26 — 常量池#33

72 6c 64 07 00 1f 0c 00 20 00 21 01 00 21 63 6f

6d 2f 62 6c 61 63 6b 2f 63 61 74 2f 6a 76 6d 2f

63 68 30 31 2f 48 65 6c 6c 6f 57 6f 72 6c 64 01

#26 CONSTANT_Utf8_info com/black/cat/jvm/ch01/HelloWorld

63 68 30 31 2f 48 65 6c 6c 6f 57 6f 72 6c 64 01

00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65

63 74 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53

#27 CONSTANT_Utf8_info java/lang/Object

63 74 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53

79 73 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a

#28 CONSTANT_Utf8_info java/lang/System

79 73 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a

#29 CONSTANT_Utf8_info out

79 73 74 65 6d 01 00 03 6f 75 74 01 00 15 4c 6a

61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65

61 6d 3b 01 00 13 6a 61 76 61 2f 69 6f 2f 50 72

#30 CONSTANT_Utf8_info Ljava/io/PrintStream;

61 6d 3b 01 00 13 6a 61 76 61 2f 69 6f 2f 50 72

69 6e 74 53 74 72 65 61 6d 01 00 07 70 72 69 6e

#31 CONSTANT_Utf8_info java/io/PrintStream

69 6e 74 53 74 72 65 61 6d 01 00 07 70 72 69 6e

74 6c 6e 01 00 15 28 4c 6a 61 76 61 2f 6c 61 6e

#32 CONSTANT_Utf8_info println

74 6c 6e 01 00 15 28 4c 6a 61 76 61 2f 6c 61 6e

67 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00

#33 CONSTANT_Utf8_info (Ljava/lang/String;)V

access_flags

67 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00

标志名称标志值含义
ACC_PUBLIC0x0001是否为public
ACC_FINAL0x0010是否被声明为final,只有类可设置
ACC_SUPER0x0020是否允许使用invokespecial 字节码指令得新语意,invokespecial指令得语意在JDK 1.0.2发生改变,为了区别这条指令使用哪种语意,在JDK1.0.2之后便宜出来的类这个标志是真
ACC_INTERFACE0x0200标识这是一个接口
ACC_ABSTRACT0x0400是否为abstract类型,对于接口或者抽象类来说,此标志值为真,其他类值为假
ACC_SYNTHETIC0x1000标识这个类并非由用户代码产生
ACC_ANNOTATION0x2000标识这是一个注解
ACC_ENUM0x4000标识这是一个枚举

this_class

67 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00

常量池第5项 #5 #26 // com/black/cat/jvm/ch01/HelloWorld

super_class

67 2f 53 74 72 69 6e 67 3b 29 56 00 21 00 05 00

06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00

常量池第6项 #6 #27 // java/lang/Object

interfaces_count

06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00

没有接口,没有接口表的信息

fields_count

06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00

没有字段,没有字段表的信息

methods_count:2个

06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00

method_info methods

class method_info {
 u2 access_flags;//访问标志
 u2 name_index;  //名称索引,常量池的有效索引
 u2 descriptor_index; //描述符索引,常量池的有效索引
 u2 attributes_count; //方法附加属性的数量
 attribute_info attributes[attributes_count];//方法的附加属性
}
标志名称标志值含义
ACC_PUBLIC0x0001方法是否public
ACC_PRIVATE0x0002方法是否private
ACC_PROTECTED0x0004方法是否protected
ACC_STATIC0x0008方法是否static
ACC_FINAL0x0010方法是否final
ACC_SYNCHRONIZED0x0020方法是否synchronized
ACC_BRIDGE0x0040方法是否由编译器产生的桥接方法
ACC_VARARGS0x0080方法是否接受不定参数
ACC_NATIVE0x1000方法是否为native
ACC_ABSTRACT0x0400方法是否为abstract
ACC_STRICTFP0x0800方法是否为strictfp
ACC_SYNTHETIC0x1000方法是否由编译器自动产生

06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00

access_flags :00 01 public

name_index: 00 07

descriptor_index : 00 08

attributes_count: 00 01

06 00 00 00 00 00 02 00 01 00 07 00 08 00 01 00

09 00 00 00 2f 00 01 00 01 00 00 00 05 2a b7 00

01 b1 00 00 00 02 00 0a 00 00 00 06 00 01 00 00

00 09 00 0b 00 00 00 0c 00 01 00 00 00 05 00 0c

00 0d 00 00 00 09 00 0e 00 0f 00 01 00 09 00 00

方法一:public com.black.cat.jvm.ch01.HelloWorld();

00 09 #9 code

attribute_length:00 00 00 2f

max_stack:00 01

max_locals:00 01

code_length:00 00 00 05

code:2a b7 00 01 b1

exception_table_length:00 00

Code_attribute {
	u2 attribute_name_index;//对应常量池的索引项    code 
	u4 attribute_length; //表示当前属性的长度,不包括开始的6个字节
	u2 max_stack; //当前方法的操作数栈在运行执行的任何时间点的最大深度
	u2 max_locals;//分配在当前方法引用的局部变量表中的局部变量个数
	u4 code_length;//给出了当前方法的 code[]数组的字节数
	u1 code[code_length];//当前方法的 Java 虚拟机字节码
	u2 exception_table_length;// exception_table[]数组的成员个数量
 	exception_table[exception_table_length];{ 
    u2 start_pc;
	u2 end_pc;
	u2 handler_pc;
	u2 catch_type;}
	u2 attributes_count;
	attribute_info attributes[attributes_count];
}

attributes_count:00 02

attribute_info: 00 0a LineNumberTable

LineNumberTable_attribute {
	u2 attribute_name_index;
	u4 attribute_length;
	u2 line_number_table_length;
	{ u2 start_pc;
	u2 line_number;
	} line_number_table[line_number_table_length];
}

attribute_length:00 00 00 06

line_number_table_length: 0001

start_pc:0000

line_number:0009

attribute_info: 00 0b LocalVariableTable

LocalVariableTable_attribute {
	u2 attribute_name_index;
	u4 attribute_length;
	u2 local_variable_table_length;
	{ u2 start_pc;
	u2 length;
	u2 name_index;
	u2 descriptor_index;
	u2 index;
	} local_variable_table[local_variable_table_length];
}

attribute_length:00 00 00 0c

local_variable_table_length:00 01

start_pc:00 00

length:00 05

name_index:00 0c

descriptor_index:00 0d

index:00 00

00 0d 00 00 00 09 00 0e 00 0f 00 01 00 09 00 00

access_flags :00 09 public 、static

name_index: 00 0e

descriptor_index : 00 0f

attributes_count: 00 01

00 0d 00 00 00 09 00 0e 00 0f 00 01 00 09 00 00

00 33 00 02 00 01 00 00 00 09 b2 00 02 12 03 b6

00 04 b1 00 00 00 02 00 0a 00 00 00 06 00 01 00

00 00 0b 00 0b 00 00 00 0c 00 01 00 00 00 09 00

10 00 11 00 00 00 01 00 12 00 00 00 02 00 13

方法二: public static void main(java.lang.String[]);

00 09 #9 code

max_stack:00 02

max_locals:00 01

code_length:00 00 00 09

code:b2 00 02 12 03 b6 00 04 b1

exception_table_length:00 00

attributes_count :00 02

attribute_info: 00 0a LineNumberTable

​ attribute_length:00 00 00 06

​ line_number_table_length: 0001

   start_pc:0000

​ line_number:000b

attribute_info: 00 0b LocalVariableTable

​ attribute_length:00 00 00 0c

​ local_variable_table_length:00 01

​ start_pc:00 00

​ length:00 09

​ name_index:00 10

​ descriptor_index:00 11

​ index:00 00

attributes_count:1个

10 00 11 00 00 00 01 00 12 00 00 00 02 00 13

attribute_info attributes

10 00 11 00 00 00 01 00 12 00 00 00 02 00 13

0012 #12 SourceFile

00 00 00 02 长度为2

0013 #13 HelloWorld.java

属性表通用结构

类型名称数量
u2attribute_name_index1
u4attribute_length1
u1infoattribute_length

虚拟机规范预定义的属性(具体相关属性请查阅相关文档)

属性名称使用位置含义
Code方法表Java代码编译成的字节码指令
ConstantValue字段表由final关键字定义的常量值
Deprecated类、方法表、字段被声明为deprecated的方法和字段
Exceptions方法表方法抛出的异常列表
EnclosingMethod类文件仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法
InnerClass类文件内部类列表
LineNumberTableCode属性Java源码的行号与字节码指令的对应关系
LocalVariableTableCode属性方法的局部变量描述
StackMapTableCode属性JDK1.6中新增的属性,供新的类型检查检验器检查和处理目标方法的局部变量和操作数有所需要的类是否匹配
Signature类、方法表、字段用于支持泛型情况下的方法签名
SourceFile类文件记录源文件名称
SourceDebugExtension类文件用于存储额外的调试信息
Synthetic类、方法表、字段标志方法或字段为编译器自动生成的
LocalVariableTypeTable使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimeVisibleAnnotations类、方法表、字段为动态注解提供支持
RuntimeInvisibleAnnotations类、方法表、字段用于指明哪些注解是运行时不可见的
RuntimeVisibleParameterAnnotations方法表作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法
RuntimeInvisibleParameterAnnotations方法表作用与RuntimeInvisibleAnnotations属性类似,作用对象哪个为方法参数
AnnotationDefault方法表用于记录注解类元素的默认值
BootstrapMethos类文件用于保存invokeddynamic指令引用的引导方式限定符
RuntimeVisibleTypeAnnotations类、方法表、字段、Code属性指明哪些注解运行时可见的
RuntimeInvisibleTypeAnnotations类、方法表、字段、Code属性指明哪些注解运行时不可见的
MethodParameters方法表用于支持(编译时加上-parameters参数)将方法名称编译进class文件,并在运行中获取
Module用于记录一个Module名称及相关信息
ModulePackages用于记录一个模块中被exports 或则opens的包
ModuleMainClass指定一个模块的指令
NestHost用于支持嵌套类的反射和访问控制API,一个内部类通过该属性得知自己的宿主类
NestMembers用于支持嵌套类的反射和访问控制API,一个宿主类通过该属性得知自己有哪些内部类

使用javap 输出

Classfile /C:/Users/49504/Desktop/thread/target/classes/com/black/cat/jvm/ch01/HelloWorld.class
  Last modified 2021-2-20; size 575 bytes
  MD5 checksum 3ca5b4584f502ae13ddae23567f05d02
  Compiled from "HelloWorld.java"
public class com.black.cat.jvm.ch01.HelloWorld
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // hello world
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // com/black/cat/jvm/ch01/HelloWorld
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/black/cat/jvm/ch01/HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               hello world
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               com/black/cat/jvm/ch01/HelloWorld
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public com.black.cat.jvm.ch01.HelloWorld();
    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 9: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/black/cat/jvm/ch01/HelloWorld;

  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
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"