字节码文件是什么
我们在命令后使用 java
命令,就能将java源文件(.java)编译成对应的字节码文件(.class)。字节码文件是一种八位字节的二进制流文件,各个数据项按照一定顺序从前到后紧密排列。因此,这样的安排会使得字节码文件非常紧凑,可以被jvm快速的加载到内存中,并且占用较少的内存空间。
java源文件在被编译器编译之后,每个类(或者接口)都单独占据一个字节码文件。类中所有信息都在字节码文件中有所描述,由于字节码文件非常灵活,它对类的描述能力甚至强于java源文件。
字节码文件中的信息是一项一项排列的,每一项都有各自固定的长度:有的占一个字节,有的占两个字节,有的占四个或八个字节。数据项不同的长度分别用u1, u2, u4, u8表示,对应数据项在字节码文件中占据1,2,4,8个字节。
字节码文件的结构
一个字节码文件由十部分构成:MagicNumber,Version,Constant_pool,Access_flag,This_class,Super_class,Interfaces,Fields,Methods和Attributes。可以用下面的表来展示他们的排列顺序和每一部分数据项的大小:
下面分别对每一项进行展开:
(1)magic:存在于字节码文件的开头四个字节,存放着字节码文件的魔数,他是一个固定值 0xCAFEBABE(英文就是java那个咖啡的图标)。如果是十六进制的CAFEBABE,那么这个文件符合字节码文件的标准;如果不是,则不能被jvm所识别。
(2)minor_version和major_version:紧跟着魔数的就是次版本号和主版本号。例如版本1.8.0,那么主版本号就是1.8,而此版本号对应是0。版本号与某一个整数之间有映射关系,因此只要将major_version的十六进制数转化为十进制,再查表找到映射关系,就可以知道java编译器的版本号。
(3)constant_pool:版本号后面为常量池,常量池是字节码文件中一个非常重要的部分。常量池存放了文字字符串,常量值,当前类的类名,字段名,方法名,各个字段和方法的描述符,对当前类的字段和方法的引用信息,当前类中对其他类的引用信息等等。几乎包含了类中所有信息的描述。
首先常量池开头两个字节描述了常量池中数据项的数目。常量池中的数据也是一项一项紧密排列的,各个数据项通过索引来访问。每一个数据项都有自己的数据类型,由开头的一个字节(标志值tag)决定,映射关系如下表所示:
每一种不同的数据类型,都对应不同的结构,例如CONSTANT_Methodref:
CONSTANT_Methodref_info {
u1 tag; //u1表示占一个字节
u2 class_index; //u2表示占两个字节
u2 name_and_type_index; //u2表示占两个字节
}
再比如CONSTANT_Utf8结构:
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
总而言之,每一个不同的数据类型都对应不同的组织结构。
(4)access_flag:保存了当前类的访问权限,包括了public,private,protected等等。
(5)this_class:保存了当前类的全局限定名在常量池中的索引。
(6)super_class:保存了当前类的父类全局限定名在常量池中的索引。
(7)interfaces:保存了当前类实现的接口列表,包括两个部分:interfaces_count和interfaces[interfaces_count]。前者表示当前类实现的接口数量,两个字节表示(因此理论上一个类最多实现65535个接口);后者表示包含interfaces_count个接口的全局限定名索引的数组。
(8)fields:保存了当前类中的成员列表,也是分为两部分:fields_count和fields[fields_count]。前者表示一个类中的成员个数,后者表示字段详细信息的列表,如下所示:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
详细信息包括了访问权限,成员名在常量池中的索引,类型描述符在常量池的索引,一些其他的属性(比如volatile之类的,但count如果是0,这个字段也就不存在了)。
(9)methods:保存了当前类的方法列表,包含两部分的内容:methods_count和methods[methods_count]。前者是该类或者接口显示定义的方法的数量,后者包含方法信息的一个详细列表,每一个方法信息如下:
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
这里的attribute比较复杂,涉及到操作数栈、局部变量表等等信息,不展开了。感兴趣的话可以看下结尾引用的那篇文章。
(10)attributes:包含了当前类的attributes列表,包含attributes_count和 attributes[attributes_count]。属性不仅仅可以出现在这个字段,如果出现在这个地方,则是对整个字节码文件所对应的类或者接口的描述;如果出现在fields或者methods中,则是对该字段或者该方法的额外信息的描述。
每一个attribute的结构如下:
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
最常见的一个属性就是attribute_name_index指向常量池中的SourceFile,而sourcefile_index指向常量池中的xxx.java(也就是这个字节码文件的源文件)。
总结
字节码文件中的十部分结构简单的分析如上所述,包含了让jvm识别的magic;java的版本号;常量池(各种成员名,方法名,类名,源文件名,方法描述符等等),这些常量信息被后面的field,method等等所引用;访问权限;类名和父类全限定名;实现的接口信息;类中成员和方法;类的额外属性。
如果想了解更具体的,可以参考一下深入理解JVM之Java字节码(.class)文件详解
最后放一个java代码,和编译后字节码文件的一部分信息(用javap反编译相当于对字节码文件进行解密):
源代码:
public class Hello{
private int test;
public int test(){
return test;
}
}
字节码文件:
javap反编译得到每一部分的值:
参考
- https://blog.csdn.net/weelyy/article/details/78969412
- 《深入理解java虚拟机》