深入class文件的结构
1.jvm指令简介(各种指令的详细信息见P122-P132),自己的理解:class文件的二进制字节码(用十六进制的形式查看的话2位代表一个字节?),该二进制文件是按照 “头部信息-常量池-类信息-Fields和Methods定义-类属性描述”的顺序的字节码,由以下2-6将顺序描述各个部分如何阅读。(也应该按照这样的顺序去读取class文件)
2.文件的头部信息:
8个字节:(书上说的是6个字节,最小版本号,最大版本号一起占两个字节,与网上资料不符,有问题?)
前四个字节:魔数(magic),四个字节的固定值,若头四个字节不是该值,jvm将认为这不是一个class文件,四个字节的16进制值分别为: ca fe ba be
第六、七个字节:最小版本号(minor version),又叫副版本号
地八、九个自己:最大版本号(major version),又叫主版本号
后面四个字节构成版本号的范围,从java到java8的范围是 45.3~53.0,jvm加载的时候会检查该版本是否符合。(转载的资料好像同样不符:“分别为Class文件的副版本和主版本。它们共同构成了Class文件的格式版本号。不同版本的虚拟机实现支持的Class文件版本号也相应不同,高版本号的虚拟机可以支持低版本的Class文件,反之则不成立。”)(在java7下编译的的最小版本号是0,最大版本号是51,估计是jvm会查看这个主版本号是否在当前jvm的接受范围内(高版本的jvm兼容低版本的),还是检查 主版本号.副版本号 即 51.0是否在范围内?)
3.常量池:(转载的资料:“常量池,constant_pool是一种表结构,它包含Class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池不同于其他,索引从1开始到constant_pool_count -1。”)
前两个字节表示常量池中有多少个常量: 如书中例子 00 1d则是指有29个,但是后前常量池的内容会看到只有28个,因为还有一个0是保留常量
后面的内容将是28个常量,每个常量的字节码的值都有部分是常量字节描述(后面是值还是什么的不同常量不同)(常量字节描述将包含三个字节 第一个字节是常量类型,后两个字节将是常量的索引地址(但是像UTF8类型常量后面四个字节则是该常量值的长度,转载的信息关于后两个字节的说法也不同:“以1个字节的tag开头,后面info[]项的内容tag由的类型所决定。”))
P139页有各种常量的 (类型)数值表示(对应常量字节描述的第一个字节的值)、数据类型(如UTF8,Integer等,注意了阅读class文件的时候,该常量后面的的常量值占多少个字节要通过此处确定,如Integer为4个字节,UTF8是不定长的,其长度在常量描述符的后两个字节)
UTF8是长度不定的,用于表达字符串常量的值(String类型是指向一个UTF8类型的下标)(书上的格式是:2-byte unsigned integer bytes,是不是说该类型是两个字节的无符号整形字节数组?但是utf-8不是会以1-6个字节表示字符么?如汉字则有3个字节啊,常量中也可以含有汉字吧,到底怎么回事没理解,成UTF-16了么?)。(读取值时可以尝试吧其中的字节值转换成字符或unicode,可查看编码一章的utf的代码)
Fieldref、Methodref类型:这两个类型用于标书class中的属性项和方法,第一个字节为09(0a)表示是其常量类型是Fieldref(MethodRef),后面四个字节,前两个表示filed所在类名class的下标,后两个表示该常量的名字和类型NameAndType的索引
Class常量:表示的是该类的名称,第一个字节为07表示其类型,后面两个字节为其对应的UTF8类型的索引(类名的字符串)
Name And Type:对MethodRef和FieldRef作进一步描述,第一个参数表示属性或方法名称,第二个参数表示属性的类型或方法的返回值类型。第二个只有一个常量描述符,第一个字节是类型后面四个字节分别是name和descriptor的两个指向UTF8类型的索引
其他详细看P139
4.类信息
(1)常量列表后面的字节码就是关于这个类本身的信息描述了(如这个类的访问控制,名称,类型,是否有父类,是否实现了某接口等)
(2)前两个字节表示访问控制:(不单单是访问控制?)
由两个字节表示访问控制的描述,实际上只用了这两个字节中的5个bit,第一个表示是该类是否public,是则为1(奇数就public?),第5个bit表示是否final,1表示是,第6个bit表示是否含有invokeSpecial即是否有父类,第10个bit表示是否接口类,0表示不是接口类,1是接口类,第12个bit表示是否抽象类,1则是是
(3)第3,4个字节表示这个类的名称的UTF8常量的索引
(4)第5,6个字节表示这个类的父类名称的索引
(5)第7,8个字节表示该类有没有实现接口类
5.fields和Methods定义
(1)类信息后面就是每个field和method的具体定义了
第1,2个字节是field的个数,第3,4个字节是method的个数,接下来就是具体每个方法和属性的描述
<1>方法和属性与类一样有访问控制,由两个字节描述:
[1]第一个bit:是否public
[2]第二个bit:是否private
[3]第三个bit:是否protected
[4]第四个bit:是否static
接下来的4个bit分别定义是那种类型的方法或属性
[5]第5个bit:是否final
[6]第6个bit:是否synchronized
[7]第7个bit:有没有被定义成volatile
[8]第8个bit::有没有被定义成transient(该关键字定义进行序列化时不必传输)
[9]第9个bit:是否native方法(一个Native Method就是一个java调用非java代码的接口。)
[12]第12个bit:是否abstract方法
<2>接下来的2个字节将是方法/属性名name的索引,然后是两个字节的描述descriptor的索引(像是nameAndType类型,但是没有表示常量的类型的第一个字节)
<3>接下的两个字节将是属性或方法的附加属性(只知道如code即方法的代码也是附加属性,主要包括什么则不了解)的个数。然后是这个方法或属性的附加属性的具体定义
[1]附加属性“code”,即代码:
前两个字节是name的索引,如指向的UTF8值时code,即表示这个附加属性时当前方法的具体代码实现
接着是四个字节描述具体代码的长度(指定是编译过后方法的字节码长度),从这里看方法的字节码最大是2的32次方也就是4G但由于后面段落描述符表示的行号长度只有两个字节即2的16次方,所以实际最长是64k
接下来是两个字节定义的最大栈深度和本地常量最大值(jvm加载字节码到内存时会验证是否超过这两个值,超过的话则jvm拒绝加载)
接下来将表示方法对应的jvm指令,首先四个字节表示指令的长度(有多少个字节),再接下来是具体jvm命令及其操作数对应的字节
接下来两个字节表示定义抛出的异常数
接下来是代码的属性描述,首先是两个字节描述代码的属性个数,接下来是具体每个代码的属性的定义,如对LineNumberTable(段落描述?)的定义,前两个字节是name,后面四个字节表示该代码属性的具体定义占的字节数,如书中例子,该值为6,则后面的6个字节,前两个是指后面的字节中描述了多少个对应关系,该例中为1个。接下来是具体对应关系,前2个字节表示运行时的指针(对应的class文件的字节码的行号?),后两个字节表示对应源代码的行号。(由段落描述中对运行时指针对应源代码的行数只占两个字节可知,java源代码的行总数最多只有65535行,而字节码的总字节数也最多是65535个,超过则不能表示)
6.类属性描述
(1)接下来将是类的附加属性的描述,前两个字节是类的附加属性的个数
(2)接下来是各个附加属性的具体描述(sorcefile属性):前两个字节描述name,接着4个描述这个属性内容占多少个字节,这里值将是2,因为该附加属性的内容将只有两个字节的UTF8常量的索引,表示源文件名
7.java生成的class文件结构
(1)二进制的原文件难以阅读,可以使用javap命令来生成class文件的格式,这个格式的文件更加易于理解(应该是相当于把二进制翻译成字符串的一个文件)
(2)使用javap命令生成该文件: javap -verbose class文件名 > 输出到的文件名(如class.txt,不存在会自动创建)
(3)关于LineNumberTable:
<1>它的值时 一对对的 pc偏移量:源代码行号
<2>一个方法的开始的第一行的pc偏移量是0
<2>pc偏移量自己的理解,就是对应字节码文件中描述该方法的代码的字节码以0为下标开始的第几个字节,也就是源代码中的某一行对应第几个字节开始
(4)LocalVariableTable
Start:由该本地变量被赋值xload的对应的下一个字节指令,也就是从哪一个字节开始的指令作用
length:从start开始到期作用域结束有多少个字节的指令(如佛如循环中的变量,就是由变量被赋值后的下一条指令到for循环结束的指令的字节数)
slot:自己的理解,本地变量占用的编号,当作用域不重合时允许共用(即[start,start+length]不重合,如if,else中两个语句块中的本地变量)
name:变量名
Signature:表示该变量的类型L,I,C等分别表示long,int,char