zhulibin2012

深入理解java虚拟机---Class文件(二十)

无符号数、表

当实现了不同语言的编译器,比如jython,jruby等等,那么就可以利用这些语言编写代码,通过各自的编译器编译成符合jvm规范的字节码文件,就可以利用jvm来执行了。

Class文件在Java体系结构中的位置和作用

 

在上一篇博客中, 大致讲解了Java虚拟机的体系结构和执行原理。 本篇博客主要讲解能够被JVM识别, 加载并执行的class文件的格式。

 

对于理解JVM和深入理解Java语言, 学习并了解class文件的格式都是必须要掌握的功课。 原因很简单, JVM不会理解我们写的Java源文件, 我们必须把Java源文件编译成class文件, 才能被JVM识别, 对于JVM而言, class文件相当于一个接口, 理解了这个接口, 能帮助我们更好的理解JVM的行为;另一方面, class文件以另一种方式重新描述了我们在源文件中要表达的意思, 理解class文件如何重新描述我们编写的源文件, 对于深入理解Java语言和语法都是很有帮助的。 另外, 不管是什么语言, 只要能编译成class文件, 都能被JVM识别并执行, 所以class文件不仅是跨平台的基础, 也是JVM跨语言的基础, 理解了class文件格式, 对于我们学习基于JVM的其他语言会有很大帮助。 

 

总之, 在整个Java技术体系结构中, class文件处于中间的位置, 对于理解整个体系有着承上启下的作用。 如图所示:

 

Class文件格式概述

 
class文件是一种8位字节的二进制流文件, 各个数据项按顺序紧密的从前向后排列, 相邻的项之间没有间隙, 这样可以使得class文件非常紧凑, 体积轻巧, 可以被JVM快速的加载至内存, 并且占据较少的内存空间。 我们的Java源文件, 在被编译之后, 每个类(或者接口)都单独占据一个class文件, 并且类中的所有信息都会在class文件中有相应的描述, 由于class文件很灵活, 它甚至比Java源文件有着更强的描述能力。
 
class文件中的信息是一项一项排列的, 每项数据都有它的固定长度, 有的占一个字节, 有的占两个字节, 还有的占四个字节或8个字节, 数据项的不同长度分别用u1, u2, u4, u8表示, 分别表示一种数据项在class文件中占据一个字节, 两个字节, 4个字节和8个字节。 可以把u1, u2, u3, u4看做class文件数据项的“类型” 。

一、概述

不论什么一个Class文件都相应唯一一个类或接口的定义信息,可是不是全部的类或接口都得定义在文件里(它们也能够通过类载入器直接生成)。

Class文件是一组以8位字节为基础单位的二进制流。各个数据项严格按顺序排列,没有不论什么分隔符。

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

无符号数:是基本数据类型。以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节的无符号数,能够用来描写叙述数字、索引引用、数量值或者依照UTF-8编码构成的字符串值。

:由多个无符号数或者其它表作为数据项构成的复合数据类型。全部表都习惯性地以“_info”结尾。整个Class文件本质上就是一张表,例如以下所看到的:

类型

名称

数量

u4

magic

1

u2

minor_version

1

u2

major_version

1

u2

constant_pool_count

1

cp_info

constant_pool

constant_pool_count-1

u2

access_flags

1

u2

this_class

1

u2

super_class

1

u2

interfaces_count

1

u2

interfaces

interfaces_count

u2

fields_count

1

field_info

fields

fields_count

u2

methods_count

1

method_info

methods

methods_count

u2

attributes_count

1

attribute_info

attributes

attributes_count

 

 

接下来分别对表中的各个字段作出解释。
 
 

二、各个字段具体解释

 
使用以下的类进行说明:
package com.test;

public class Test {
	private int m;
	
	public int getM(){
		return m + 1;
	}
}
编译后的class文件例如以下:
 
 

1.魔数

每一个class文件的头4个字节称为魔数,它唯一的作用是确定这个文件是否为一个能被虚拟机接受的Class文件。

 

非常多文件存储标准中都使用魔数来进行身份识别。譬如图片格式gif、jpeg等。使用魔数而不是拓展名来进行识别主要是基于安全方面的考虑,由于文件拓展格式能够任意修改。

 
Class文件的魔数为:0xCAFEBABE(咖啡宝贝?)。这个魔数似乎也预示着日后JAVA这个商标名称的出现。

 

 

 
 

2.版本

第五六个字节是次版本(Minor Version)。第7和第8个字节是主版本(Major Version)。
高版本号的JDK能够向下兼容曾经版本号的Class文件,可是无法执行以后版本号的Class文件,即使文件格式并未发生变化,虚拟机也必须拒绝执行超过其版本号号的Class文件。
 

3.常量池

 
常量池能够理解为Class文件之中的资源仓库,是Class文件结构中与其它项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之中的一个。同一时候也是在Class文件里第一个出现的表类型数据项目。
因为常量池中常量的数目是不固定的所以在常量池入口须要放置一个2字节长的无符号数constatn_pool_count来代表常量池容量计数值。这个容量计数从1而不是0開始。
 
constant_pool_count:占2字节。0x0016。转化为十进制为22,即说明常量池中有21个常量(仅仅有常量池的计数是从1開始的,其他集合类型均从0開始),索引值为1~22。第0项常量具有特殊意义。假设某些指向常量池索引值的数据在特定情况下须要表达“不引用不论什么一个常量池项目”的含义,这样的情况能够将索引值置为0来表示
 
常量池中主要存放两大类常量:字面量和符号引用。字面量如文本字符串、声明为final的常量值等。符号引用包含三类常量:类和接口的全限定名、字段的名称和描写叙述符、方法的名称和描写叙述符。
 
常量池中的每一项常量都是一个表,在JDK1.7之前共同拥有11种结构各不同样的表数据结构。这些表数据结构在表開始的第一位是一个u1类型的标志位,代表当前这个常量属于那种常量类型。例如以下表所看到的:

类型

简单介绍

项目

类型

描写叙述

CONSTANT_Utf8_info

utf-8缩略编码字符串

tag

u1

值为1

length

u2

utf-8缩略编码字符串占用字节数

bytes

u1

长度为length的utf-8缩略编码字符串

CONSTANT_Integer_info

整形字面量

tag

u1

值为3

bytes

u4

依照高位在前储存的int值

CONSTANT_Float_info

浮点型字面量

tag

u1

值为4

bytes

u4

依照高位在前储存的float值

CONSTANT_Long_info

长整型字面量

tag

u1

值为5

bytes

u8

依照高位在前储存的long值

CONSTANT_Double_info

双精度浮点型字面量

tag

u1

值为6

bytes

u8

依照高位在前储存的double值

CONSTANT_Class_info

类或接口的符号引用

tag

u1

值为7

 

index

u2

指向全限定名常量项的索引

CONSTANT_String_info

字符串类型字面量

tag

u1

值为8

index

u2

指向字符串字面量的索引

CONSTANT_Fieldref_info

字段的符号引用

tag

u1

值为9

index

u2

指向声明字段的类或接口描写叙述符CONSTANT_Class_info的索引项

index

u2

指向字段描写叙述符CONSTANT_NameAndType_info的索引项

CONSTANT_Methodref_info

类中方法的符号引用

tag

u1

值为10

index

u2

指向声明方法的类描写叙述符CONSTANT_Class_info的索引项

index

u2

指向名称及类型描写叙述符CONSTANT_NameAndType_info的索引项

CONSTANT_InterfaceMethodref_info

接口中方法的符号引用

tag

u1

值为11

index

u2

指向声明方法的接口描写叙述符CONSTANT_Class_info的索引项

index

u2

指向名称及类型描写叙述符CONSTANT_NameAndType_info的索引项

CONSTANT_NameAndType_info

字段或方法的部分符号引用

tag

u1

值为12

index

u2

指向该字段或方法名称常量项的索引

index

u2

指向该字段或方法描写叙述符常量项的索引

 


首先来看常量池中的第一项常量,其标志位为0x07,是一个CONSTANT_Class_info类型常量。此类型常量代表一个类或接口的符号引用。依据其数据结构,接下来2位字节用来保存一个索引值,它指向常量池中一个CONSTANT_Utf8_info类型的常量,此常量代表了这个类或接口的全限定名,索引值为0x0002。即指向了常量池中的第二项常量。

第二项常量标志位为0x01。确实是一个CONSTANT_Utf8_info类型的常量。依据其数据结构。接下来2个字节用来保存utf-8缩略编码字符串长度,其值为0x000D,转化为十进制为13,即接下来的13个字节为一个utf-8缩略编码的字符串。为com/test/Test。能够看到正好是測试类的全限定名。

 

 

4.訪问标志

 
 
在常量池结束之后,紧接着的两个字节代表訪问标志。用于识别一些类或者接口层次的訪问信息。例如以下表所看到的。
 

志名称

标志值

含义

ACC_PUBLIC

0x0001

是否为public类型

ACC_FINAL

0x0010

是否被声明为final,仅仅有类可设置

ACC_SUPER

0x0020

是否同意使用invokespecial字节码指令,JDK1.2以后编译出来的类这个标志为真

ACC_INTERFACE

0x0200

标识这是一个接口

ACC_ABSTRACT

0x0400

是否为abstract类型,对于接口和抽象类,此标志为真。其他类为假

ACC_SYNTHETIC

0x1000

标识别这个类并不是由用户代码产生

ACC_ANNOTATION

0x2000

标识这是一个注解

ACC_ENUM

0x4000

标识这是一个枚举

 

依据上面的表格,測试类的訪问标志0x0021= 0x0001 | 0x0020 =ACC_PUBLIC | ACC_SUPER 

 

5.类索引、父类索引和接口索引集合

 
 

Class文件里由这3项数据来确定这个类的继承关系

this_class:类索引,用于确定这个类的全限定名,占2字节

super_class:父类索引。用于确定这个类父类的全限定名(Java语言不同意多重继承,故父类索引仅仅有一个。

除了java.lang.Object类之外全部类都有父类,故除了java.lang.Object类之外,全部类该字段值都不为0),占2字节

interfaces_count:接口索引计数器。占2字节。

假设该类没有实现不论什么接口。则该计数器值为0,而且后面的接口的索引集合将不占用不论什么字节。

interfaces:接口索引集合,一组u2类型数据的集合。用来描写叙述这个类实现了哪些接口。这些被实现的接口将按implements语句(假设该类本身为接口,则为extends语句)后的接口顺序从左至右排列在接口的索引集合中

this_class、super_class与interfaces中保存的索引值均指向常量池中一个CONSTANT_Class_info类型的常量。通过这个常量中保存的索引值能够找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串

this_class的值为0x0001,即常量池中第一个常量,super_class的值为0x0003,即常量池中的第三个常量,interfaces_counts的值为0x0000,故接口索引集合大小为0

6.字段表集合

字段表用于描写叙述接口或者类中声明的变量,包含类级变量和实例级变量(是否是static)。但不包含在方法内部声明的局部变量。
 

fields_count:字段表计数器。即字段表集合中的字段表数据个数。占2字节,其值为0x0001,即仅仅有一个字段表数据。也就是測试类中仅仅包括一个变量(不算方法内部变量)

fields:字段表集合,一组字段表类型数据的集合。字段表用于描写叙述接口或类中声明的变量。包含类级别(static)和实例级别变量,不包含在方法内部声明的变量

在Java中一般通过例如以下几项描写叙述一个字段:字段作用域(public、protected、private修饰符)、是类级别变量还是实例级别变量(static修饰符)、可变性(final修饰符)、并发可见性(volatile修饰符)、可序列化与否(transient修饰符)、字段数据类型(基本类型、对象、数组)以及字段名称。

在字段表中,变量修饰符使用标志位表示,字段数据类型和字段名称则引用常量池中常量表示,字段表格式例如以下表所看到的:

类型

名称

数量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

 

字段修饰符放在access_flags中,占2字节,其值为0x0002,可见这个字段由private修饰,与訪问标志位十分相似

标志名称

标志值

含义

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

ACC_SYNTHETIC

0x1000

字段是否为编译器自己主动产生

ACC_ENUM

0x4000

字段是否为enum




7.方法表集合

methods_count:方法表计数器,即方法表集合中的方法表数据个数。

占2字节,其值为0x0002,即測试类中有2个方法(还自己主动添加了一个构造函数)

methods:方法表集合,一组方法表类型数据的集合。

方法表结构和字段表结构一样:

类型

名称

数量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

数据项的含义很相似。仅在訪问标志位和属性表集合中的可选项上有稍微不同

因为ACC_VOLATILE标志和ACC_TRANSIENT标志不能修饰方法,所以access_flags中不包括这两项,同一时候添加ACC_SYNCHRONIZED标志、ACC_NATIVE标志、ACC_STRICTFP标志和ACC_ABSTRACT标志

标志名称

标志值

含义

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_BRIDGE

0x0040

方法是否是由编译器产生的桥接方法

ACC_VARARGS

0x0080

方法是否接受不定參数

ACC_NATIVE

0x0100

字段是否为native

ACC_ABSTRACT

0x0400

字段是否为abstract

ACC_STRICTFP

0x0800

字段是否为strictfp

ACC_SYNTHETIC

0x1000

字段是否为编译器自己主动产生

 

第一个方法(由编译器自己主动加入的默认构造方法):

access_flags为0x0001,即public;name_index为0x0007。即常量池中第7个常量;descriptor_index为0x0008,即常量池中第8个常量

[plain] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. const #7 = Asciz        <init>;  
  2. const #8 = Asciz        ()V;  

接下来2个字节为属性计数器,其值为0x0001,说明这种方法的属性表集合中有一个属性。属性名称为接下来2位0x0009,指向常量池中第9个常量:Code。接下来4位为0x0000002F,表示Code属性值的字节长度为47。接下来2位为0x0001。表示该方法的操作数栈的深度最大值为1。接下来2位依旧为0x0001,表示该方法的局部变量占用空间为1。接下来4位为0x0000005。则紧接着的5个字节0x2AB7000AB1为该方法编译后生成的字节码指令(各字节相应的指令不介绍了,可查询虚拟机字节码指令表)。接下来2个字节为0x0000,说明Code属性异常表集合为空。

接下来2个字节为0x0002,说明Code属性带有2个属性,那么接下来2位0x000C即为Code属性第一个属性的属性名称,指向常量池中第12个常量:LineNumberTable。接下来4位为0x00000006。表示LineNumberTable属性值所占字节长度为6。接下来2位为0x0001,即该line_number_table中仅仅有一个line_number_info表,start_pc为0x0000,line_number为0x0003,LineNumberTable属性结束。

接下来2位0x000D为Code属性第二个属性的属性名。指向常量池中第13个常量:LocalVariableTable。

该属性值所占的字节长度为0x0000000C=12。接下来2位为0x0001,说明local_variable_table中仅仅有一个local_variable_info表。依照local_variable_info表结构,start_pc为0x0000。length为0x0005,name_index为0x000E。指向常量池中第14个常量:this。descriptor_index为0x000F,指向常量池中第15个常量:Lcom/test/Test;。index为0x0000。

第一个方法结束

第二个方法:

access_flags为0x0001,即public。name_index为0x0010。即常量池中第16个常量。descriptor_index为0x0011,即常量池中第17个常量

[plain] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. const #16 = Asciz       getM;  
  2. const #17 = Asciz       ()I;  

接下来2个字节为属性计数器,其值为0x0001,说明这种方法有一个方法属性,属性名称为接下来2位0x0009,指向常量池中第9个常量:Code。接下来4位为0x00000031。表示Code属性值的字节长度为49。接下来2位为0x0002,表示该方法的操作数栈的深度最大值为2。接下来2位为0x0001,表示该方法的局部变量占用空间为1。接下来4位为0x0000007,则紧接着的7个字节0x2AB400120460AC为该方法编译后生成的字节码指令。

接下来2个字节为0x0000。说明Code属性异常表集合为空。

接下来2个字节为0x0002,说明Code属性带有2个属性。那么接下来2位0x000C即为Code属性第一个属性的属性名称,指向常量池中第12个常量:LineNumberTable。

接下来4位为0x00000006。表示LineNumberTable属性值所占字节长度为6。

接下来2位为0x0001。即该line_number_table中仅仅有一个line_number_info表,start_pc为0x0000。line_number为0x0007,LineNumberTable属性结束。

 

和第一个方法的LocalVariableTable属性基本同样,唯一的差别是局部变量this的作用范围覆盖的长度为7而不是5,第二个方法结束

假设子类没有重写父类的方法,方法表集合中就不会出现父类方法的信息。有可能会出现由编译器自己主动加入的方法(如:<init>。实例类构造器)

在Java语言中,重载一个方法除了要求和原方法拥有同样的简单名称外。还要求必须拥有一个与原方法不同的特征签名(方法參数集合),因为特征签名不包括返回值,故Java语言中不能只依靠返回值的不同对一个已有的方法重载;可是在Class文件格式中。特征签名即为方法描写叙述符,只要是描写叙述符不全然同样的2个方法也能够合法共存。即2个除了返回值不同之外全然同样的方法在Class文件里也能够合法共存

javap工具在后半部分会列出分析完毕的方法(能够看到和我们的分析结果是一样的):

[plain] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. d:\>javap -verbose Test  
  2. ......  
  3. {  
  4. public com.test.Test();  
  5.   Code:  
  6.    Stack=1, Locals=1, Args_size=1  
  7.    0:   aload_0  
  8.    1:   invokespecial   #10; //Method java/lang/Object."<init>":()V  
  9.    4:   return  
  10.   LineNumberTable:  
  11.    line 3: 0  
  12.   
  13.   LocalVariableTable:  
  14.    Start  Length  Slot  Name   Signature  
  15.    0      5      0    this       Lcom/test/Test;  
  16.   
  17. public int getM();  
  18.   Code:  
  19.    Stack=2, Locals=1, Args_size=1  
  20.    0:   aload_0  
  21.    1:   getfield        #18; //Field m:I  
  22.    4:   iconst_1  
  23.    5:   iadd  
  24.    6:   ireturn  
  25.   LineNumberTable:  
  26.    line 7: 0  
  27.   
  28.   LocalVariableTable:  
  29.    Start  Length  Slot  Name   Signature  
  30.    0      7      0    this       Lcom/test/Test;  
  31. }  

 

8.属性表集合

在Class文件、属性表、方法表中都能够包括自己的属性表集合。用于描写叙述某些场景的专有信息

与Class文件里其他数据项对长度、顺序、格式的严格要求不同,属性表集合不要求当中包括的属性表具有严格的顺序,而且仅仅要属性的名称不与已有的属性名称反复。不论什么人实现的编译器可以向属性表中写入自定义的属性信息。虚拟机在执行时会忽略不能识别的属性,为了能正确解析Class文件,虚拟机规范中提前定义了虚拟机实现必须可以识别的9项属性:

属性名称

使用位置

含义

Code

方法表

Java代码编译成的字节码指令

ConstantValue

字段表

finalkeyword定义的常量值

Deprecated

类文件、字段表、方法表

被声明为deprecated的方法和字段

Exceptions

方法表

方法抛出的异常

InnerClasses

类文件

内部类列表

LineNumberTale

Code属性

Java源代码的行号与字节码指令的相应关系

LocalVariableTable

Code属性

方法的局部变量描写叙述

SourceFile

类文件

源文件名

Synthetic

类文件、方法表、字段表

标识方法或字段是由编译器自己主动生成的

每种属性均有各自的表结构。

这9种表结构有一个共同的特点,即均由一个u2类型的属性名称開始,能够通过这个属性名称来判段属性的类型

Code属性:Java程序方法体中的代码经过Javac编译器处理后,终于变为字节码指令存储在Code属性中。当然不是全部的方法都必须有这个属性(接口中的方法或抽象方法就不存在Code属性)。Code属性表结构例如以下:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

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

 

max_stack:操作数栈深度最大值,在方法执行的不论什么时刻,操作数栈深度都不会超过这个值。虚拟机执行时依据这个值来分配栈帧的操作数栈深度

max_locals:局部变量表所需存储空间,单位为Slot(參见备注四)。

并非全部局部变量占用的Slot之和,当一个局部变量的生命周期结束后。其所占用的Slot将分配给其他依旧存活的局部变量使用。按此方式计算出方法执行时局部变量表所需的存储空间

code_length和code:用来存放Java源程序编译后生成的字节码指令。code_length代表字节码长度,code是用于存储字节码指令的一系列字节流。

 

每个指令是一个u1类型的单字节,当虚拟机读到code中的一个字节码(一个字节能表示256种指令,Java虚拟机规范定义了当中约200个编码相应的指令)。就能够推断出该字节码代表的指令。指令后面是否带有參数,參数该怎样解释。尽管code_length占4个字节,可是Java虚拟机规范中限制一个方法不能超过65535条字节码指令。假设超过。Javac将拒绝编译

ConstantValue属性:通知虚拟机自己主动为静态变量赋值,仅仅有被statickeyword修饰的变量(类变量)才干够使用这项属性。

其结构例如以下:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

constantvalue_index

1

能够看出ConstantValue属性是一个定长属性,当中attribute_length的值固定为0x00000002,constantvalue_index为一常量池字面量类型常量索引(Class文件格式的常量类型中仅仅有与基本类型和字符串类型相相应的字面量常量,所以ConstantValue属性仅仅支持基本类型和字符串类型)

对非static类型变量(实例变量。如:int a = 123;)的赋值是在实例构造器<init>方法中进行的

对类变量(如:static int a = 123;)的赋值有2种选择,在类构造器<clinit>方法中或使用ConstantValue属性。当前Javac编译器的选择是:假设变量同一时候被static和final修饰(虚拟机规范仅仅要求有ConstantValue属性的字段必须设置ACC_STATIC标志,对finalkeyword的要求是Javac编译器自己增加的要求),而且该变量的数据类型为基本类型或字符串类型。就生成ConstantValue属性进行初始化;否则在类构造器<clinit>方法中进行初始化

Exceptions属性:列举出方法中可能抛出的受查异常(即方法描写叙述时throwskeyword后列出的异常),与Code属性平级,与Code属性包括的异常表不同,其结构为:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

number_of_exceptions

1

u2

exception_index_table

number_of_exceptions

 

 

 

number_of_exceptions表示可能抛出number_of_exceptions种受查异常

exception_index_table为异常索引集合,一组u2类型exception_index的集合,每个exception_index为一个指向常量池中一CONSTANT_Class_info型常量的索引,代表该受查异常的类型

InnerClasses属性:该属性用于记录内部类和宿主类之间的关系。

假设一个类中定义了内部类。编译器将会为这个类与这个类包括的内部类生成InnerClasses属性,结构为:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

number_of_classes

1

inner_classes_info

inner_classes

number_of_classes

inner_classes为内部类表集合。一组内部类表类型数据的集合,number_of_classes即为集合中内部类表类型数据的个数

每个内部类的信息都由一个inner_classes_info表来描写叙述,inner_classes_info表结构例如以下:

类型

名称

数量

u2

inner_class_info_index

1

u2

outer_class_info_index

1

u2

inner_name_index

1

u2

inner_name_access_flags

1

inner_class_info_index和outer_class_info_index指向常量池中CONSTANT_Class_info类型常量索引,该CONSTANT_Class_info类型常量指向常量池中CONSTANT_Utf8_info类型常量。分别为内部类的全限定名和宿主类的全限定名

inner_name_index指向常量池中CONSTANT_Utf8_info类型常量的索引。为内部类名称,假设为匿名内部类。则该值为0

inner_name_access_flags类似于access_flags。是内部类的訪问标志

标志名称

标志值

含义

ACC_PUBLIC

0x0001

内部类是否为public

ACC_PRIVATE

0x0002

内部类是否为private

ACC_PROTECTED

0x0004

内部类是否为protected

ACC_STATIC

0x0008

内部类是否为static

ACC_FINAL

0x0010

内部类是否为final

ACC_INTERFACE

0x0020

内部类是否为一个接口

ACC_ABSTRACT

0x0400

内部类是否为abstract

ACC_SYNTHETIC

0x1000

内部类是否为编译器自己主动产生

ACC_ANNOTATION

0x4000

内部类是否是一个注解

ACC_ENUM

0x4000

内部类是否是一个枚举

LineNumberTale属性:用于描写叙述Java源代码的行号与字节码行号之间的相应关系,非执行时必需属性。会默认生成至Class文件里,能够使用Javac的-g:none或-g:lines关闭或要求生成该项属性信息,其结构例如以下:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

line_number_table_length

1

line_number_info

line_number_table

line_number_table_length

line_number_table是一组line_number_info类型数据的集合。其所包括的line_number_info类型数据的数量为line_number_table_length。line_number_info结构例如以下:

类型

名称

数量

说明

u2

start_pc

1

字节码行号

u2

line_number

1

Java源代码行号

不生成该属性的最大影响是:1,抛出异常时,堆栈将不会显示出错的行号。2。调试程序时无法依照源代码设置断点

LocalVariableTable属性:用于描写叙述栈帧中局部变量表中的变量与Java源代码中定义的变量之间的关系。非执行时必需属性,默认不会生成至Class文件里,能够使用Javac的-g:none或-g:vars关闭或要求生成该项属性信息。其结构例如以下:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

local_variable_table_length

1

local_variable_info

local_variable_table

local_variable_table_length

local_variable_table是一组local_variable_info类型数据的集合,其所包括的local_variable_info类型数据的数量为local_variable_table_length,local_variable_info结构例如以下:

类型

名称

数量

说明

u2

start_pc

1

局部变量的生命周期開始的字节码偏移量

u2

length

1

局部变量作用范围覆盖的长度

u2

name_index

1

指向常量池中CONSTANT_Utf8_info类型常量的索引,局部变量名称

u2

descriptor_index

1

指向常量池中CONSTANT_Utf8_info类型常量的索引。局部变量描写叙述符

u2

index

1

局部变量在栈帧局部变量表中Slot的位置,假设这个变量的数据类型为64位类型(long或double),

它占用的Slot为index和index+1这2个位置

start_pc + length即为该局部变量在字节码中的作用域范围

不生成该属性的最大影响是:1,当其它人引用这种方法时,全部的參数名称都将丢失,IDE可能会使用诸如arg0、arg1之类的占位符取代原有的參数名称,对代码执行无影响,会给代码的编写带来不便。2,调试时调试器无法依据參数名称从执行上下文中获取參数值

SourceFile属性:用于记录生成这个Class文件的源代码文件名,为可选项,能够使用Javac的-g:none或-g:source关闭或要求生成该项属性信息,其结构例如以下:

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

sourcefile_index

1

能够看出SourceFile属性是一个定长属性,sourcefile_index是指向常量池中一CONSTANT_Utf8_info类型常量的索引。常量的值为源代码文件的文件名称

对大多数文件,类名和文件名称是一致的,少数特殊类除外(如:内部类)。此时假设不生成这项属性。当抛出异常时,堆栈中将不会显示出错误代码所属的文件名称

Deprecated属性和Synthetic属性:这两个属性都属于标志类型的布尔属性。仅仅存在有和没有的差别。没有属性值的概念

Deprecated属性表示某个类、字段或方法已经被程序作者定为不再推荐使用。可在代码中使用@Deprecated注解进行设置

Synthetic属性表示该字段或方法不是由Java源代码直接产生的,而是由编译器自行加入的(当然也可设置訪问标志中的ACC_SYNTHETIC标志。全部由非用户代码产生的类、方法和字段都应当至少设置Synthetic属性和ACC_SYNTHETIC标志位中的一项,唯一的例外是实例构造器<init>和类构造器<clinit>方法)

这两项属性的结构为(当然attribute_length的值必须为0x00000000):

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

起始2位为0x0001。说明有一个类属性。接下来2位为属性的名称,0x0014,指向常量池中第20个常量:SourceFile。

接下来4位为0x00000002,说明属性体长度为2字节。最后2个字节为0x0014。指向常量池中第21个常量:Test.java。即这个Class文件的源代码文件名称为Test.java

 

PS:

1,全限定名:将类全名中的“.”替换为“/”,为了保证多个连续的全限定名之间不产生混淆,在最后加上“;”表示全限定名结束。

比如:"com.test.Test"类的全限定名为"com/test/Test;"

2,简单名称:没有类型和參数修饰的方法或字段名称。比如:"public void add(int a,int b){...}"该方法的简单名称为"add","int a = 123;"该字段的简单名称为"a"

3。描写叙述符:描写叙述字段的数据类型、方法的參数列表(包含数量、类型和顺序)和返回值。依据描写叙述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符表示,而对象类型则用字符L加对象全限定名表示

标识字符

含义

B

基本类型byte

C

基本类型char

D

基本类型double

F

基本类型float

I

基本类型int

J

基本类型long

S

基本类型short

Z

基本类型boolean

V

特殊类型void

L

对象类型,如:Ljava/lang/Object;

对于数组类型,每一维将使用一个前置的“[”字符来描写叙述,如:"int[]"将被记录为"[I","String[][]"将被记录为"[[Ljava/lang/String;"

用描写叙述符描写叙述方法时,依照先參数列表,后返回值的顺序描写叙述,參数列表依照參数的严格顺序放在一组"()"之内。如:方法"String getAll(int id,String name)"的描写叙述符为"(I,Ljava/lang/String;)Ljava/lang/String;"

4,Slot。虚拟机为局部变量分配内存所使用的最小单位,长度不超过32位的数据类型占用1个Slot,64位的数据类型(long和double)占用2个Slot

 

 

posted on 2018-05-06 16:47  zhulibin2012  阅读(480)  评论(0编辑  收藏  举报

导航