Java字节码结构

我们都知道,Java程序最终是转换成class文件执行在虚拟机上的,那么class文件是个怎样的结构,虚拟机又是如何处理去执行class文件里面的内容呢,这篇文章带你深入理解Java字节码中的结构。

准备工作

1.编写一个简单的源码

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

2.将java源码转化为.class文件

javac helloworld.java

3.以16进制的形式查看class文件

在Windows系统下需要一个可查看16进制的编辑器,因此我用linux系统生成了以下内容
方法:
①vim helloworld.class ,然后输入:%!xxd 就是以16进制显示class文件
②xxd helloworld.class helloworld.txt,然后cat helloworld.txt就可以显示16进制的class文件

0000000: cafe babe 0000 0034 0022 0a00 0600 1409  .......4."......
0000010: 0015 0016 0800 170a 0018 0019 0700 1a07  ................
0000020: 001b 0100 063c 696e 6974 3e01 0003 2829  .....<init>...()
0000030: 5601 0004 436f 6465 0100 0f4c 696e 654e  V...Code...LineN
0000040: 756d 6265 7254 6162 6c65 0100 124c 6f63  umberTable...Loc
0000050: 616c 5661 7269 6162 6c65 5461 626c 6501  alVariableTable.
0000060: 0004 7468 6973 0100 164c 6578 616d 706c  ..this...Lexampl
0000070: 655f 312f 6865 6c6c 6f77 6f72 6c64 3b01  e_1/helloworld;.
0000080: 0004 6d61 696e 0100 1628 5b4c 6a61 7661  ..main...([Ljava
0000090: 2f6c 616e 672f 5374 7269 6e67 3b29 5601  /lang/String;)V.
00000a0: 0004 6172 6773 0100 135b 4c6a 6176 612f  ..args...[Ljava/
00000b0: 6c61 6e67 2f53 7472 696e 673b 0100 0a53  lang/String;...S
00000c0: 6f75 7263 6546 696c 6501 000f 6865 6c6c  ourceFile...hell
00000d0: 6f77 6f72 6c64 2e6a 6176 610c 0007 0008  oworld.java.....
00000e0: 0700 1c0c 001d 001e 0100 0c48 656c 6c6f  ...........Hello
00000f0: 2057 6f72 6c64 2107 001f 0c00 2000 2101   World!..... .!.
0000100: 0014 6578 616d 706c 655f 312f 6865 6c6c  ..example_1/hell
0000110: 6f77 6f72 6c64 0100 106a 6176 612f 6c61  oworld...java/la
0000120: 6e67 2f4f 626a 6563 7401 0010 6a61 7661  ng/Object...java
0000130: 2f6c 616e 672f 5379 7374 656d 0100 036f  /lang/System...o
0000140: 7574 0100 154c 6a61 7661 2f69 6f2f 5072  ut...Ljava/io/Pr
0000150: 696e 7453 7472 6561 6d3b 0100 136a 6176  intStream;...jav
0000160: 612f 696f 2f50 7269 6e74 5374 7265 616d  a/io/PrintStream
0000170: 0100 0770 7269 6e74 6c6e 0100 1528 4c6a  ...println...(Lj
0000180: 6176 612f 6c61 6e67 2f53 7472 696e 673b  ava/lang/String;
0000190: 2956 0021 0005 0006 0000 0000 0002 0001  )V.!............
00001a0: 0007 0008 0001 0009 0000 002f 0001 0001  .........../....
00001b0: 0000 0005 2ab7 0001 b100 0000 0200 0a00  ....*...........
00001c0: 0000 0600 0100 0000 0700 0b00 0000 0c00  ................
00001d0: 0100 0000 0500 0c00 0d00 0000 0900 0e00  ................
00001e0: 0f00 0100 0900 0000 3700 0200 0100 0000  ........7.......
00001f0: 09b2 0002 1203 b600 04b1 0000 0002 000a  ................
0000200: 0000 000a 0002 0000 0009 0008 0016 000b  ................
0000210: 0000 000c 0001 0000 0009 0010 0011 0000  ................
0000220: 0001 0012 0000 0002 0013                 ..........

附上对照表:

类型 名称 说明 长度
u4固定值 magic 魔数,识别Class文件格式 4个字节
u2 minor_version 副版本号 2个字节
u2 major_version 主版本号 2个字节
u2 constant_pool_count 常量池计算器 2个字节
cp_info常量表 constant_pool 常量池 n个字节
u2 access_flags 访问标志 2个字节
u2 this_class 类索引 2个字节
u2 super_class 父类索引 2个字节
u2 interfaces_count 接口计数器 2个字节
u2 interfaces 接口索引集合 2个字节
u2 fields_count 字段个数 2个字节
field_info字段表 fields 字段集合 n个字节
u2 methods_count 方法计数器 2个字节
method_info方法表 methods 方法集合 n个字节
u2 attributes_count 附加属性计数器 2个字节
attribute_info属性表 attributes 附加属性集合 n个字节

注:一个字节=8位2进制数=2位16进制数

说明一下:class文件只有两种数据类型:无符号数和表。如下表所示:

数据类型 定义 说明
无符号数 无符号数可以用来描述数字、索引引用、数量值或按照utf-8编码构成的字符串值。 其中无符号数属于基本的数据类型。以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节
表是由多个无符号数或其他表构成的复合数据结构。 所有的表都以“_info”结尾。由于表没有固定长度,所以通常会在其前面加上个数说明。

实际上整个class文件就是一张表,其结构就是上面的表一了。

开始解读

1、魔数

咖啡宝贝是jvm识别文件是否为.class的关键

类型 名称 说明 长度 16进制数
u4固定值 magic 魔数,识别Class文件格式 4个字节 cafe babe

2、版本号

类型 名称 说明 长度 16进制数 10进制数
u2 minor_version 副版本号 2个字节 0000 0
u2 major_version 主版本号 2个字节 0034 52

主版本号52代表1.8,所以jdk的版本为1.8.0


3、常量池

常量池主要存放两大类常量:字面量和符号引用。如下表:

常量 具体的常量
字面量 文本字符串
声明为final的常量值
符号引用 类和接口的全限定名
字段的名称和描述符
方法的名称和描述符
类型 名称 说明 长度 16进制数 10进制数
u2 constant_pool_count 常量池计算器 2个字节 0022 34

常量池是从1开始,因为第0号常量被jvm占用,表示什么都不引用,所以应该有33个常量

类型 名称 说明 长度 备注
cp_info常量表 constant_pool 常量池 n个字节 第一个字节是tag位,后面的内容要查手册

附上手册:

该表的bit看成byte才正确

开始解读常量池
第一个常量:
tag-->0a(10)--> CONSTANT_Methodref_info
class_info2个字节--->00 06(#6)--->java/lang/Object
nameAndType2个字节---00 14(#20)---> "<init>":()V-->表示构造方法,没有入参,返回值为null
第二个常量:
tag-->09(9)--->Fieldref
class_info2个字节-->0015(#21)-->java/lang/System
nameAndType2个字节---00 16(#22)--->out:Ljava/io/PrintStream;
第三个常量:
tag-->08(8)--->String
index2个字节-->00 17(#23)-->Hello World!
.......
第七个常量:
tag-->01(1)->utf8
length-->00 06(6)
byte-->3c 696e 6974 3e-->转换成ASCLL码--><init>
第八个常量:
tag-->01(1)->utf8
length-->00 03(3)
byte-->2829 56-->转换成ASCLL码-->()V
接着也是依次按照手册解读

实际上我们在class文件的目录下敲一行简单的命令:javap -verbose helloworld,就可以得到以下内容

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            // example_1/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               Lexample_1/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               example_1/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

这里说几个字符串
#9 Code:存放JVM指令
#10 LineNumberTable:JVM指令与源码的映射关系(也是我们IDE可以找到我们错误源码行数的原因)
#11 LocalVariableTable:栈帧中局部变量表

{
  public example_1.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 7: 0    //表示code第0条指令对应第7行
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lexample_1/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 9: 0
        line 22: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "helloworld.java"

4、访问标志

类型 名称 说明 长度 16进制
u2 access_flags 访问标志 2个字节 0021

0x0021是通过位运算得出的:


5、类索引、父类索引、接口索引

1、访问标志后的两个字节就是类索引(This class name)
0005-->#5-->example_1/helloworld
2、类索引后的两个字节就是父类索引(super class name)
0006 -->#6-->java/lang/Object
3、父类索引后的两个字节则是接口索引计数器
0000-->即没有任何接口索引
通过这三项,就可以确定了这个类的继承关系了。

。。。。。
后面还有字段、方法、附加属性,我累了,不想解读了


附上两个资料:
博客:https://blog.csdn.net/u011810352/article/details/80316870
视频:https://www.bilibili.com/video/av79500296

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 这篇博客开始,我打算带大家去解读一下JVM平台下的字节码文件(熟悉而又陌生的感觉)。众所周知,Class文件包含了...
    java菜阅读 1,407评论 0 0
  • Class文件的结构 Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Cl...
    Java耕耘者阅读 251评论 0 0
  • 本文通过解析Class文件中字节码的结构,来加深对Java类文件结构的理解。建议先阅读Java类文件结构解析这篇文...
    tianbin阅读 423评论 0 0
  • 上篇介绍了字节码文件的结构和其常量池分析。紧接其后呢,我们要去了解字段表的概念和组成结构。接着上篇里的字节码的常量...
    java菜阅读 427评论 0 0
  • 长治路小学四年级穆思邈 无风,无雨,无雾。明净的天空中,飘着几朵软软的白云,犹如蚕丝一般柔软,让人感觉仿佛置身于一...
    芝兰幽香阅读 1,319评论 0 3