JVM学习笔记(1)——java class
例子主要是《深入jvm》中的例子,class文件是其中的act.class,java源文件是:
class Act {
public static void doMathForever() {
int i = 0;
for (;;) {
i += 1;
i *= 2;
}
}
}
class文件hex形式:
CA FE BA BE 00 03 00 2D 00 11 07 00 07 07 00 10
0A 00 02 00 04 0C 00 06 00 05 01 00 03 28 29 56
01 00 06 3C 69 6E 69 74 3E 01 00 03 41 63 74 01
00 08 41 63 74 2E 6A 61 76 61 01 00 04 43 6F 64
65 01 00 0D 43 6F 6E 73 74 61 6E 74 56 61 6C 75
65 01 00 0A 45 78 63 65 70 74 69 6F 6E 73 01 00
0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65
01 00 0E 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65
73 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00
0D 64 6F 4D 61 74 68 46 6F 72 65 76 65 72 01 00
10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63
74 00 20 00 01 00 02 00 00 00 00 00 02 00 09 00
0F 00 05 00 01 00 09 00 00 00 30 00 02 00 01 00
00 00 0C 03 3B 84 00 01 1A 05 68 3B A7 FF F9 00
00 00 01 00 0C 00 00 00 12 00 04 00 00 00 05 00
02 00 07 00 05 00 08 00 09 00 06 00 00 00 06 00
05 00 01 00 09 00 00 00 1D 00 01 00 01 00 00 00
05 2A B7 00 03 B1 00 00 00 01 00 0C 00 00 00 06
00 01 00 00 00 02 00 01 00 0E 00 00 00 02 00 08
- java的class文件是8位二进制流,数据项按顺序存放,无间隔,占多字节的数据项以高位在前的顺序分几个字节存放;
- java class基本类型:u1,u2,u4,u8,分别对应:1,2,4,8字节的无符号类型;
- java class file表格展示:(太大了,来个url自己看吧,wikipedia)
- 表项详解:1)magic(魔数):说白了就是cafebabe,本来java就是咖啡嘛,这4个字节用来区分是否是java的class文件,有则是;2)minor_version&major_version:两个字节的minor和两个字节的major,以上为例就是minor:3,major:2D(JDK 1.1);3)再之后就是常量池了,2个字节表示constant_pool_count,本例是17,表示class文件中常量池中的项数(比实际的大1),接着就是常量池,连续的constant_pool_count-1个字节存储常量池各个入口,常量池入口项解释见本文第5条笔记;4)在之后的是access_flags:2个字节表示访问类型,是类还是接口,是public还是private,abstract或者final等等,标志位具体定义见本文第6条笔记,本例是00 20,表示老版本ACC_SUPER;5)之后是this_class,这是2字节的一个对常量池的索引,本例是00 01,即本例的自身类叫做Act;6)之后就是super_class,也是一个2字节常量池索引,指向父类的名字, 本例是00 02,即java.lang.Object;7)interface_count和interfaces:interface_count指出本文件有多少直接实现或者由接口扩展的父接口的数量,本例没有,故为00 00,之后是interfaces的具体内容,实际的项数就是之前的interface_count数,因为interface_count=0,所以本例的interfaces就没有值;8)接下来是fields_count和fields:对本类所声明的字段的描述,首先是个2字节的count,本例是00 00,所以后续也没有fields的项;9)再之后就是methods_count和methods了,count是一个2字节数据,本例的method_count是00 02,这个count只表示在类或接口中显式定义的方法,继承的方法不计数,count后是method_info的表,包含方法的一些信息如方法名、描述符等,本例中00 09表明方法是public(01)&static(08),00 0F是方法名的常量池入口,即常量池的第15项doMathForever,再下来00 05是方法描述符常量池入口,即常量池第5项:()V,然后00 01是属性表的count数,表示1项属性,接下来是00 09表示属性表的常量池入口即常量池第9项Code,接下来的4个字节00 00 00 30表示code属性长度:48字节,接着00 02是操作数最大数,然后00 01是局部变量存储长度,这里方法里只有一个变量i,所以是1,然后00 00 00 0c是code字节码长度12,然后的12个字节就是字节码code了,再后的00 00是异常数,之后异常栈数是0,跳过,就是00 01的属性数,然后00 0C指向常量池的第12项即LineNumberTable,这是一个code属性,之后的00 00 00 12是属性长度,再后的00 04是line_number_info表的项数,接下来的4项(每项4字节)表示line_number_info,00 00 表示代码偏移量,00 05表示代码行号,后面的类似;10)最后是attributes_count和attributes,表明了类的属性,属性比较特殊,jvm定义了两种属性:SourceCode和InnerClass。
- 常量池各个标志解读(来源wikipedia)
,这里详解一下本例中的常量池,常量池是
cp_info { tag; info[]; }
类似这样的结构,先有一个无符号byte作为tag标志,对应表格中的数据,额外的info字节数组存储对应的数据index.
我以表格的形式列出常量池的所有数据,应该算一目了然了吧: -
常量index 标志 标志内容 字节 具体数据 实际含义 1 07 00 07 2 class reference 常量池第7项是该class的内容 2 07 00 10 2 class reference 常量池第16项是该class的内容 3 0A 00 02 00 04 4 method ref 两个index,前两个字节表示池内的class索引位置,后两个字节是名字和类型描述 4 0C 00 06 00 05 4 name & type 就是第3项方法指向的名字和类型的index 5 01 00 03 2+x x个utf-8字符,此处x=3 实际值:()V,表示type(第三项方法的类型,具体含义参见描述符定义) 6 01 00 06 2+x x=6 实际值:<init>,表示name(第三项方法的名字) 7 01 00 03 2+x x=3 实际值:Act,第一项指向的具体字符内容 8 01 00 08 2+x x=8 实际值:Act.java, 9 01 00 04 2+x x=4 实际值:Code 10 01 00 0D 2+x x=0D=13 实际值:ConstantValue 11 01 00 0A 2+x x=0A=10 实际值:Exceptions 12 01 00 0F 2+x x=0F=15 实际值:LineNumberTable 13 01 00 0E 2+x x=0E=14 实际值:LocalVariable 14 01 00 0A 2+x x=0A=10 实际值:SourceFile 15 01 00 0D 2+x x=0D=13 实际值:doMathForever 16 01 00 10 2+x x=10=16 实际值:java/lang/Object - 访问标志,待完善,这里有详细的spec
- 描述符的完整定义:非终结符是正常体,终结符是粗体,*号表示之前符号出现0或多次
FieldDescriptor FieldType ComponentType FieldType FieldType BaseType,ObjectType,ArrayType BaseType B,C,D,F,I,J,S,Z ObjectType L<classname>; ArrayType [ComponentType MethodDescriptor (ParameterDescriptor*)ReturnDescriptor ParameterDescriptor FieldType ReturnDescriptor FieldType,V - 基本类型终结符:V代表void
终结符 类型 B byte C char D double F float I int J long S short Z boolean - 一些描述符的例子:
描述符 字段或方法声明 I int a; [[J long[][] b; [Ljava/lang/Object; java.lang.Object[] c; Ljava/util/HashMap; java.util.HashMap map; [[[Z boolean[][][] ok; ()I int m1(); ()Ljava/lang/String; String m2(); ([Ljava/lang/String;)V void main(String[] args); ()V void m3(); (JI)V void m4(long a,int b); (Z[Ljava/lang/String;II)Z boolean m5(boolean a,String[] b,int c, int d); ([BII)I int m6(byte[] a,int b,int c); - 声明字段时的字段表field_info:
类型 名称 数量 含义 u2 access_flags 1 访问标志 u2 name_index 1 字段简单名称的常量池utf8_info入口索引 u2 descriptor_index 1 字段描述符的常量池utf8_info入口索引 u2 attributes_count 1 attribute_info表的项数 attribute_info attributes attributes_count 字段属性:ConstantValue, Deprecated, Synthetic(JVM规范) - field_info中的access_flags标志含义:
标志名 值 含义 使用范围 ACC_PUBLIC 0x0001 public 类和接口 ACC_PRIVATE 0x0002 private 类 ACC_PROTECTED 0x0004 protected 类 ACC_STATIC 0x0008 static 类和接口 ACC_FINAL 0x0010 final 类和接口 ACC_VOLATILE 0x0040 volatile 类 ACC_TRANSIENT 0x0080 transient 类 - 声明方法时的方法表method_info:
类型 名称 数量 含义 u2 access_flags 1 访问修饰符 u2 name_index 1 方法简单名称的常量池入口 u2 descriptor_index 1 方法描述符的常量池入口 u2 attributes_count 1 属性表的项数 attribute_info attributes attributes_count 方法属性:Code,Deprecated, Exceptions和Synthetic(JVM规范) - method_info表中的访问标志对应含义:值得说明的是接口的方法一定是public和abstract的,接口初始化方法可以用strictFP
标志名 值 含义 使用范围 ACC_PUBLIC 0x0001 public 类和接口 ACC_PRIVATE 0x0002 private 类 ACC_PROTECTED 0x0004 protected 类 ACC_STATIC 0x0008 static 类 ACC_FINAL 0x0010 final 类 ACC_SYNCHRONIZED 0x0020 synchronized 类 ACC_NATIVE 0x0100 native 类 ACC_ABSTRACT 0x0400 abstract 类和接口 ACC_STRICT 0x0800 strictFP 类和接口的<clinit>方法 - 类和接口的初始化方法(<clinit>)只有JVM可以直接调用,永远不会被java字节码直接调用。
- JVM规范定义的所有属性:
属性名 使用者 描述 Code method_info 方法的字节码和其他数据 ConstantValue field_info final变量的值 Deprecated field_info,method_info 字段或方法被禁用的指示符 Exceptions method_info 方法可能抛出的可被检测的异常 InnerClasses ClassFile 内部、外部类的列表 LineNumberTable Code_attribute 方法的行号与字节码的映射 LocalVariableTable Code_attribute 方法的局部变量的描述 SourceFile ClassFile 源文件名 Synthetic field_info,method_info 编译器产生的字段或者方法的指示符 - code属性的表code_attribute:
类型 名称 数量 含义 u2 attribute_name_index 1 包含“Code”的常量池入口 u4 attribute_length 1 去除起始6个字节后的code属性长度 u2 max_stack 1 方法执行任意时刻,该方法操作数栈的最大长度(以字为单位) u2 max_locals 1 方法局部变量需要的存储空间长度(以字为单位) u4 code_length 1 该方法字节码流的长度 u1 code code_length u2 exception_table_length 1 异常表项数 exception_info exception_table exception_table_length 异常表 u2 attributes_count 1 属性数 attribute_info attributes attributes_count code属性:LineNumberTable和LocalVariableTable(JVM规范) - 异常表excption_info:
类型 名称 数量 含义 u2 start_pc 1 代码数组起始处到异常处理器起始处的代码偏移量 u2 end_pc 1 代码数组起始处到异常处理器结束后的一个字节的偏移量 u2 handler_pc 1 代码数组起始处跳转到异常处理器的第一条指令的偏移量 u2 catch_type 1 异常处理器捕获的异常类型的Class_info常量池入口,如果为0则表示处理finally子句,即处理所有异常 - constantValue属性:各种基础类型加字符串类型的常量池入口查找,结构很简单,这里就不列出了。
- LineNumberTable属性建立了方法字节码流便宜量和源代码行号之间的映射关系:
类型 名称 数量 含义 u2 attribute_name_index 1 包含“LineNumberTable”的常量池入口 u4 attribute_length 1 去除起始6字节后的属性长度 u2 line_number_table_length 1 line_number_info表长度 line_number_info line_number_table line_number_
table_length属性表 - line_number_info表很简单,就两个项:u2的start_pc给出新行开始时代码数组的偏移量,u2的line_number给出了从start_pc开始的行号。
这次就到这里。to be continued...