JAVA字节码

一文了解英伟达(NVIDIA)所有显卡芯片 英伟达(NVIDIA)的产品线庞杂繁复,从面向游戏玩家的GeForce RTX系列,到数据中心的H100、B200,再到Jetson、DRIVE等边缘智能平台,一般人很难以厘清其全貌。这篇文章将用通俗而专业的语言,一文打通英伟达显卡芯片体系,让你彻底读懂这个GPU帝国是如何运转的。 阅读详情

什么是class文件
JAVA字节码文件(.class文件)是java编译器编译java源文件(.java文件)所产生的目标文件。它是一种8位字节的二进制流文件,各个数据项按顺序紧密的从前向后排列,相邻的项之间没有间隙,这样使得class文件非常紧凑,体积轻巧,可被JVM快速加载到内存,并且占据较少的内存空间。理解字节码其实就是了解JVM是如何解析字节码的。JVM能够解析并执行字节码文件,而不关心字节码是由哪种语言编译而来的,所以理论上JVM还可以支持除了JAVA以外的其他能够编译生成字节码文件的语言,如Groovy,JRuby,Jython等,只要安装了虚拟机的平台(无论是windows,linux,还是mac),都可以直接运行字节码.

class文件结构

我们可以认为class文件只有两种数据结构:无符号数和表。

无符号数:属于基本的数据类型,以u1,u2,u4,u8来分别代表1个字节,2个字节,4个字节,8个字节的无符号数。

表:表是由多个无符号数或者其他表作为数据项构成的复合数据类型,class文件中所有的表都以"_info"结尾。其实,整个class文件本质上就是一张表。

一个典型的class文件结构会按照预先规定好的顺序紧密的从前向后排列,相邻之间没有间隙。如下图所示(该图很重要,最好能倒背如流):在这里插入图片描述
当JVM加载某个class文件时,JVM就是根据上图中的结构去解析class文件,在强调一遍(那顺序是不会改变的,严格按照上图给出的顺序),加载class文件到内存中,并在内存中分配相应的的空间。具体某一种结构需要占用多大空间,可以参考下图(该图也很重要):
在这里插入图片描述
实例分析

看十遍不如自己操作一遍,所以最好还是自己码一遍,加深理解.

首先编写一个简单的Java源代码 ByteCodeTestInRoyole.java,如下所示:

package com.example.helloroyole;
 
import java.io.Serializable;
 
/**
 * ClassName ByteCodeTestInRoyole
 * description
 *
 * @author Ace.Huang@royole.com
 * @date 2020/12/08
 */
public class ByteCodeTestInRoyole implements Serializable,Cloneable {
    private static final long serialVersionUID = -2793625091785878343L;
    private static final String KEY_SUMMARY = "color_temperature_title_key";
    private int count;
 
    private int add(int i,int y){
        return i + y;
    }
 
}

通过javac命令将其编译:
在这里插入图片描述
生成ByteCodeTestInRoyole.class字节码文件:
在这里插入图片描述
如果使用notepad++打开的话,会显示乱码,需要安装插件,我使用了Beyond Compare也可以打开,如下图所示(这图在以下说明中会反复提到):
在这里插入图片描述
上图都是一些16进制数字,每两个字符代表一个字节。如果是第一次看,会感觉各个字符之间毫无规律,跟乱码差不多,但其实在JVM的视角里这些16进制字符是按照严格的规律排列的。接下来就结合写的demo源码看下JVM是如何解析它们的。

我按照class文件结构图一个个来,首先是:

魔数 magic number(至于为什么叫魔数,我不知道…只是个叫法而已,无须纠结)

在class文件中开头的四个字节就表示该class文件的魔数,它是一个固定的值,0XCAFEBABE。魔数是class文件的标志,用来判断一个文件是不是class格式文件的标准,如果开头四个字节不是0XCAFEBABE,那么就说明它不是class文件,不能被JVM识别或加载。

版本号

版本号也是占4个字节,也就是紧跟在魔数后面的4个字节。前两个字节0000表示副版本号minor_version,后两个字节0034是主版本号major_version,对应的十进制值为52,也就是说当前class文件的主版本号为52,副版本号为0,所以综合版本号是52.0,这里说的版本号是指JDK的版本号,也就是jdk1.8.0。附上一张jdk版本对应的class文件的版本号:
在这里插入图片描述
常量池(重点,重中之重)constant_pool

紧跟在版本号之后的是一个叫做常量池的表(cp_info)。在常量池中保存了类的各种相关信息,比如类的名称,父类的名称,类的方法名,参数名称,参数类型等,这些信息都是以各种表的形式保存在常量池中的。

常量池中的每一项都是一个表,其项目类型共有14种,如下图所示:
在这里插入图片描述
以上14种表都有自己的结构,每一种结构可参考如下总表:
在这里插入图片描述
从表名可以得出其数据类型,比如,CONSTANT_utf8_info表,其数据类型就是CONSTANT_Utf8,常量池中的每一项都会有一个u1大小的tag值。tag值是表的标识,JVM解析class文件时,通过这个值来判断当前数据结构是哪一种表。

例如CONSTANT_Class_info表具体结构如下:

table CONSTANT_Class_info{
u1 tag = 7;
u2 name_index;
}

tag:占用一个字节,比如值为7,根据上图,说明是CONSTANT_Class_info类型表。

name_index:是一个索引值,可以将它理解为一个指针,指向常量池中索引为name_index的常量表。比如name_index = 2,则它指向常量池中第2个常量。

再比如CONSTANT_Utf8_info表具体结构如下:

table CONSTANT_Utf8_info{
u1 tag;
u2 length;
u1[] bytes;
}

tag:值为1,表示是CONSTANT_Utf8_info类型表。

length:length表示u1[]的长度,比如length = 5;则表示接下来的数据是5个连续的u1类型数据。

bytes:u1类型数组,长度为上面第2个参数length的值。

了解了这些概念后,紧接着我们上面字节码的分析,跟在版本号后是常量池,常量池的大小constant_pool_count占2个字节,即demo中的001e,即十进制30,其中下标为0的常量被JVM留作其他特殊用途,所以字节码常量池是从1开始计数的,实际中常量池中的大小为该值减1,也就是在本demo中有29个常量.

第一个常量,也就是紧接着常量池大小的一个字节0A,转为十进制后为10,通过查看常量池14种表格可知,10对应的表类型为CONSTANT_Methodref_info,其结构如下:

table CONSTANT_Methodref_info {
u1 tag = 10;
u2 class_index;  //指向此方法所属的类在常量池的索引
u2 name_type_index; //指向此方法的名称和类型的索引
}

也就是说在0A之后的2个字节0003指向这个方法是属于哪个类,紧接的2个字0018节指向这个方法的名称和类型。即:

0003:十进制3,表示指向常量池中的第3个常量。

0018:十进制24,表示指向常量池中的第24个常量。

至此,第一个常量就解读完了,紧接着的就是第二个常量,07,转为十进制后为7,同理通过查看常量池表14种表可知,7对应的表类型为CONSTANT_Class_info,该结构已经给出,接下来的2个字节0019指向这个类的名称。即:

0019:十进制25,表示指向常量池中的第25个常量。

第三个常量,跟在0019后面的第三个常量是07,同第二个常量一样,对应的表类型为CONSTANT_Class_info,接下来的2个字节001A指向这个类的名称。即:

001A:十进制26,表示指向常量池中的第26个常量。

以此类推,第四个常量,即跟在001A后面的一个字节07,对应的表类型也为CONSTANT_Class_info,接下来的2个字节001B指向这个类的名称。即:

001B:十进制27,表示指向常量池中的第27个常量。

第五个常量,即跟在001B后面的一个字节也是07,对应的表类型也为CONSTANT_Class_info,接下来的2个字节001C指向这个类的名称。即:

001C:十进制28,表示指向常量池中的第28个常量。

第六个常量,即跟在001C后面的一个字节01,转为十进制后为1,查看常量池14种表可知,1对应的表类型为CONSTANT_Utf8_info,重复一遍该结构如下:

table CONSTANT_Utf8_info{
u1 tag;
u2 length;
u1[] bytes;
}

也就是说在01之后的两个字节0010,表示这个字符串的长度是16字节,也就是后面的16个字节73 65 72 69 61 6C 56 65 72 73 69 6F 6E 55 49 44,通过ASCII码表转换后,表示的是字符串“serialVersionUID”。即:

0010:十进制16,表示该字符串的长度就是16字节。

73 65 72 69 61 6C 56 65 72 73 69 6F 6E 55 49 44:通过ASCII码表转换后,表示的是字符串“serialVersionUID”。

第七个常量,即跟在73 65 72 69 61 6C 56 65 72 73 69 6F 6E 55 49 44后面的是01,对应的表类型也是CONSTANT_Utf8_info,同理,接下来的2个字节00 01表示字符串的长度为1字节,后面跟着的1字节4A,通过ASCII码转换后,表示的是字符串“J”。即:

0001:十进制1,表示该字符串的长度为1字节。

4A:通过ASCII码转换后,表示的是字符串“J”。

第八个常量,即跟在4A后面的是01,对应的表类型也是CONSTANT_Utf8_info,同理,接下来的2个字节00 0D表示字符串的长度为13字节,后面跟着的13字节43 6F 6E 73 74 61 6E 74 56 61 6C 75 65,通过ASCII码转换后,表示的是字符串“ConstantValue”。即:

000D:十进制13,表示该字符串的长度为13字节。

43 6F 6E 73 74 61 6E 74 56 61 6C 75 65:通过ASCII码转换后,表示的是字符串“ConstantValue”。

第九个常量,即跟在43 6F 6E 73 74 61 6E 74 56 61 6C 75 65后面的是05,对应的表类型为CONSTANT_Long_info,由结构总表可知,接下来的8个字节D9 3B 0C C7 C0 7C 14 B9表示长整型:-2793625091785878343L。

备注1:此处有点蛋疼,先埋个雷,自己挖坑往里跳。
第十个常量(其实是第十一个),即跟在D9 3B 0C C7 C0 7C 14 B9后面的是01,对应的表类型为CONSTANT_Utf8_info,根据结构表,接下来的2个字节000B表示字符串的长度为11字节,则后面跟着的11字节4B 45 59 5F 53 55 4D 4D 41 52 59,通过ASCII码转换后,表示的是字符串“KEY_SUMMARY”。即:

000B:十进制11,表示该字符串的长度为11字节。

4B 45 59 5F 53 55 4D 4D 41 52 59:通过ASCII码转换后,表示的是字符串“KEY_SUMMARY”。

第十一个常量(其实是第十二个),01,表示对应的表类型为CONSTANT_Utf8_info,根据结构表,接下来的2个字节0012表示字符串的长度为18字节,则后面跟着的18字节4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B,通过ASCII码转换后,表示的是字符串“Ljava/lang/String;”。即:

0012:十进制18,表示该字符串的长度为18。

4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B:通过ASCII码转换后,表示的是字符串“Ljava/lang/String;”。

第十二个常量(其实是第十三个),08,表示对应的表类型为CONSTANT_String_info,根据结构表,接下来的2个字节001D表示指向字符串字面量的索引。即:

001D:十进制29,表示指向常量池中的第29个常量。

第十三个常量(其实是第十四个),01,这应该很熟悉了,对应的表类型为CONSTANT_Utf8_info,接下来的2字节0005表示字符串的长度为5字节,则后面跟着的5字节63 6F 75 6E 74,通过ASCII码转换后,表示的是字符串“count”。

第十四个常量(其实是第十五个),01,表类型CONSTANT_Utf8_info,接下来的2字节0001表示字符串的长度为1字节,则后面跟着的1字节49,通过ASCII码转换后,表示的是字符串“I”。

第十五个常量(其实是第十六个),01,表类型CONSTANT_Utf8_info,接下来的2字节0006表示字符串的长度为6字节,则后面跟着的6字节3C 69 6E 69 74 3E,通过ASCII码转换后,表示的是字符串“”。

第十六个常量(其实是第十七个),01,表类型CONSTANT_Utf8_info,接下来的2字节0003表示字符串的长度为3字节,则后面跟着的3字节28 29 56,通过ASCII码转换后,表示的是字符串“()V”。

第十七个常量(其实是第十八个),01,表类型CONSTANT_Utf8_info,接下来的2字节0004表示字符串的长度为4字节,则后面跟着的4字节43 6F 64 65,通过ASCII码转换后,表示的是字符串“Code”。

第十八个常量(其实是第十九个),01,表类型CONSTANT_Utf8_info,接下来的2字节000F表示字符串的长度为15字节,则后面跟着的15字节4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65,通过ASCII码转换后,表示的是字符串“LineNumberTable”。

第十九个常量(其实是第二十个),01,表类型CONSTANT_Utf8_info,接下来的2字节0003表示字符串的长度为3字节,则后面跟着的3字节61 64 64,通过ASCII码转换后,表示的是字符串“add”。

第二十个常量(其实是第二十一个),01,表类型CONSTANT_Utf8_info,接下来的2字节0005表示字符串的长度为5字节,则后面跟着的5字节28 49 49 29 49,通过ASCII码转换后,表示的是字符串“(II)I”。

第二十一个常量(其实是第二十二个),01,表类型CONSTANT_Utf8_info,接下来的2字节000A表示字符串的长度为10字节,则后面跟着的10字节53 6F 75 72 63 65 46 69 6C 65,通过ASCII码转换后,表示的是字符串“SourceFile”。

第二十二个常量(其实是第二十三个),01,表类型CONSTANT_Utf8_info,接下来的2字节0019表示字符串的长度为25字节,则后面跟着的25字节42 79 74 65 43 6F 64 65 54 65 73 74 49 6E 52 6F 79 6F 6C 65 2E 6A 61 76 61,通过ASCII码转换后,表示的是字符串“ByteCodeTestInRoyole.java”。

第二十三个常量(其实是第二十四个),0C,对应的十进制12,根据表类型可知,对应的表为CONSTANT_NameAndType_info,根据结构总表,可知接下来的两个字节0010表示指向该字段或方法名称的索引,紧接着的2个字节0011表示指向该字段或该方法描述符的索引。即:

0010:指向常量池中的第16个常量。

0011:指向常量池中的第17个常量。

第二十四个常量(其实是第二十五个),01,表类型CONSTANT_Utf8_info,接下来的2字节002C表示字符串的长度为44字节,则后面跟着的44字节63 6F 6D 2F 65 78 61 6D 70 6C 65 2F 68 65 6C 6C 6F 72 6F 79 6F 6C 65 2F 42 79 74 65 43 6F 64 65 54 65 73 74 49 6E 52 6F 79 6F 6C 65,通过ASCII码转换后,表示的是字符串“com/example/helloroyole/ByteCojideTestInRoyole”。

第二十五个常量(其实是第二十六个),01,表类型CONSTANT_Utf8_info,接下来的2字节0010表示字符串的长度为16字节,则后面跟着的16字节6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74,通过ASCII码转换后,表示的是字符串“java/lang/Object”。

第二十六个常量(其实是第二十七个),01,表类型CONSTANT_Utf8_info,接下来的2字节0014表示字符串的长度为16字节,则后面跟着的20字节6A 61 76 61 2F 69 6F 2F 53 65 72 69 61 6C 69 7A 61 62 6C 65,通过ASCII码转换后,表示的是字符串“java/io/Serializable”。

第二十七个常量(其实是第二十八个),01,表类型CONSTANT_Utf8_info,接下来的2字节0013表示字符串的长度为19字节,则后面跟着的19字节6A 61 76 61 2F 6C 61 6E 67 2F 43 6C 6F 6E 65 61 62 6C 65,通过ASCII码转换后,表示的是字符串“java/lang/Cloneable”。

第二十八个常量(其实是第二十九个),01,表类型CONSTANT_Utf8_info,接下来的2字节001B表示字符串的长度为27字节,则后面跟着的27字节63 6F 6C 6F 72 5F 74 65 6D 70 65 72 61 74 75 72 65 5F 74 69 74 6C 65 5F 6B 65 79,通过ASCII码转换后,表示的是字符串“color_temperature_title_key”。

第二十九个常量(其实到这里常量池已经分析完毕),也就是最后一个,发现紧接着的一个字节是00,查看类型表,what? 没有对应的类型?!难道是哪里出错了?暂时先不管。。。

其实我们上面这样一个个的分析字节码很累的,我们只是为了了解class文件的原理才来一步一步分析每一个二进制字节码,但自己实操过一次后,印象还是很深刻的,其实JDK提供了现成的工具可以直接解析此二进制文件,即javap工具(在JDK的bin目录下),我们通过javap命令来解析此class文件,结果如下图:
在这里插入图片描述
我们看到明明是有29个常量,仔细一看发现第9个常量之后,索引直接变成11了,10消失了。这也就是上文标红字体备注1所提到的坑。现在来填坑,经查找资料表明:long或者double类型的常量在常量池里要占据两个索引。因为我这边demo源码里面实现了Serializable接口,自动生成了一个long类型的serialVersionUID,所以上图第九个常量表示long的常量要占两个索引,下一个变量直接就是从索引11开始。所以上述红色字体分割线后面的常量应该都加1,即第十个常量,应该是第十一个。以此类推。。。最终是29个常量。权威解释,如果英文好的同学可以参考官方文档说明:

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6.1

或者这个链接:https://www.zhihu.com/question/22930911,此处不再深入,常量池到此为止。

访问标志
回顾下class文件的结构,魔数-版本号-常量池之后就是访问标志,紧跟在常量池之后的是访问标志,占两个字节,即0021,访问标志代表类或接口的访问信息,比如该class文件是类还是接口,是否被定义成public,是否是abstract,如果是类,是否被声明成final等等。各种访问标志参考下图:
在这里插入图片描述
我们发现这个表里无法查到0021这个值,原因是0021 = 0001+0020,也就是表示当前的class的访问标志是ACC_PUBLIC|ACC_SUPER。ACC_PUBLIC和代码里的public关键字相对应。ACC_SUPER表示当用invokespecial指令来调用父类的方法时需要特殊处理。

类索引

紧接着的2个字节0002表示当前类的索引。即:

0002:表示指向常量池中第2个常量,我们在上面分析常量池的时候已经分析了,第2个常量,又指向第25个常量,该常量类型是CONSTANT_Utf8_info,经过ASCII码解析后字符串为“com/example/helloroyole/ByteCojideTestInRoyole”,表示当前的类名。

父类索引

紧接着的2个字节0003表示父类的索引。即:

0003:表示指向常量池中第3个常量,上面也已经分析了,第3个常量,又指向第26个常量,该常量类型也是CONSTANT_Utf8_info,经过ASCII码解析后字符串为“java/lang/Object”,表示当前父类的类目。我们知道java中所有的类都基础自Object类。

接口索引计数器

紧接着的2个字节0002表示接口索引计数器,表示这个类实现了2个接口,每个接口占2个字节,则接下来的4个字节0004 0005代表了2个接口。即:

0004:表示指向常量池中第4个常量,上面已经分析,第4个常量,又指向了第27个常量,该常量类型也是CONSTANT_Utf8_info,经过ASCII码解析后字符串为“java/io/Serializable”,表示当前实现的接口为Serializable接口。

0005:表示指向常量池中第5个常量,上面已经分析,第5个常量,又指向了第28个常量,该常量类型也是CONSTANT_Utf8_info,经过ASCII码解析后字符串为“java/lang/Cloneable”,表示当前实现的接口为Cloneable接口。

综上所述,可以得出如下结论:当前类ByteCojideTestInRoyole继承自Object类,并实现了Serializable和Cloneable这两个接口。

字段表
在这里插入图片描述
紧跟在后面就是字段表了,字段表的主要功能是用来描述类或者接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量。因为一个类中的变量个数是不固定的,因此在字段表集合之前还是用一个计数器来表示变量的个数,即接下来的2个字节0003。

0003:表示类中声明了3个变量(class文件中叫字段),我这demo里面确实声明了3个变量,分别是long类型的serialVersionUID,String类型的KEY_SUMMARY,以及int型的count。每个成员变量对应个field_info(我不知道官方是不是这样命名的),其结构如下:

field_info{
    u2 access_flags  //字段的访问标志
    u2 name_index    //字段的名称索引(也就是变量名)
    u2 descriptor_index //字段的描述索引(也就是变量的类型)
    u2 attributes_count //属性计数器
    attribute_info
}

即接下来的8个字节00 1A 00 06 00 07 00 01分别表示第一个变量的访问标志,名称索引,描述索引,计数器。即:

001A:表示访问标志

0006:表示变量名,指向常量池中的第6个常量,该常量类型为CONSTANT_Utf8_info,通过ASCII码表转换后,表示的是字符串“serialVersionUID”。即表示变量名为serialVersionUID。

0007:表示变量的类型,指向常量池中的第7个常量,该常量类型为CONSTANT_Utf8_info,通过ASCII码表转换后,表示的是字符串“J”。即表示变量的类型为long。为什么J表示long,可以参考:https://www.jb51.net/article/116313.htm

0001:属性计数器,此处为1,表示有一个属性。

每个属性都是一个attribute_info结构:

attribute_info {
   u2 attribute_name_index;
   u4 attribute_length;
   u1 info[attribute_length];
}

紧接着的2个字节0008表示该属性名字的索引,即:

0008:十进制8,表示指向第8个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“ConstantValue”。表示这是一个常量

紧接着的4个字节00 00 00 02表示该属性的长度

紧接着的2个字节00 09表示该变量的值,即:

0009:十进制9,表示指向第9个常量,类型为CONSTANT_Long_info,表示长整型:-2793625091785878343L。

紧跟着的8个字节00 1A 00 0B 00 0C 00 01分别表示第二个变量的访问标志,名称索引,描述索引,计数器。即:

001A:表示访问标志

000B:表示变量名,指向常量池中的第11个常量,该常量类型为CONSTANT_Utf8_info,通过ASCII码表转换后,表示的是字符串“KEY_SUMMARY”。即表示变量名为KEY_SUMMARY。

000C:表示变量的类型,指向常量池中的第7个常量,该常量类型为CONSTANT_Utf8_info,通过ASCII码表转换后,表示的是字符串“Ljava/lang/String;”。即表示变量的类型为String。

0001:属性计数器,此处为1,表示有一个属性。

紧接着的2个字节0008表示该属性名字的索引,即:

0008:十进制8,表示指向第8个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“ConstantValue”。表示这是一个常量

紧接着的4个字节00 00 00 02表示该属性的长度

紧接着的2个字节00 09表示该变量的值,即:

000D:十进制9,表示指向第13个常量,第13个常量又指向第29个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“color_temperature_title_key”。

紧跟着的8个字节00 02 00 0E 00 0F 00 00分别表示第三个变量的访问标志,名称索引,描述索引,计数器。即:

0002:表示E访问标志

000E:表示变量名,指向常量池中的第14个常量,该常量类型为CONSTANT_Utf8_info,通过ASCII码表转换后,表示的是字符串“count”。即表示变量名为count。

000F:表示变量的类型,指向常量池中的第15个常量,该常量类型为CONSTANT_Utf8_info,通过ASCII码表转换后,表示的是字符串“I”。即表示变量的类型为int。

0000:属性计数器,此处为0,表示没有属性。

方法表
首先看第一个方法,如下截图:
在这里插入图片描述
字段表之后跟着的就是方法表,方法表常量也是以一个计数器开始的,前2个字节,即0002,表示我这个demo里面有两个方法,可我明明只定义了一个add方法,哪来的2个方法,这是因为默认构造方法也算一个。方法表的结构如下:

method_info{
    u2 access_flags;
    u2 name_index;
    u2 descriptor_index;
    u2 attributes_count;
    attribute_info attributes;
}

接下来的8个字节00 01 00 10 00 11 00 01分别表示访问标志,方法名,方法返回值,属性计数器。即:

0001:表示访问标志为public

0010:十进制16,表示指向第16个常量,其结构为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“”。表示构造方法。

0011:十进制17,表示指向第17个常量,其结构为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“()V”。构造方法是没有返回值的。

0001:十进制1,表示有1个属性。

其中attribute_info结构如下:

attribute_info {
  u2 attribute_name_index;
  u4 attribute_length;
  u1 info[attribute_length];
 }

接下来的2个字节0012,十进制18,表示常量池中第18个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“Code”。即:

0012:表示该属性是Code。

接下来的4个字节0000001D,表示属性所包含的字节数为29。

code attribute的结构如下:

Code_attribute {
          u2 attribute_name_index;
          u4 attribute_length;
          u2 max_stack;
          u2 max_locals;
          u4 code_length;
          u1 code[code_length];
          u2 exception_table_length;
          {
               u2 start_pc;
               u2 end_pc;
               u2 handler_pc;
               u2 catch_type;
          } exception_table[exception_table_length];
          u2 attributes_count;
          attribute_info attributes[attributes_count];
     }

上面说到code属性包含29个字节,这里不包括attribute_name_index,attribute_length。

max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度,即接下来的2个字节0001。

max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量,这里是0001。

接下来的4个字节表示00 00 00 05表示code_length长度为5,紧接着的5个字节2a b7 00 01 b1为对应的字节码指令的指令码,可参照如下指令码对应的助记符:

2a aload_0
b7 invokespecial
00 nop
01 aconst_null
b1 return

这就是该方法被调用时,虚拟机所执行的字节码。

接下来时exception_table,这里存放的是异常处理信息,不过我这里的demo代码没有做异常处理,所以接下来的2字节0000表示exception_table_length长度为0;接下来的2个字节是该方法的附加属性0001,表示有1个属性,接下来的2个字节0013表示该属性名字的索引,即:

0013:十进制19,指向常量池中第19个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“LineNumberTable”。表示属性名为LineNumberTable。

这个属性用来表示code数组中的字节码和java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数。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];
  }

接下来的4个字节00 00 00 06表示该属性的长度为6,第一方法即构造函数到这里就差不多结束了。

接下来,再简单看一下第二个方法,如下截图:
在这里插入图片描述
该方法就稍微快一点过一遍:首先4个字节0001 0014 0015 0001分别表示访问标志,方法名,返回值,属性计数器。即:

0001:表示public

0014:表示指向第20个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“add”。即表示方法名

0015:表示指向第21个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“(II)I”。即表示返回值为int型,并且方法带两个int类型的参数。

0001:表示有一个属性。

接下来的2个字节0012,十进制18,表示常量池中第18个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“Code”。即:(这里跟方法1构造方法一样)

0012:表示该属性是Code。

接下来的4个字节0000001C,表示属性所包含的字节数为28。

max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度,即接下来的2个字节0002。(对比方法1构造函数次数字节码是0001)

max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量,这里是0003。(对比方法1构造函数次数字节码是0001)

接下来的4个字节表示00 00 00 04表示code_length长度为4,紧接着的5个字节1B 1C 60 AC为对应的字节码指令的指令码,可参照如下指令码对应的助记符:

(此处的字节码 1B 1C 60 AC对应的助记符我没有找到,我的理解是官方应该会有一个类似的表列出所有的对应关系,如有错误请指出,谢谢)

此方法一样没有做异常处理,所以接下来2个字节一样是00 00,接下来的2个字节00 01,表示附加属性有1个。接下来的2个字节0013表示该属性名字的索引(同方法1构造方法),即:

0013:十进制19,指向常量池中第19个常量,类型为CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“LineNumberTable”。表示属性名为LineNumberTable。

属性表

最后就是属性表了,这里的属性表示整个class的附加属性。同样的,紧接着上面的方法表之后的2个字节0001表示有1个属性,属性表并没有一个固定的结构,各种不同的属性只要满足以下结构即可:

attribute_info{
 u2 attribute_name_index;
 u4 attribute_lenght;
 u2 sourcefile_index;
}

接下来的2个字节表示0016表示属性名字的索引,即:

0016:十进制22,表示常量池的第22个常量,表类型CONSTANT_Utf8_info,通过ASCII码转换后,表示的是字符串“SourceFile”。

接下来的4个字节00 00 00 02表示长度为2,最后2个字节0017,指向常量池里的第23个常量,为ByteCodeTestInRoyole.java,这个属性表明当前的class文件是从ByteCodeTestInRoyole.java文件编译而来。

【突变检验合集】含Pettitt突变检验等 突变检验方法合集 阅读详情

相关推荐

歪枣网股票数据下载接口汇总一

歪枣网上下载接口动态信息如下: 接口名称:A股列表,接口描述:沪深京A股基本信息,接口地址:/doc/getStockHSABaseInfo?code=all&fields=code,name,stype,hsgt,bk,roe,zgb,ltgb&export=0&token= 请求参数描述: 参数名:code,参数描述:股票代码,支持批量查询,用逗号分隔,每次最多30个;若为all,则表示全部,即可获取任意一天内的所有数据。 请求参数描述: 参数名:fields,参数描述:数据字段

waizaowang的博客 2731

Java二进制字节码解读

解析后字节码 运行命令:javap -v 类名或者类全路径 Classfile /class文件路径 Last modified 2022-5-12; size 556 bytes MD5 checksum 87970f58aeaa06be9b4af0063cde7e87 Compiled from "JvmClassByteCode.java" // 类基本信息 public class com.tigerkin.jvm.JvmClassByteCode minor version

weixin_42270645的博客 2480

Vivado实战:如何用BUFGMUX_CTRL实现FPGA双时钟热切换(附时序约束模板)

本文详细介绍了如何使用Xilinx FPGA中的BUFGMUX_CTRL原语实现双时钟热切换的工业级解决方案。通过分析BUFGMUX_CTRL的核心机制、PCB布局约束和电源噪声抑制方法,提供Vivado实现全流程和时序约束模板,帮助工程师在工业自动化、车载电子等场景中提升系统可靠性。

weixin_29186249的博客 577

初探Java字节码格式,编程解析字节码文件

本文较为详细的探讨了Java字节码文件(.class文件)的格式,并以HelloWorld.java的字节码文件为例,带读者逐个字节对文件进行解析,最后使用Java编程实现了对.class文件的解析过程 本文比较直观的对读者阅读字节码文件,对于初学者是一个字节码格式学习较好的入门教程,对于OG来说,这也是比较好的复习资料

qq_44525150的博客 3428

深入解析SVPWM七段式与五段式调制的电压波形差异及优化策略

本文深入解析了SVPWM七段式与五段式调制在相电压和线电压波形上的核心差异。七段式波形对称、谐波含量低,适合追求静音与性能的场景;五段式开关次数少、损耗低,适用于对效率与散热要求高的应用。文章提供了基于谐波与损耗权衡的优化策略与选型指南。

weixin_29216957的博客 440

MAX30102心率血样模块在STM32F103芯片的程序说明

我希望你已经安装好了Keil MDK5这个软件,也安装了M3内核,可以编译一些STM32F103的一些项目下载程序后步骤: 1-按照下图找到IIC.uvprojx并打开(途中唯一绿色那个图标) 2-编译一次 3-确保0错误,0警告,下图所示 4-点击目标选项,然后找到STM32F103C8,再点debug–CMSIS****(我实际用的JTAG,用不了,选择这个CMSIS****即可)下图所示 5-下图所示,看看你的串口找到了没,找到了就点左下角的OK 6-按图上传到开发板 7-MAX30102接线方法

weixin_51357422的博客 5801

彻底让你搞懂什么是Java字节码

因为JVM针对各种操作系统和平台都进行了定制,无论在什么平台,都可以通过javac命令将一个.java文件编译成固定格式的字节码(.class文件)供JVM使用。之所以被称为字节码,是因为 .class文件是由十六进制值组成的,JVM以两个十六进制值为一组,就是以字节为单位进行读取 格式如下:JVM对字节码的规范是有要求的,要求每一个字节码文件都要有十部分固定的顺序组成,如下图:1. 魔数所有的.class文件的前4个字节都是魔数,魔数以一个固定值:0xCAFEBABE,放在文件的开头,JVM就可以根据这个

Moonxiyue的博客 4412

NVIDIA Jetson Orin Nano Super刷机避坑指南:从虚拟机配置到NVMe烧录实战

本文详细介绍了NVIDIA Jetson Orin Nano Super的完整刷机流程,涵盖虚拟机配置、SDK Manager安装、硬件连接、固件下载、rpcbind服务冲突解决以及NVMe烧录等关键步骤。重点解析了Super模式下的NVMe烧录命令参数和操作时机,帮助开发者高效完成刷机并避免常见错误。

y2z3a4b5c的博客 752

JVM之Java字节码(.class)文件详解

我们在使用IDEA工具编写java代码时,当执行程序时,IDEA会使用JDK帮我们执行编译命令,在target目录下生成对应的.class文件,为了加深对.class文件的理解。写了这篇文章,加深对字节码文件的理解。编译前: 执行程序后:这一步操作,就是将.java文件编译成JVM可执行的.class文件,也叫做字节码文件。 在 Java 中,JVM 可以执行的代码就叫做字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过

qq_37080455的博客 4777

一文搞懂鲸鱼优化算法Whale Optimization Algorithm(WOA)!!!(核心原理+数学模型+代码实战+实际应用)

本文详细介绍了鲸鱼优化算法(WOA)的原理与实现。该算法模拟座头鲸气泡网捕食行为,通过包围猎物、螺旋攻击和随机搜索三种策略平衡全局探索与局部开发。文章首先从生物学角度解释鲸鱼捕食行为,然后给出算法的数学模型和完整流程,包括关键公式和决策逻辑。最后通过Python代码实例演示如何求解Sphere函数最小值,展示算法的实际应用。WOA具有结构简单、参数少等优点,在工程优化、机器学习等领域有广泛应用前景。

qq_42212808的博客 1011

C#与倍福Plc通信——使用仿真软件模拟倍福PLC运行

安装这个软件之前先安装VisualStudio,本文安装这个软件时已经安装了VisualStudio2019。1、下载倍福PLC编程软件TwinCAT。

qq_34059233的博客 3664

java判断文件类型

package com.base.utils; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Date; import java.util.HashMap; import java.util.Iterator; im...

weixin_39892176的博客 1863

SAP Event Mesh 简介

Get Started with Messaging for Decoupled Communication Step 1: What is the SAP Event Mesh service? SAP Event Mesh 是一种完全托管的服务,用于连接应用程序、服务和系统,以便它们可以通过消息和事件相互交互。 它通过引入中央消息代理取代了传统的点对点通信。 虽然点对点通信适用于在有限数量的发送方和接收方之间共享数据,但可扩展性很快就会成为一个问题。 SAP Event Mesh 解决了这个问题,并确保

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1243

什么是Java字节码?

什么是Java字节码? 它是程序的一种低级表示,可以运行于Java虚拟机上。将程序抽象成字节码可以保证Java程序在各种设备上的运行 Java号称是一门“一次编译到处运行”的语言,从我们写的java文件到通过编译器编译成java字节码文件(.class文件),这个过程是java编译过程;而我们的java虚拟机执行的就是字节码文件。不论该字节码文件来自何方,由哪种编译器编译,甚至是手写字节码文件...

nO0b 1万+

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

java字节码

Java之所以可以“一次编译,到处运行”,一是因为JVM针对各种操作系统、平台都进行了定制,二是因为无论在什么平台,都可以编译生成固定格式的字节码(.class文件)供JVM使用。因此,也可以看出字节码对于Java生态的重要性。之所以被称之为字节码,是因为字节码文件由十六进制值组成,而JVM以两个十六进制值为一组,即以字节为单位进行读取。

zhangjunli的博客 2555

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

读懂Java字节码

目录 一、简单demo分析 字节码文件信息 常量池 方法表集合 二、分析try-catch-finally的demo 计算机只认识0和1。这意味着任何语言编写的程序最终都需要经过编译器编译成机器码才能被计算机执行。所以,我们所编写的程序在不同的平台上运行前都要经过重新编译才能被执行。 而Java刚诞生的时候曾经提过一个非常著名的宣传口号: "一次编写,到处运行"。为了实现该目的,Sun...

l1394049664的博客 6848

java字节码_掌握Java字节码

java字节码 嘿! Happy Advent:D我是ZeroTurnaround的技术布道者Simon Maple( @sjmaple) 。 您知道, JRebel伙计们! 由于编写了类似JRebel的产品,该产品与字节码进行交互的结果比您想像中的要多,因此我们想分享很多关于它的知识。 让我们从头开始……Java是一种旨在在虚拟机上运行的语言,因此只需要编译一次就可以在任何地方运行(是的...

最佳 Java 编程 184

java字节码_Java字节码:

有关字节码以及此处显示的字节码的信息基于Java 2 SDK Standard Edition v1.2.1 javac编译器。 其他编译器生成的字节码可能略有不同。 为什么要理解字节码? 字节码是Java程序的中间表示,就像汇编程序是C或C ++程序的中间表示一样。 最有名的C和C ++程序员知道他们要为其编译的处理器的汇编程序指令集。 在调试以及进行性能和内存使用调整时,此知识至关...

cusi77914的博客 742

Java字节码,字节码指令

各种字节码指令的相关介绍,方法重载和重写在字节码层面的实现原理和机制,字节码的查看和分析方法简单介绍

m0_66668867的博客 1748

Java字节码和Dalvik字节码

Java字节码和Dalvik字节码的区别,生成JAVA字节码和Dalvik字节码

SHENGLI_509的博客 1474

Java字节码文件

目录 一、简单demo分析 字节码文件信息 常量池 方法表集合 二、分析try-catch-finally的demo 计算机只认识0和1。这意味着任何语言编写的程序最终都需要经过编译器编译成机器码才能被计算机执行。所以,我们所编写的程序在不同的平台上运行前都要经过重新编译才能被执行。 而Java刚诞生的时候曾经提过一个非常著名的宣传口号: "一次编写,到处运行"。为了实现该目的,Su...

跳跃的list 2188

Java 字节码

本文对字节码概念及 Java 字节码文件结构进行详细介绍,并围绕示例代码进行详细解析。

大大怪将军的博客 1387

【潜意识Java】深度探索Java字节码

家人们,今天来深挖 Java 中极为底层却又无比关键的部分 —— 字节码。平常我们编写 Java 代码,可能很少直接接触字节码,但它却是 Java 程序运行的基石。理解字节码,能让你透彻明白 Java 程序是如何在虚拟机里运行的,无论是排查问题,还是优化代码,都离不开它。我也是在不断钻研中才把它摸透,现在就把这些宝贵经验分享给大伙。

潜意识Java的博客 853

我爱你的java字节码_java字节码

一、“一处编译到处运行”原因就是字节码,由于不同的平台编译出来的机器码0,1是不同的,java采用不直接编译成机器码(0,1)而是把他们编译成字节码。再由不同平台上的JVM翻译成对应平台的机器码(0,1)。如今,JVM也不再只支持Java,由此衍生出了许多基于JVM的编程语言,如Groovy, Scala, Koltin等等。而字节码命令所能提供的语义描述能力是要明显强于Java本身的,所以有其他...

weixin_30983563的博客 450

java中字节码_Java字节码解读

1. 字节码Java字节码是Java虚拟机执行的一种指令格式.class文件是编译器编译之后供虚拟机解释执行的二进制字节码文件.下面举个例子,写一段Java打码,并编译.package com.xfhy.test; public class Hello { private int num = 1; public int add() { num = num + 2...

weixin_39699313的博客 439

java load字节码_从Java源码到Java字节码

Java Class文件含有丰富的符号信息。而且javac默认的编译参数会让编译器生成行号表,这些都有助于了解对应关系。关于Java语法结构如何对应到Java字节码,在JVM规范里有相当好的例子:Chapter 3. Compiling for the Java Virtual Machine好好读完这章基本上就能手码字节码了。记住一个要点就好:“运算”全部都在“操作数栈”(operand sta...

weixin_35644453的博客 362

Java字节码

在代码世界里,你遇到过多少次‘似懂非懂’的困境?我是曾续缘,一个喜欢把技术嚼碎了讲给你的开发者。点击关注,从此每个技术卡点都有解!

曾续缘的博客 1138
上一篇: 关于AIDL接口定义中oneway的修饰符源码解析.
下一篇: 记录修改系统默认导航模式
Only_Studio
博客等级 码龄11年 115粉丝 18原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值