例子主要是《深入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...