美文网首页
Java代码编译后的class文件

Java代码编译后的class文件

作者: 刚子来简书啦 | 来源:发表于2020-09-09 20:57 被阅读0次

还是熟悉的味道,还是最简单的代码。

// Hello.java
public class Hello {
  public static void main(String[] args) {
    System.out.println("Hello World!");
  }
}

使用javac编译 .java 代码,得到同名的 .class文件,然后使用 java 命令,执行类名就可以运行了。

$ javac Hello.java
$ java Hello
Hello World!

编译后的class文件,以十六进制格式显示,长这个样子:

cafe babe 0000 0034 001d 0a00 0600 0f09
0010 0011 0800 120a 0013 0014 0700 1507
0016 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 046d 6169
6e01 0016 285b 4c6a 6176 612f 6c61 6e67
2f53 7472 696e 673b 2956 0100 0a53 6f75
7263 6546 696c 6501 000a 4865 6c6c 6f2e
6a61 7661 0c00 0700 0807 0017 0c00 1800
1901 000c 4865 6c6c 6f20 576f 726c 6421
0700 1a0c 001b 001c 0100 0548 656c 6c6f
0100 106a 6176 612f 6c61 6e67 2f4f 626a
6563 7401 0010 6a61 7661 2f6c 616e 672f
5379 7374 656d 0100 036f 7574 0100 154c
6a61 7661 2f69 6f2f 5072 696e 7453 7472
6561 6d3b 0100 136a 6176 612f 696f 2f50
7269 6e74 5374 7265 616d 0100 0770 7269
6e74 6c6e 0100 1528 4c6a 6176 612f 6c61
6e67 2f53 7472 696e 673b 2956 0021 0005
0006 0000 0000 0002 0001 0007 0008 0001
0009 0000 001d 0001 0001 0000 0005 2ab7
0001 b100 0000 0100 0a00 0000 0600 0100
0000 0100 0900 0b00 0c00 0100 0900 0000
2500 0200 0100 0000 09b2 0002 1203 b600
04b1 0000 0001 000a 0000 000a 0002 0000
0003 0008 0004 0001 000d 0000 0002 000e

这就是可供Java虚拟机执行的字节码文件,也是Java之所以能够实现,一次编译多次运行的根本原因。

把代码编译成一个中间状态的字节码,而不是直接运行在操作系统上的机器码,使得跨系统的工作就完全交给Java虚拟机了。这样一来,就可以大大减少开发人员的适配工作量,从而提高开发效率,这就是所谓的平台无关性

采用Java虚拟机的架构设计,为语言扩展留下了空间,相当于给代码编译和操作系统做了一个中间件,只要将代码编译成字节码文件,就都可以运行在Java虚拟机上,这也是为什么会有Scala 、Kotlin、Groovy等多种语言都可以与Java混合编码的原因,这就是所谓的语言无关性

既然我们看到了这个字节码文件,那就得好好解读一下了。其实只要是代码编译的文件,都是有约定的规范格式的,字节码也是一样的。

1. class文件结构

类型 名称 说明 备注或示例对照
u4 magic 魔数,识别class文件 cafe babe
u2 minor_version 副版本号 0000
u2 major_version 主版本号 0034
u2 constant_pool_count 常量池计数器 001d,十进制值为29
cp_info constant_pool 常量池 常量个数为constant_pool_count-1,
示例即为28个常量
u2 access_flags 访问标志 0021
u2 this_class 类索引 0005
u2 super_class 父类索引 0006
u2 interfaces_count 接口计数器 0000
u2 interfaces 接口索引集合
u2 fields_count 字段个数 0000
field_info fields 字段集合
u2 methods_count 方法计数器 0002
method_info methods 方法集合
u2 attributes_count 附加属性计数器 0001
attribute_info attributes 附加属性集合 000d 0000 0002 000e

class文件伪结构中只有两种数据类型:无符号数和表。

无符号数属于基本的数据类型,以u1、u2、u4和u8来分表代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。

是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯地以"_info"结尾。表用于描述有层次关系的复合结构的数据,而整个class文件本质上就是一张表

2. 常量类型

类型 标志 描述
CONSTANT_Utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整形字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整型字面量
CONSTANT_Double_info 6 双精度浮点型字面量
CONSTANT_Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MothodType_info 16 标识方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

3. 常量结构

常量 项目 类型 描述
CONSTANT_Utf8_info tag u1 值为1
length u2 utf-8编码的字符串占用的字节数
bytes u1 长度为length的utf-8编码的字符串
CONSTANT_Integer_info tag u1 值为3
bytes u4 按照高位在前存储的int值
CONSTANT_Float_info tag u1 值为4
bytes u4 按照高位在前存储的floatt值
CONSTANT_Long_info tag u1 值为5
bytes u8 按照高位在前存储的long值
CONSTANT_Double_info tag u1 值为6
bytes u8 按照高位在前存储double值
CONSTANT_Class_info tag u1 值为7
index u2 指向全限定名常量的索引
CONSTANT_String_info tag u1 值为8
index u2 指向字符串字面量的索引
CONSTANT_Fieldref_info tag u1 值为9
index u2 指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项
index u2 指向字段描述符CONSTANT_NameAndType_info的索引项
CONSTANT_Methodref_info tag u1 值为10
index u2 指向声明方法的类描述符CONSTANT_Class_info的索引项
index u2 指向名称及类型描述符CONSTANT_NameAndType_info的索引项
CONSTANT_InterfaceMethodref_info tag u1 值为11
index u2 指向声明方法的接口描述符CONSTANT_Class_info的索引项
index u2 指向名称及类型描述符CONSTANT_NameAndType_info的索引项
CONSTANT_NameAndType_info tag u1 值为12
index u2 指向该字段或方法名称常量项的索引
index u2 指向该字段或方法描述符常量项的索引
CONSTANT_MethodHandle_info tag u1 值为15
reference_kind u1 值必须是1-9,它决定了方法句柄的类型,方法句柄类型的值表示方法句柄的字节码行为
reference_index u2 值必须是对常量池的有效索引
CONSTANT_MothodType_info tag u1 值为16
descriptor_index u2 值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符
CONSTANT_InvokeDynamic_info tag u1 值为18
bootstrap_method_attr_index u2 值必须是对当前Class文件中引导方法表示的bootstrap_methods[]数组的有效索引
name_and_type_index u2 值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符

人工解读示例字节码中的常量池部分

0a 0013 0014 0700 1507
0016 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 046d 6169
6e01 0016 285b 4c6a 6176 612f 6c61 6e67
2f53 7472 696e 673b 2956 0100 0a53 6f75
7263 6546 696c 6501 000a 4865 6c6c 6f2e
6a61 7661 0c00 0700 0807 0017 0c00 1800
1901 000c 4865 6c6c 6f20 576f 726c 6421
0700 1a0c 001b 001c 0100 0548 656c 6c6f
0100 106a 6176 612f 6c61 6e67 2f4f 626a
6563 7401 0010 6a61 7661 2f6c 616e 672f
5379 7374 656d 0100 036f 7574 0100 154c
6a61 7661 2f69 6f2f 5072 696e 7453 7472
6561 6d3b 0100 136a 6176 612f 696f 2f50
7269 6e74 5374 7265 616d 0100 0770 7269
6e74 6c6e 0100 1528 4c6a 6176 612f 6c61
6e67 2f53 7472 696e 673b 2956
index 十六进制码 长度 类型或备注
1 0a 1 CONSTANT_Methodref_info
0006 2 #6
000f 2 #15
2 09 1 CONSTANT_Fieldref_info
0010 2 #10
0011 2 #11
3 08 1 CONSTANT_String_info
0012 2 #18
4 0a 1 CONSTANT_Methodref_info
0013 2 #19
0014 2 #20
5 07 1 CONSTANT_Class_info
0015 2 #21
6 07 1 CONSTANT_Class_info
0016 2 #22
7 01 1 CONSTANT_Utf8_info
0006 2 length=6
3c 696e 6974 3e 6 <init>
8 01 1 CONSTANT_Utf8_info
0003 2 length=3
2829 56 3 ()V
9 01 1 CONSTANT_Utf8_info
0004 2 length=4
436f 6465 4 Code
10 01 1 CONSTANT_Utf8_info
000f 2 length=15
4c 696e 654e 756d 6265 7254 6162 6c65 15 LineNumberTable
11 01 1 CONSTANT_Utf8_info
0004 2
6d 6169 6e 4 main
12 01 1 CONSTANT_Utf8_info
0016 2
285b 4c6a 6176 612f 6c61 6e672f53 7472 696e 673b 2956 22 ([Ljava/lang/String;)V
13 01 1 CONSTANT_Utf8_info
000a 2
53 6f757263 6546 696c 65 10 SourceFile
14 01 1 CONSTANT_Utf8_info
000a 2
4865 6c6c 6f2e 6a61 7661 10 Hello.java
15 0c 1 CONSTANT_NameAndType_info
0007 2
0008 2
16 07 1 CONSTANT_Class_info
0017 2
17 0c 1 CONSTANT_NameAndType_info
0018 2
0019 2
18 01 1 CONSTANT_Utf8_info
000c 2
4865 6c6c 6f20 576f 726c 6421 12 Hello World!
19 07 1 CONSTANT_Class_info
001a 2
20 0c 1 CONSTANT_NameAndType_info
001b 2
001c 2
21 01 1 CONSTANT_Utf8_info
0005 2
48 656c 6c6f 5 Hello
22 01 1 CONSTANT_Utf8_info
0010 2
6a 6176 612f 6c61 6e67 2f4f 626a 6563 74 16 java/lang/Object
23 01 1 CONSTANT_Utf8_info
0010 2
6a61 7661 2f6c 616e 672f 5379 7374 656d 16 java/lang/System
24 01 1 CONSTANT_Utf8_info
0003 2
6f 7574 3 out
25 01 1 CONSTANT_Utf8_info
0015 2
4c 6a61 7661 2f69 6f2f 5072 696e 7453 74726561 6d3b 21 Ljava/io/PrintStream;
26 01 1 CONSTANT_Utf8_info
0013 2
6a 6176 612f 696f 2f50 7269 6e74 5374 7265 616d 19 java/io/PrintStream
27 01 1 CONSTANT_Utf8_info
0007 2
70 7269 6e74 6c6e 7 println
28 01 1 CONSTANT_Utf8_info
0015 2
28 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 2956 21 (Ljava/lang/String;)V

4. 类访问标志

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public类型
ACC_FINAL 0x0010 是否被声明为final,只有类可以设置
ACC_SUPER 0x0020 是否允许使用invokespecial字节码指令的新语义,JDK1.0.2之后编译出来的类的这个标志默认为真
ACC_INTERFACE 0x0200 标志这是一个接口
ACC_ABSTRACT 0x0400 是否为abstract类型,对于接口或者抽象类来说,此标志值为真,其他类型为假
ACC_SYNTHETIC 0x1000 标志这个类并非由用户代码产生
ACC_ANNOTATION 0x2000 标志这是一个注解
ACC_ENUM 0x4000 标志这是一个枚举

5. 方法表结构

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

6. 方法访问标志

标志名称 标志值 含义
ACC_PUBLIC 0x0001 方法是否为public
ACC_PRIVATE 0x0002 方法是否为private
ACC_PROTECTED 0x0004 方法是否为protected
ACC_STATIC 0x0008 方法是否为static
ACC_FINAL 0x0010 方法是否为final
ACC_SYHCHRONRIZED 0x0020 方法是否为synchronized
ACC_BRIDGE 0x0040 方法是否是有编译器产生的方法
ACC_VARARGS 0x0080 方法是否接受参数
ACC_NATIVE 0x0100 方法是否为native
ACC_ABSTRACT 0x0400 方法是否为abstract
ACC_STRICTFP 0x0800 方法是否为strictfp
ACC_SYNTHETIC 0x1000 方法是否是有编译器自动产生的

7. 通用属性结构表

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u1 info attribute_length

人工解读示例字节码中的方法部分

0002 0001 0007 0008 0001
0009 0000 001d 0001 0001 0000 0005 2ab7
0001 b100 0000 0100 0a00 0000 0600 0100
0000 0100 0900 0b00 0c00 0100 0900 0000
2500 0200 0100 0000 09b2 0002 1203 b600
04b1 0000 0001 000a 0000 000a 0002 0000
0003 0008 0004
十六进制码 类型或备注
0002 方法个数为2
第一个方法 0001 方法标志为public
0007 方法名索引为7, <init>
0008 方法描述符索引为8,()V
0001 attribute个数为1
第一个属性 0009 属性索引为9, Code
0000 001d 属性值长度为29
0001 0001 0000 0005 2ab7 0001 b100 0000 0100 0a00 0000 0600 0100 0000 01 29个字节
第二个方法 00 09 方法标志为public,static
00 0b 方法名索引为11,main
00 0c 方法描述符索引为12,([Ljava/lang/String;)V
00 01 attribute个数为1
第一个属性 00 09 属性索引为9, Code
00 0000 25 属性值长度为37
00 0200 0100 0000 09b2 0002 1203 b600 04b1 0000 0001 000a 0000 000a 0002 0000 0003 0008 0004 37个字节

属性类型比较多,字段、接口和方法中都存在,但是都可以按照通用结构进行字节拆分。各个属性有自己的表结构,不做逐一列举,只列出常用的Code结构做栗子。

8. Code属性结构表

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 max_stack 1
u2 max_locals 1
u4 code_length 1
u1 code code_length
u2 exception_table_length 1
exception_info exception_table exception_table_length
u2 attributes_count 1
attribute_info attributes attributes_count

逐字节解读虽然繁琐,但是能加深对字节码的结构的理解。不过,在掌握了结构原理之后,使用工具才是最实在的,毕竟人工解读的效率太低了。

$ javap -verbose Hello.class
Classfile /Users/cage/Study/java_cmd/Hello.class
  Last modified 2020-3-15; size 416 bytes
  MD5 checksum e2e1c1e330c7df4592041d815080126b
  Compiled from "Hello.java"
public class Hello
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            // Hello World!
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // Hello
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               Hello.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Utf8               Hello World!
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               Hello
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  public Hello();
    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 1: 0

  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 3: 0
        line 4: 8
}
SourceFile: "Hello.java"

有兴趣的同学,平时可以经常看看字节码文件及其结构,看都用到了哪些字节码指令,这样对理解Java代码的执行会更有好处。

相关文章

网友评论

      本文标题:Java代码编译后的class文件

      本文链接:https://www.haomeiwen.com/subject/ejtfshtx.html