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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,295评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,928评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,682评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,209评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,237评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,965评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,586评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,487评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,016评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,136评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,271评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,948评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,619评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,139评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,252评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,598评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,267评论 2 358

推荐阅读更多精彩内容

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