Android art 虚拟机(一)Java class文件解析

一、本文用的示例代码及代码编译的class文件

代码

package com.android.test;
public class test {

	private final int a =2;
	private final double b;

	public static void main(String[] args) {
		int f = 23;
		double s = 2.2222;
	}
}

编译出的class文件用sublime打开后的16进制数据

  后续class文件解析即用此数据,下文引用此数据用图1-1表示

			cafe babe 0000 0034 001d 0a00 0900 1809
			0008 0019 0640 0aaa 9930 be0d ed09 0008
			001a 0640 01c7 10cb 295e 9e07 001b 0700
			1c01 0001 6101 0001 4901 000d 436f 6e73
			7461 6e74 5661 6c75 6503 0000 0002 0100
			0162 0100 0144 0100 063c 696e 6974 3e01
			0003 2829 5601 0004 436f 6465 0100 0f4c
			696e 654e 756d 6265 7254 6162 6c65 0100
			046d 6169 6e01 0016 285b 4c6a 6176 612f
			6c61 6e67 2f53 7472 696e 673b 2956 0100
			0a53 6f75 7263 6546 696c 6501 0009 7465
			7374 2e6a 6176 610c 0010 0011 0c00 0a00
			0b0c 000e 000f 0100 1563 6f6d 2f61 6e64
			726f 6964 2f74 6573 742f 7465 7374 0100
			106a 6176 612f 6c61 6e67 2f4f 626a 6563
			7400 2100 0800 0900 0000 0200 1200 0a00
			0b00 0100 0c00 0000 0200 0d00 1200 0e00
			0f00 0100 0c00 0000 0200 0300 0200 0100
			1000 1100 0100 1200 0000 3100 0300 0100
			0000 112a b700 012a 05b5 0002 2a14 0003
			b500 05b1 0000 0001 0013 0000 000e 0003
			0000 0002 0004 0004 0009 0005 0009 0014
			0015 0001 0012 0000 0028 0002 0004 0000
			0008 1017 3c14 0006 49b1 0000 0001 0013
			0000 000e 0003 0000 0008 0003 0009 0007
			000a 0001 0016 0000 0002 0017 
	

二、Java class文件格式

在这里插入图片描述
                        表1-1
  上图是Java class的文件格式,需要注意的是class的文件结构不像xml等描述语言,他没有任何分隔符号,所以上表中的数据,无论是顺序还是数量,都是被严格限定的,哪个字节是什么含义,长度多少,顺序如何都不允许改变,所以手动解析class文件时需要严格按照上表的顺序去解析。表中u2、u4表示2个字节、4个字节的无符号数。

(1) 魔数magic

  每个Class文件的开头4个字节称为魔数,它的作用是用来确定这个文件是否为一个能被虚拟机接受的Class文件,这是固定的数,值为0xCAFEBABE,即图1-1开头的cafe babe。

(2) 版本号:minor_version和major_version

  紧接着魔数后的4个字节表示次版本号(前两个字节,图1-1中0000)和主版本号(后两个字节,图1-1中0034,即52,我用的是jdk1.8)。

(3) 常量池容量与常量池:constant_pool_count和constant_pool

  紧接着版本号后的是常量池入口,常量池可以理解为class文件里的资源仓库。与常量池相关共有两项:常量池数组容量和常量池数组。由于常量池中的常量项数量不是固定的,所以在常量池入口需要放一个u2类型的数据,即constant_pool_count来表示常量池数组元素的个数。constant_pool是一个存储cp_info(cp即constant pool)信息的数组,每一个class文件都会包含一个常量池。另外cp数组的索引是从1开始如表1-1中所述,cp数组的元素个数为constant_pool_count - 1,而constant_pool_count的值对应版本号后两个字节001d即29,所以常量池的元素数量为28个(1–28)

  设计者将index 0空出来不用是为了后面某些指向常量池的索引值数据在特定情况下需要表达“不引用任何一个常量池项目的含义”。需要注意的是后文所述的其他集合类型,包括接口索引集合、字段表集合等容量计数都是从0开始

  下面开始解析常量池数组
  上文说了常量池数组是一个cp_info类型的数组,即数组的每个元素都是cp_info对象,此对象可以用如下伪代码表示,那么怎么从图1-1中的一堆数字解析出常量池呢。

cp_info {
	u1 tag;//每一个cp_info的第一个字节都用来表示该常量项的类型
	u1 info[];//表示常量项的具体人内容,此处不是固定的,也有可能是index等。
}

  首先常量池的元素数量(即常量项数量)我们已经确定是0x001d -1 = 28个。即有28个cp_info元素。而通过每个cp_info的第一个字节可以确定cp_info的类型。
下表是常量池中常量项的类型及对应的值,那么constant_pool_count后第一个字节为0a=10,即第一个常量项类型为CONSTANT_Methodref_info
但是仅仅这样我们也只能确定出常量池的第一个常量项是啥类型,无法解析出这个常量项以及后续常量项的内容。所以同志仍需努力。

在这里插入图片描述
  而下表就是我们可以继续解析的依赖,下表是常量池中17种常量项数据类型的结构表,即每种常量项的数据结构,接着上面第一个常量项是CONSTANT_Methodref_info,其对应结构在下表中查找发现为

CONSTANT_Methodref_info {
	u1 tag; 
	u2 class_info_index; 
	u2 Name_And_Type_index;
}

CONSTANT_Methodref_info 常量项存储着成员函数的信息,class_info_index指向声明方法的类描述符CONSTANT_Class_info的索引(最终值是“java/lang/Object”,此为此类的父类索引,例如写的类继承自ArrayList,此处的值就是“java/util/ArrayList”), Name_And_Type_index指向函数名称及类型描述符CONSTANT_NameAndType_info的索引。

  按照图1-1中数据继续对应起来就是tag对应0a=10,class_info_index对应0009=9,这个index为9表示使用的是常量池数组中第9项的常量值,Name_And_Type_index对应0018=24表示使用的是常量池数组中第24项的常量值,具体是啥等我们解析完常量池之后才能见分晓。至此第一个常量项解析完毕。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  继续苦逼的手动解析,接着第二个常量项tag为09=9,即第二个常量项为

CONSTANT_Fieldref_info {
   u1 tag;  //---对应09=9
   u2 class_info_index; //---对应0008=8
   u2 Name_And_Type_index; //---对应0019=25
}

  按照图1-1中数据继续对应起来就是tag对应09=9,class_info_index对应0008=8,这个index为8表示使用的是常量池数组中第8项的常量值,Name_And_Type_index对应0019=25表示使用的是常量池数组中第25项的常量值。

  接着第三个常量项tag为06=6,即对应常量项为

CONSTANT_Double_info {
 u1 tag;  //---对应06=6
 u8 bytes //---对应8个字节数据,40 0aaa 9930 be0d ed
}

  按照图1-1中数据继续对应起来就是tag对应06=6,对应的double类型数据值是8个字节,即40 0aaa 99 30 be0d ed,关于这8字节数据怎么得到的3.3333,请大家自行百度double转二进制吧。。。。。

  接着第四个常量项tag为09=9,即第二个常量项为

CONSTANT_Fieldref_info {
   u1 tag;  //---对应09=9
   u2 class_info_index; //---对应0008=8
   u2 Name_And_Type_index; //---对应001a=26
}

  按照图1-1中数据继续对应起来就是tag对应09=9,class_info_index对应0008=8,这个index为8表示使用的是常量池数组中第8项的常量值,Name_And_Type_index对应001a=26表示使用的是常量池数组中第25项的常量值。

  接着第五个常量项tag为06=6,即对应常量项为

CONSTANT_Double_info {
 u1 tag;  //---对应06=6
 u8 bytes //---对应8个字节数据,40 0aaa 9930 be0d ed
}

  按照图1-1中数据继续对应起来就是tag对应06=6,对应的double类型数据值是8个字节,即40 01c7 10cb 295e 9e,关于这8字节数据怎么得到的2.2222,也请大家自行百度double转二进制吧。。。。。

  接着第六个常量项tag为07=7,即对应常量项为

CONSTANT_Class_info {
 u1 tag;  //---对应07=7
 u2 index//---对应2个字节数据 001b=27 
}

  按照图1-1中数据继续对应起来就是tag对应07=7,index对应001b=27,这个index为27表示使用的是常量池数组中第27项的常量值。

  接着第七个常量项tag为07=7,即对应常量项为

CONSTANT_Class_info {
 u1 tag;  //---对应07=7
 u2 index//---对应2个字节数据 001c=28 
}

  按照图1-1中数据继续对应起来就是tag对应07=7,index对应001c=28,这个index为28表示使用的是常量池数组中第28项的常量值。

  接着第八个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;  //---对应01=1
 u2 length//---对应2个字节数据 0001=1
 u1 bytes[length]//---对应长度为length的UTF-8编码的字符串
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应0001=1,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为1个字节,那么对应的数据为61,通过查ASCII表得61对应为"a"。

  接着第九个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;
 u2 length
 u1 bytes[length]
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应0001=1,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为1个字节,那么对应的数据为49,通过查ASCII表得49对应为"I"。

  接着第十个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;
 u2 length
 u1 bytes[length]
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应000d=13,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为13个字节,那么对应的数据为43 6f 6e 73 74 61 6e 74 56 61 6c 75 65,通过查ASCII表得此串数据对应为"ConstantValue"。

  接着第十一个常量项tag为03=3,即对应常量项为

CONSTANT_Integer_info {
 u1 tag;
 u4 bytes
}

  按照图1-1中数据继续对应起来就是tag对应03=3,bytes对应4个字节存储的int型数据,0000 0002,即值为2。

  接着第十二个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;
 u2 length
 u1 bytes[length]
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应0001=1,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为1个字节,那么对应的数据为62,通过查ASCII表得对应为"b"。

  接着第十三个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;
 u2 length
 u1 bytes[length]
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应0001=1,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为1个字节,那么对应的数据为44,通过查ASCII表得对应为"D"。

  接着第十四个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;
 u2 length
 u1 bytes[length]
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应0006=6,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为6个字节,那么对应的数据为3c 696e 6974 3e,通过查ASCII表得对应为"<init>"。

  接着第十五个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;
 u2 length
 u1 bytes[length]
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应0003=3,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为3个字节,那么对应的数据为2829 56,通过查ASCII表得对应为"()V"。

  接着第十六个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;
 u2 length
 u1 bytes[length]
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应0004=4,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为4个字节,那么对应的数据为436f 6465,通过查ASCII表得对应为"Code"。

  接着第十七个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;
 u2 length
 u1 bytes[length]
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应000f=15,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为15个字节,那么对应的数据为4c696e 654e 756d 6265 7254 6162 6c65,通过查ASCII表得对应为"LineNumberTable"。

  接着第十八个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;
 u2 length
 u1 bytes[length]
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应0004=4,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为4个字节,那么对应的数据为6d 6169 6e,通过查ASCII表得对应为"main"。

  接着第十九个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;
 u2 length
 u1 bytes[length]
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应0016=22,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为22个字节,那么对应的数据为285b 4c6a 6176 612f
6c61 6e67 2f53 7472 696e 673b 2956,通过查ASCII表得对应为"([Ljava/lang/String;)V"。

  接着第二十个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;
 u2 length
 u1 bytes[length]
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应000a=10,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为10个字节,那么对应的数据为53 6f75 7263 6546 696c 65,通过查ASCII表得对应为"SourceFile"。

  接着第二十一个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;
 u2 length
 u1 bytes[length]
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应0009=9,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为9个字节,那么对应的数据为7465 7374 2e6a 6176 61,通过查ASCII表得对应为"test.java"。

  接着第二十二个常量项tag为0c=12,即对应常量项为

CONSTANT_NameAndType_info {
 u1 tag;
 u2 index
 u2 index
}

  按照图1-1中数据继续对应起来就是tag对应0c=12,index对应0010=16,即index为常量池数组的第16项,第二个index对应0011=17,即index为常量池数组的第17项

  接着第二十三个常量项tag为0c=12,即对应常量项为

CONSTANT_NameAndType_info {
 u1 tag;
 u2 index
 u2 index
}

  按照图1-1中数据继续对应起来就是tag对应0c=12,index对应000a=10,即index为常量池数组的第10项,第二个index对应000b=11,即index为常量池数组的第11项

  接着第二十四个常量项tag为0c=12,即对应常量项为

CONSTANT_NameAndType_info {
 u1 tag;
 u2 index
 u2 index
}

  按照图1-1中数据继续对应起来就是tag对应0c=12,index对应0003=14,即index为常量池数组的第14项,第二个index对应000f=15,即index为常量池数组的第15项

  接着第二十五个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;
 u2 length
 u1 bytes[length]
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应0015=21,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为21个字节,那么对应的数据为63 6f6d 2f61 6e64 726f 6964 2f74 6573 742f 7465 7374,通过查ASCII表得对应为"com/android/test/test"。

  接着第二十六个常量项tag为01=1,即对应常量项为

CONSTANT_Ut8_info {
 u1 tag;
 u2 length
 u1 bytes[length]
}

  按照图1-1中数据继续对应起来就是tag对应01=1,length对应0010=16,bytes对应即为长度为length的UTF-8编码的字符串,也就是此字符串的长度为16个字节,那么对应的数据为6a 6176 612f 6c61 6e67 2f4f 626a 6563
74,通过查ASCII表得对应为"java/lang/Object"。

  继续发现后面一个字节是00,常量池中的类型中并没有tag为0的,至此说明常量池已经解析完毕,但是综上总共26个常量项和实际刚开头说的常量项count为28不符,难道我们是计算差了么。实际并不是,注意,CONSTANT_Long_info 或 CONSTANT_Double_info常量结构占两个常量表项的空间,其他都占一个空间,即如果一个CONSTANT_Long_info 或 CONSTA,所以前面我们解析出了有两个double类型的常量项所以最后需要加上他俩各多占的一个空间,就能和28对应起来了。
综上,最终解析出来的常量池数据如下
在这里插入图片描述

(4) 访问标志(access_flags)

  常量池结束后,紧接着的2个字节表示访问标志,这个标志用于识别一些类或者接口层次的访问信息,包括这个Class是类还是接口,是否定义为public类型,是否定义为abstract类型,如果是类的话,是否被声明为final等等,详细标志的含义见下表
在这里插入图片描述
  继续解析,接着的2个字节为00 21由上表可知为ACC_SUPER|ACC_PUBLIC得出,即此是个声明为public的类

(5)类索引和父类索引(this_class和super_class)

  这两项存储的是指向常量池的索引,this_class指向的索引内容表示本类的名称,即0008,第8个常量项最终指向的内容是com/android/test/test;super_class指向的索引内容表示父类的名称即0009,第9个常量项最终指向的内容是java/lang/Object。

(6)接口数量和接口索引表(interfaces_count和interfaces)

  这两项表示该类中实现了多少个接口以及接口类的类名。
由于本文中所用的代码没有接口,所以interfaces_count的值为0000,故而接口索引表就不再占用任何字节。

(7)成员变量数量和成员变量表(fields_count和fields)

  这俩项也叫字段数量和字段表,java中的“字段”包括类级变量以及实例级变量,但不包括方法内部声明的局部变量。 fields_count表示成员变量的数量,本示例中为00 02,即2个变量。fields表示成员变量的信息,成员变量的信息由field_info结构体表示。

field_info {
	u2 access_flags;
	u2 name_index;
	u2 descriptor_index;
	u2 attributes_count;
	attribute_info attributes[attributes_count];
}

access_flags表示访问标志,相关标志取值见下表

在这里插入图片描述

name_index表示指向成员变量名字的常量项索引

descriptor_index,常量项索引,描述成员变量的FieldDescriptor,FieldDescriptor表示成员变量的数据类型,分为3种:1、基本数据类型(B,byte;C,char;D,double;F,float;I,int;J,long;S,short;Z,boolean;)。2、引用类型(LXXX如Ljava/lang/String)。3、数组类型([XXX,如int数组描述为[I,字符串数组为[Ljava/lang/String)

attributes为成员变量的属性信息,attribute_info结构体伪代码如下

attribute_info{
	u2 attribute_name_index;
	u4 attribute_length;
	u1 info[attribute_length];
}

attribute_name_index表示属性名称,指向常量项索引
attribute_length表示该属性内容的长度,即info数组的长度
info表示属性的具体内容

虚拟机规范中的一些属性

在这里插入图片描述

  综上开始解析成员变量,由于fields_counts为2,首先解析第一个变量access_flags的值为0012即ACC_FINAL|ACC_PRIVATE,此变量是private final修饰的;name_index值为000a=10,对应变量名称为a;descriptor_index值为000b,即I,表示变量为int类型;attributes_count值为0001,有一条属性信息。继续解析attributes表,attribute_name_index值为000c,表示属性名称为ConstantValue(由上表可知表示由final关键字定义的常量值)。attribute_length值为00 0000 02。进而得出info占2个字节,属性的内容为000d,对应常量池的常量项为13即2。如此可得出第一个变量为private final int a =2;
  同理,第二个变量access_flags的值为0012即ACC_FINAL|ACC_PRIVATE,此变量是private final修饰的;name_index值为000e=14,对应变量名称为b;descriptor_index值为000f,即D,表示变量为double类型;attributes_count值为0001,有一条属性信息。继续解析attributes表,attribute_name_index值为000c,表示属性名称为ConstantValue(由上表可知表示由final关键字定义的常量值)。attribute_length值为00 0000 02。进而得出info占2个字节,属性的内容为0003,对应常量池的常量项为3即3.3333d。如此可得出第一个变量为private final double b =3.3333d;

(8)成员函数数量和成员函数表(methods_count和methods)

   methods_count表示成员函数的数量,本示例中为00 02,即2个函数。methods表示成员函数的信息,成员函数的信息由method_info结构体表示。

method_info{
	u2 access_flags;
	u2 name_index;
	u2 descriptor_index;
	u2 attributes_count;
	attribute_info attributes[attributes_count];
}

access_flags表示访问标志,相关标志取值见下表

在这里插入图片描述

name_index表示指向成员函数名字的常量项索引

descriptor_index,常量项索引,描述成员函数的Method_Descriptor,Method_Descriptor用于描述函数的返回值和参数的数据类型,返回值类型主要有两种:FieldType(参考上文中成员变量FieldDescriptor的三种数据类型)和VoidDescriptor(仅用来表示void,值为V),参数的数据类型也是FieldType(参考上文中成员变量FieldDescriptor的三种数据类型)。

attributes为成员变量的属性信息,attribute_info结构体伪代码如下,注意这只是个示例,实际不同的属性表的结构是不同的。

attribute_info{
	u2 attribute_name_index;
	u4 attribute_length;
	u1 info[attribute_length];
}

attribute_name_index表示属性名称,指向常量项索引

attribute_length表示该属性内容的长度,即info数组的长度

info表示属性的具体内容,不同属性其内容是不同的,可能有多项,此处仅是示例,例如下面讲到的Code
虚拟机规范中的一些属性

在这里插入图片描述

  综上开始解析成员函数,methods_count值为0002,表示有两个成员函数。首先第一个函数access_flags值00 01,即表示ACC_PUBLIC;name_index值为0010=16,对应成员函数名称为<init>,而实际代码中并没有此函数,此函数是编译器自动添加的实例构造函数。descriptor_index值为0011,对应()V即表示该函数没有参数,返回值为void;attributes_count值为0001,即仅有一个属性;则attribute_name_index值为0012,指向常量池第18个常量项,对应属性名称为Code,下表为Code属性表的结构

在这里插入图片描述
  Code属性伪代码如下,在这么多属性中Code属性是相对重要的一个,因为函数的源码转换后的字节码就是存储在Code属性中。

Code_attribute {
	u2 attribute_name_index;
	u4 attribute_length;
	u2 max_stack;
	u2 max_locals;
	u4 code_length;
	u1 code[code_length];
	u2 exception_table_length 
	exception_table[exception_table_length];
	{
		u2 start_pc;
		u2 end_pc;
		u2 handler_pc;
		u2 catch_type;
	}
	u2 attributes_count;
	attribute_info attr[attributes_count];
}

attribute_name_index:指向内容为Code的常量池中的常量项;

attribute_length:表示接下来属性内容的长度;

max_stack:java虚拟机执行一个指令的时候,该指令的操作数存储在一个名叫“操作数栈(operand stack)”的地方,每一个操作数占用一个或两个(long、double类型的操作数会占用两个)栈项。stack是一块只能先入后出的内存,max_stack用于说明这个函数在执行过程中,需要最深多少栈空间,也就是需要多少栈项。

max_locals:表示该函数包括最多几个局部变量。
注意max_stack和max_locals都和JVM如何执行一个函数有关。根据java虚拟机规范,每个函数执行的时候都会分配一个操作数栈和局部变量数组。所以Code属性表需要包含这些内容,这样虚拟机在执行函数前就可以分配好内存。

code_lengthcode:函数对应的指令内容也就是这个函数的源码经过编译器转换后得到的java字节码就存储在code数组里,长度为code_length,根据java虚拟机规范,此字节码包含两种类型的信息,首先是指令码,长度一个字节,紧接着指令码之后的是0个或多个操作数,不同的指令码可能需要不同个数的参数也可能不需要参数,具体指令码后需要几个字节请参考java虚拟机规范第6章;code数组内容如下图所示。

在这里插入图片描述

exception_table_lengthexception_table:一个函数可以包含多个try/catch语句,一个try/catch语句对应exception_table数组中的一项。先了解下pc(program counter)的概念。虚拟机执行的时候会维护一个变量来指向当前要执行的指令,这个变量就是pc。则:
start_pc:用来表示try/catch语句从哪条指令开始,注意,这个table中的各个pc变量取值必须位于代表整个函数内容的java字节码code[code_length]数组中。
end_pc:用来表示try语句到哪条指令结束,注意只包括try语句,不包括catch。
handler_pc:表示catch语句的内容从哪条指令开始。
catch_type:表示catch中截获的Exception或error的名字,指向常量池中的常量项。如果catch_type值为0,则表示它是final{}语句块

attributes_countattr:Code属性表中还可以继续包含其他属性,常见的有:
LineNumberTable:用于调试,比如指明哪条指令对应于源码哪一行,即java源码行号和字节码行号(字节码偏移量)之间的对应关系。此属性的如果取消的话最主要的影响是当程序运行时抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序时也无法按照源码行来设置断点,伪代码如下。

LineNumberTable_attribute {
	u2 attribute_name_index;
	u4 attribute_length;
	u2 line_number_table_length;
	line_number_table[] 
	{
		u2 start_pc;
		u2 line_number;
	}
}

attribute_name_index:属性名称索引,指向常量池常量项
attribute_length:属性内容长度;
**line_number_table:是一个数量为line_number_table_length的集合,集合中的start_pc表示字节码行号,line_number表示java源码行号。

LocalVariableTable:用于调试,调试时可以用于计算本地变量的值,详细可以参考深入理解java虚拟机一书,此处不再赘述。

  说了这么多开始解析Code属性吧,接着上文attribute_name_index值为0012=18即Code,attribute_length值为00000031=49;max_stack值为0003=3,表示此函数的操作数栈最大深度为3;max_locals值为0001,表示此函数只有一个局部变量;code_length值为00000011 =17,即表示code数组长度为17个字节,则code数组内容为2a b700 012a 05b5 0002 2a14 0003 b500 05b1;

根据字节码指令表(请参考java虚拟机规范第6章和第7章)翻译得到

在这里插入图片描述

  接着exception_table_length为0000,即没有try catch语句,所以exception_table也不占用字节;则attributes_count为0001,即一个属性,对应attribute_name_index值为0013=19,所以该属性名为LineNumberTable,由上文LineNumberTable的属性表结构得attribute_length为0000 000e=14,属性内容长度为14个字节;line_number_table_length值为0003=3,即line_number_table数组有3项,解析出来如下
在这里插入图片描述
  至此第一个函数解析完毕,开始解析第二个函数,首先access_flags值为0009,即ACC_PUBLIC|ACC_STATIC;name_index值为0014,对应函数名称为main;descriptor_index值为0015=21,对应常量项中常量为([Ljava/lang/String;)V,即此函数参数为String类型数组,返回值为Void;attributes_count值为0001=1,仅有一个属性;attribute_name_index值为0012=18,即Code,由Code属性表结构继续解析,attribute_length值为0000 0028=40;max_stack值为0002;max_locals值为0004;code_length值为00000008,即code数组长度为8个字节,1017 3c14 0006 49b1,翻译得
在这里插入图片描述

  接着exception_table_length为0000,即没有try catch语句,所以exception_table也不占用字节;则attributes_count为0001,即一个属性,对应attribute_name_index值为0013=19,所以该属性名为LineNumberTable,由上文LineNumberTable的属性表结构得attribute_length为0000 000e=14,属性内容长度为14个字节;line_number_table_length值为0003=3,即line_number_table数组有3项,解析出来如下
在这里插入图片描述

(9)类属性数量和类属性信息表(attributes_count和attributes)

   attributes_count表示类属性的数量,本示例中为0001,即1个属性。attributes表示类的属性信息,由attribute_info结构体表示。其实此属性的解析方法和上文中的函数属性差不多。

attribute_info{
   u2 attribute_name_index;
   u4 attribute_length;
   u1 info[attribute_length];
}

attribute_name_index表示属性名称,指向常量项索引

attribute_length表示该属性内容的长度,即info数组的长度

info表示属性的具体内容,不同属性其内容是不同的,可能有多项,此处仅是示例。

   attribute_name_index值为0016,对应该属性名称为SourceFile,此属性是用来记录生成这个class文件的源码文件名称。结构见下方伪代码,继而得attribute_length值为0000 0002,属性内容为2个字节,sourcefile_index值为最后的2个字节:0017,指向常量池第23个常量项即test.java。

SourceFile{
  u2 attribute_name_index;
  u4 attribute_length;
  u2 sourcefile_index;
}

至此整个test.class文件就解析完了,写的有点乱,等有时间再重新整理下,还有一些不理解的地方下次一起梳理修改。
实际使用javap -v test.class命令解析出的结果如下,大家可以比对下

Last modified 2022-5-30; size 412 bytes
MD5 checksum 587b28e86ac843eb4d8d147dec907f89
Compiled from "test.java"
public class com.android.test.test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#24         // java/lang/Object."<init>":()V
   #2 = Fieldref           #8.#25         // com/android/test/test.a:I
   #3 = Double             3.33
   #5 = Fieldref           #8.#26         // com/android/test/test.b:D
   #6 = Double             2.2222d
   #8 = Class              #27            // com/android/test/test
   #9 = Class              #28            // java/lang/Object 
   #10 = Utf8               a
   #11 = Utf8               I
   #12 = Utf8               ConstantValue
   #13 = Integer            2
   #14 = Utf8               b
   #15 = Utf8               D
   #16 = Utf8               <init>        
   #17 = Utf8               ()V
   #18 = Utf8               Code
   #19 = Utf8               LineNumberTable
   #20 = Utf8               main
   #21 = Utf8               ([Ljava/lang/String;)V
   #22 = Utf8               SourceFile
   #23 = Utf8               test.java
   #24 = NameAndType        #16:#17        // "<init>":()V
   #25 = NameAndType        #10:#11        // a:I
   #26 = NameAndType        #14:#15        // b:D
   #27 = Utf8               com/android/test/test
   #28 = Utf8               java/lang/Object
{
  public com.android.test.test();
  descriptor: ()V
    flags: ACC_PUBLIC
    Code:
    stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                   // Method java/lang/Object."<init>":()V                 
         4: aload_0
         5: iconst_2
         6: putfield      #2                  // Field a:I
         9: aload_0
         10: ldc2_w        #3                  // double 3.3333d
         13: putfield      #5                  // Field b:D
         16: return
         LineNumberTable:
           line 2: 0
           line 4: 4
           line 5: 9
    public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
    stack=2, locals=4, args_size=1
         0: bipush        23
         2: istore_1
         3: ldc2_w        #6                  // double 2.2222d
         6: dstore_2
         7: return
      LineNumberTable:
     	 line 8: 0
         line 9: 3
         line 10: 7
 }
SourceFile: "test.java"
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android Java虚拟机ART是一种全新的虚拟机,它是Android Lollipop操作系统中默认的运行时环境,相比于旧版的Dalvik虚拟机,它能够提供更好的性能和体验。 ART的最大特点是在使用前将字节码转换为机器码,这样可以在运行时减少解释和编译的时间,从而提高应用程序的响应速度。此外,在ART中也引入了一些新的技术,例如预编译、AOT和热编译等,这些都能够优化应用程序的启动速度和运行效率。 在深入研究ART之前,必须先了解Java虚拟机(JVM)的基本概念和原理。JVM是一种运行Java程序的虚拟机,将Java源代码转换为字节码,再由虚拟机解释执行。同样地,ART也采用相同的原理来实现应用程序的运行,只不过它将字节码转换为机器码,从而提高了运行速度和效率。 因此,熟悉Java虚拟机ART的工作原理,能够帮助开发者更好地理解和优化应用程序的性能。此外,对于一些需要高效运行的应用场景(例如游戏、图像处理等),ART也能够提供更好的运行环境,提高应用程序的稳定性和响应能力。 总之,深入理解Android Java虚拟机ART对于开发者来说非常重要,尤其是在需要优化应用程序性能和响应速度的情况下。只有深入了解ART的原理和特点,才能更好地应用它来提高应用程序的运行效率。 ### 回答2: Android Java虚拟机ARTAndroid系统中最新的运行时环境。相较于旧有的Dalvik虚拟机ART采用预编译技术,将应用程序字节码一次性编译为本地机器码,提高了应用程序的运行效率和响应速度,同时也降低了资源消耗。因此,深入理解Android Java虚拟机ART对于Android开发者来说是非常必要的。 深入学习ART,我们需要了解其内部运作机制,包括Dex编译、ClassLoader、Garbage Collection等关键概念。ART采用了AOT和JIT两种编译方式,也采用了一些新的优化方法,如Profile Guided Optimization(PGO)、Image Space Estimation等,以提高应用程序的可执行性和启动时间。 ARTClassLoader实现了一种高效的动态加载技术,它使得应用程序可以在运行时动态更新代码库、插件包等,从而大大扩展了应用程序的功能和灵活性。同时ARTClassLoader也是构建Android虚拟化环境的基础,它可以从不同的应用程序中加载开放的类,并为每个应用程序提供一个独立的执行环境。 最后,ART的Garbage Collection机制实现了一种全新的分代收集算法,将耗费大量时间的垃圾回收操作分散到不同的虚拟机堆内,从而大幅度提高了应用程序的性能和响应速度。 总之,深入理解Android Java虚拟机ART对于Android开发者来说十分关键,它将为我们提供更为深入的开发思路和方法,使我们的应用程序更加高效,同时也为我们的Android应用程序开发添上浓墨重彩的一笔。 ### 回答3: Android Java虚拟机ART (Android Runtime)是安卓4.4系统及以上版本中的默认虚拟机。相比原先的Dalvik虚拟机ART可实现更高的性能和更好的系统稳定性。 ART的核心思想是AOT( Ahead of Time)编译。它在应用程序安装的时候就将应用程序代码转换成本地机器指令并编译成机器代码,以C/C++库的形式保存在设备上。相比Dalvik,在应用程序的执行过程中省去了JIT编译的时间和运算,能够提高应用程序的运行速度。 除此之外,ART还有几个重要的特点: 1. 超低功耗:ART的AOT编译技术使得应用执行时可以直接使用本地机器指令,减少了CPU的时间浪费,使得应用程序的功耗更低。 2. 内存占用减少: ART允许应用程序在运行时进行类加载,实现更高效的内存管理。相比Dalvik虚拟机ART在处理内存和垃圾回收时能够更好地利用系统资源,减少了应用程序所占用的内存。 3. 支持快速应用开发:通过使用ART虚拟机可以通过模块形式快速开发出具有更好体验的应用程序。 总之,深入理解Android Java虚拟机ART需要着重理解ART AOT编译原理、内存管理机制、以及对快速应用开发的支持。这些特点的综合优势使得安卓应用程序能够实现更快的运行速度、更低的功耗、更快的开发效率和更好的用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值