Java类编译后Class文件概述(上)

一、Class文件

注意:每一个Class 文件都对应着唯一一个类或接口的定义信息,但是相对地,类或接口并不一定都得定义在文件里(譬如类或接口也可以通过类加载器直接生成)。

二、class文件的文件结构

class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型:无符号数和表。

class文件的结构描述

ClassFile {
		u4 magic; 					//4位无符号的数,表示魔数
		u2 minor_version;				//次版本号
		u2 major_version;				//主版本号
		u2 constant_pool_count;				//常量池计数器
		cp_info constant_pool[constant_pool_count-1];	//常连池
		u2 access_flags;				//访问标志
		u2 this_class;					//类索引
		u2 super_class;					//父类索引
		u2 interfaces_count;				//接口计数器
		u2 interfaces[interfaces_count];		//接口表
		u2 fields_count;				//字段计数器
		field_info fields[fields_count];		//字段表
		u2 methods_count;				//方法计数器
		method_info methods[methods_count];		//方法表
		u2 attributes_count;				//属性计数器
		attribute_info attributes[attributes_count];	//属性表
	}

准备的java文件

public class Test {
	private int age;

	public int getAge() {
		return age;
	}

	public static void main(String[] args) {
		System.out.println("Hello World");
	}
}

对应的Test.class文件

Java类编译后Class文件概述(上)_第1张图片

对应的文本文件

cafe babe 0000 0034 0023 0a00 0700 1409
0006 0015 0900 1600 1708 0018 0a00 1900
1a07 001b 0700 1c01 0003 6167 6501 0001
4901 0006 3c69 6e69 743e 0100 0328 2956
0100 0443 6f64 6501 000f 4c69 6e65 4e75
6d62 6572 5461 626c 6501 0006 6765 7441
6765 0100 0328 2949 0100 046d 6169 6e01
0016 285b 4c6a 6176 612f 6c61 6e67 2f53
7472 696e 673b 2956 0100 0a53 6f75 7263
6546 696c 6501 0009 5465 7374 2e6a 6176
610c 000a 000b 0c00 0800 0907 001d 0c00
1e00 1f01 000b 4865 6c6c 6f20 576f 726c
6407 0020 0c00 2100 2201 0004 5465 7374
0100 106a 6176 612f 6c61 6e67 2f4f 626a
6563 7401 0010 6a61 7661 2f6c 616e 672f
5379 7374 656d 0100 036f 7574 0100 154c
6a61 7661 2f69 6f2f 5072 696e 7453 7472
6561 6d3b 0100 136a 6176 612f 696f 2f50
7269 6e74 5374 7265 616d 0100 0770 7269
6e74 6c6e 0100 1528 4c6a 6176 612f 6c61
6e67 2f53 7472 696e 673b 2956 0021 0006
0007 0000 0001 0002 0008 0009 0000 0003
0001 000a 000b 0001 000c 0000 001d 0001
0001 0000 0005 2ab7 0001 b100 0000 0100
0d00 0000 0600 0100 0000 0100 0100 0e00
0f00 0100 0c00 0000 1d00 0100 0100 0000
052a b400 02ac 0000 0001 000d 0000 0006
0001 0000 0005 0009 0010 0011 0001 000c
0000 0025 0002 0001 0000 0009 b200 0312
04b6 0005 b100 0000 0100 0d00 0000 0a00
0200 0000 0800 0800 0900 0100 1200 0000
0200 13

使用 javap -verbose Test.class查看的结果

Java类编译后Class文件概述(上)_第2张图片

对应的文本

C:\Users\weixu_000\Desktop>javap -verbose Test.class
Classfile /C:/Users/weixu_000/Desktop/Test.class
  Last modified 2015-7-4; size 499 bytes
  MD5 checksum 580d9204b4544445687dea33327cdb0c
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#20         // java/lang/Object."":()V
   #2 = Fieldref           #6.#21         // Test.age:I
   #3 = Fieldref           #22.#23        // java/lang/System.out:Ljava/io/Print
Stream;
   #4 = String             #24            // Hello World
   #5 = Methodref          #25.#26        // java/io/PrintStream.println:(Ljava/
lang/String;)V
   #6 = Class              #27            // Test
   #7 = Class              #28            // java/lang/Object
   #8 = Utf8               age
   #9 = Utf8               I
  #10 = Utf8               
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               getAge
  #15 = Utf8               ()I
  #16 = Utf8               main
  #17 = Utf8               ([Ljava/lang/String;)V
  #18 = Utf8               SourceFile
  #19 = Utf8               Test.java
  #20 = NameAndType        #10:#11        // "":()V
  #21 = NameAndType        #8:#9          // age:I
  #22 = Class              #29            // java/lang/System
  #23 = NameAndType        #30:#31        // out:Ljava/io/PrintStream;
  #24 = Utf8               Hello World
  #25 = Class              #32            // java/io/PrintStream
  #26 = NameAndType        #33:#34        // println:(Ljava/lang/String;)V
  #27 = Utf8               Test
  #28 = Utf8               java/lang/Object
  #29 = Utf8               java/lang/System
  #30 = Utf8               out
  #31 = Utf8               Ljava/io/PrintStream;
  #32 = Utf8               java/io/PrintStream
  #33 = Utf8               println
  #34 = Utf8               (Ljava/lang/String;)V
{
  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."
":()V
         4: return
      LineNumberTable:
        line 1: 0

  public int getAge();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field age:I
         4: ireturn
      LineNumberTable:
        line 5: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #3                  // Field java/lang/System.out:Ljav
a/io/PrintStream;
         3: ldc           #4                  // String Hello World
         5: invokevirtual #5                  // Method java/io/PrintStream.prin
tln:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 8: 0
        line 9: 8
}
SourceFile: "Test.java"

准备工作完成。

三、class文件概述

1、魔数及主次版本信息

class 文件的头四个字节是魔数,作用是确定这个文件是否为一个能被虚拟机接收的class文件。紧接着魔数的4个字节是class文件的版本号。第5和6个字节是次版本号,7,8字节是主版本号。

cafe babe 0000 0034 

2、常量池

紧接着主次版本之后的是常量池入口,常量池可以理解是class文件之中的资源仓库,是class文件中空间最大的项目之一。

由于常量池中常量的个数是不固定的,所以在常量池的入口放置一个u2(无符号2个字节大小)的数据,代表常量池容量计数器(constant_pool_count),这个容量计数是从1开始,不是从零开始的。

0023,十进制的35,即有34个项常量,索引值范围是1---34.
验证: javap -verbose Test.class 查看图片,可以看到常量池中有34项。

2.1为什么不是从0开始索引?

这样做的目的用于满足后面某些指向常量池的索引值的数据在特定的情况下需要表达"不引用任何一个常量池项目"的含义,这种情况就需要把索引值用0来表示。

2.2 常量池中存放了一些什么数据?

两大类:字面量和符号引用(和编译原理相关)

字面量如文本字符串,声明为final的常量值等。
符号引用:类和接口的全限定名,字段的名称和描述符,以及方法的名称和限定符

由于java代码在进行javac编译的时候,并不像c和c++那样有连接这一步骤,而是在虚拟机加载class文件的时候进行动态链接。也就是说class文件中,并不会保存各个方法字段的最终内存布局信息。当虚拟机运行的时候,需要从常量池获得对应的符号引用,再在类创建时或者运行时解析,翻译到具体内存的内存地址中。

常量池中的数据,每一项都是一个表

Java类编译后Class文件概述(上)_第3张图片

常量池中每一项的格式

cp_info {
	u1 tag;
	u1 info[];
	...
}
0a00 0a就是tag位,0a是十进制的10,查表可得10是method_info methods[methods_count];//方法表。即这里是方法表

3、访问标志access_flags

访问标志,access_flags 是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性

常量池结束后,两个字节表示访问标志access_flags。
access_flag中一共有16个标志位可以使用。当前只定义了8个。没有使用到的标志位一律为0
Test类是一个普通类,不是接口,枚举或者注解。被public修饰,没有final
所以ACC_PUBLIC,ACC_SUPER为真,ACC_FINAL,ACC_INTERFACE,ACC_ABSTRACT,ACC_SYNTHETIC,ACC_ANNOTATION,
ACC_ENUM这六个标志位为假,所以标志位为0x0001|0x0020=0x0021
在验证中,也可以看到,常量池最后一个位置是V,接下来就是访问标志0021。

具体的访问标志及访问标志位如下

Java类编译后Class文件概述(上)_第4张图片

4、类索引,父类索引,接口索引集合


类索引,父类索引的值必须是对constant_pool 表中项目的一个有效索引值。类索引和父类索引分别是两个u2类型的索引值表示。

访问标志之后,就是按顺序类索引,父类索引,接口索引

接口索引,第一项u2类型的数据为接口计数器interfaces_count,表示索引表的容量
表示类索引为6,父类索引是7,接口索引集合大小是0
然后根据javap计算出来的常量池

	0006 0007 0000
	表示类索引为6,父类索引是7,接口索引集合大小是0
	然后根据javap计算出来的常量池
	类索引为6,找到第六项,是 #6 = Class              #27            // Test
	index 是27
	找到第27项,是 #27 = Utf8               Test,即类名是Test
	父类索引是7,找到第七项		#7 = Class              #28            // java/lang/Object
	index是28
	找到28,#28 = Utf8               java/lang/Object,即父类是Object

5、字段表集合

描述的是接口或者类中声明的变量。这里的变量是类级的变量,不包括方法的局部变量。
在字段表之前,有u2的容量计数器,然后才是access_flags
字段表结构

	field_info {
		//access_flags 项的值是用于定义字段被访问权限和基础属性的掩码标志
		u2 access_flags; 
		//name_index 项的值必须是对常量池的一个有效索引。
		//常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效的字段的非全限定名
		u2 name_index; 
		//descriptor_index 项的值必须是对常量池的一个有效索引。
		//常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效的字段的描述符
		u2 descriptor_index;
		attributes_count 的项的值表示当前字段的附加属性的数量。
		u2 attributes_count;
		//attributes 表的每一个成员的值必须是attribute结构,一个字段可以有任意个关联属性.
		attribute_info attributes[attributes_count];
	}

字段访问标志

Java类编译后Class文件概述(上)_第5张图片Java类编译后Class文件概述(上)_第6张图片

字段如果带有ACC_SYNTHETIC 标志,则说明这个字段不是由源码产生的,而是由编译器自动产生的。

描述符标识字符含义

B ==byte ,C==char, D==double,F==float,I==int,J==long,S==short,Z==boolean,V==void,L==对象类型

数组类型,每一维度用一个前置的'['表示,比如String[][],表示为“[[java/lang/String”,int[] 被记录为“[I”


描述符描述方法时,按照先参数列表后返回值的方法,比如lang.toString()被描述为 “()Ljava/lang/String”

int indexOf(char[] source, int sourceOffset, int sourceCount,char[] target, int tagetOffset, int targetCount, int fromIndex)
这个方法被描述为"([CII[CIII)I"

Test.class实例中的

	0001 0002 0008 0009 0000 
	实例0001 fields_count 容量表,为1,说明只有一个字段表数据。
	0002 代表private修饰
	0008 name_index 为8,查表  #8 = Utf8               age,为属性名
	0009 descriptor_index,为9,查表 #9 = Utf8               I ,则可以得出字段为 private int age;
	0000 没有其他信息

6、方法表集合

集合的结构描述

	method_info {
		//access_flags 项的值是用于定义当前方法的访问权限和基本属性的掩码标志.标志如表
		u2 access_flags;
		//name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,
		//它要么表示初始化方法的名字(),要么表示一个方法的有效的非全限定名
		u2 name_index;
		//descriptor_index 项的值必须是对常量池的一个有效索引。
		//常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效的方法的描述符
		u2 descriptor_index;
		//attributes_count 的项的值表示这个方法的附加属性的数量
		u2 attributes_count;
		//attributes 表的每一个成员的值必须是attribute结构,一个方法可以有任意个与之相关的属性。
		attribute_info attributes[attributes_count];
	}

注意:方法的定义及属性信息在方法表中,但是方法里面的代码不在这里,存放在方法属性表集合中一个名为code的属性里面。
入口是一个u2类型的计数器(methods_count),代表方法的数量,然后才是访问 标志。

方法访问标志


Java类编译后Class文件概述(上)_第7张图片

Test.class实例中的

	0003 0001 000a 000b 0001 000c
	0003 : methods_count,值为3,三个方法,getAge(),main(),以及编译器添加的实例构造器方法
	0001 : access_flags,第一个方法的访问标志位,也就是说只有ACC_PUBLIC为真
	000a : 第一个方法的name_index,值为10,查表 #10 = Utf8 ,就是init方法
	000b : 第一个方法的描述符索引,值为11,查表 #11 = Utf8   ()V,即方法的返回值为void
	0001 : attributes_count,此方法的属性表集合有一项属性
	000c : attributes_name_index,值为12,查表 #12 = Utf8  Code

6.1方法的重载

在java中,重载一个方法,除了与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合。而返回值不会包含在特征签名之中,因此java语言无法仅仅依靠返回值不同来判断方法重载。

7、属性表集合

属性表的通用格式

	attribute_info {
		//attribute_name_index 必须是对当前Class 文件的常量池的有效16 位无符号索引
		u2 attribute_name_index;
		//attribute_length 项的值给出了跟随其后的字节的长度,
		//这个长度不包括attribute_name_index 和attribute_name_index 项的6 个字节
		u4 attribute_length;
		u1 info[attribute_length];
	}

为了正确的解析class文件,java虚拟机规范预定义了20项虚拟机应当识别的属性。


Java类编译后Class文件概述(上)_第8张图片

Java类编译后Class文件概述(上)_第9张图片

属性表中的常见的几个属性说明

7.1code

Java 程序方法体中的代码经过javac编译器处理之后,最终变为字节码指令存储在code属性内。

	Code_attribute {
			//attribute_name_index 项的值必须是对常量池的有效索引,
			//常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示字符串“Code”。
			u2 attribute_name_index;
			//attribute_length 项的值表示当前属性的长度,不包括开始的6 个字节
			u4 attribute_length;
			//max_stack 项的值给出了当前方法的操作数栈在运行执行的任何时间点的最大深度
			u2 max_stack;
			//代表了局部变量表所需的存储空间,单位是slot,slot是虚拟机为局部变量分配内存所使用的最小单位,
			//double和long需要两个slot,其他基本类型包括returnAddress需要一个slot
			u2 max_locals;
			//code_length 项给出了当前方法的code[]数组的字节数,code_length 的值必须大于0,即code[]数组不能为空
			u4 code_length;
			//code[]数组给出了实现当前方法的Java 虚拟机字节码
			u1 code[code_length];
			//exception_table_length 项的值给出了exception_table[]数组的成员个数量
			u2 exception_table_length;
			//exception_table[]数组的每个成员表示code[]数组中的一个异常处理器(Exception Handler)。
			//exception_table[]数组中,异常处理器顺序是有意义的(不能随意更改)
			{ u2 start_pc;
				u2 end_pc;
				u2 handler_pc;
				u2 catch_type;
			} exception_table[exception_table_length];
			//attributes_count 项的值给出了Code 属性中attributes 表的成员个数
			u2 attributes_count;
			//属性表的每个成员的值必须是attribute结构。一个Code 属性可以有任意数量的可选属性与之关联
			attribute_info attributes[attributes_count];
	}

Code 属性是一个变长属性,位于method_info结构的属性表。一个Code 属性只为唯一一个方法、实例类初始化方法或类初始化方法保存Java 虚拟机指令及相关辅
助信息。所有Java 虚拟机实现都必须能够识别Code 属性。如果方法被声明为native 或者abstract 类型,那么对应的method_info 结构不能有明确的Code 属性,其它情况下,method_info 有必须有明确的Code 属性。

7.2Exceptions 属性

	Exceptions_attribute {
		//必须是对常量池的一个有效索引。常量池在该索引处的成员必须是CONSTANT_Utf8_info结构,表示字符串"Exceptions"
		u2 attribute_name_index;
		//attribute_length 项的值给出了当前属性的长度,不包括开始的6 个字节。
		u4 attribute_length;
		//number_of_exceptions 项的值给出了exception_index_table[]数组中成员的数量
		u2 number_of_exceptions;
		//exception_index_table[]数组的每个成员的值都必须是对常量池的有效索引。
		//常量池在这些索引处的成员必须都是CONSTANT_Class_info结构,表示这个方法声明要抛出的异常的类的类型
		u2 exception_index_table[number_of_exceptions];
	}

一个方法如果要抛出异常,必须至少满足下列三个条件中的一个:

 要抛出的是RuntimeException 或其子类的实例。

 要抛出的是Error 或其子类的实例。

 要抛出的是在exception_index_table[]数组中申明的异常类或其子类的实例。

这些要求没有在Java 虚拟机中进行强制检查,它们只在编译时进行强制检查。

7.3ConstantValue 属性

通知虚拟机自动为静态变量赋值,只有被static修饰的变量才可以使用这项属性。

	ConstantValue_attribute {
		//attribute_name_index 项的值,必须是一个对常量池的有效索引。
		//常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示字符串“ConstantValue”。	
		u2 attribute_name_index;
		//ConstantValue_attribute 结构的attribute_length 项的值固定为2
		u4 attribute_length;
		//constantvalue_index 项的值,必须是一个对常量池的有效索引。常量池在该索引处的项给出该属性表示的常量值。
		u2 constantvalue_index;
	}

7.4LineNumberTable 属性

用于描述Java源码行号与字节码行号的之间的对应关系。

	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];
	}

7.5LocalVariableTable 属性

用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系。

	LocalVariableTable_attribute {
		u2 attribute_name_index;
		u4 attribute_length;
		u2 local_variable_table_length;
		{ 
			u2 start_pc;
			u2 length;
			u2 name_index;
			u2 descriptor_index;
			u2 index;
		} local_variable_table[local_variable_table_length];
	}


你可能感兴趣的:(深入理解Java虚拟机)