关闭

【JVM系列】Java class文件解析1

标签: jvmjavaclass
442人阅读 评论(0) 收藏 举报
分类:

深入Java虚拟机读书笔记

一、关于class文件的几个点

1、每一个Java class文件都对应于一个Java类或者接口。java class文件中包含了java虚拟机所需知道的,关于类或接口的所有信息。

2、Java class文件是8位字节的二进制流,数据项按顺序存储在class文件中。占据多个字节空间的项按照高位在前的顺序分为几个连续的字节存放。

3、在class文件中,可变长度项的大小和长度位于其实际数据之前,这个特性使得class文件流可以从头到尾被顺序解析,首先读出项的大小,然后读出数据。

二、class文件的内容

(1)class文件中包含的数据类型
这里写图片描述

(2)class文件内容项

classFile中所包含的元素项,这些元素项都是class文件中所包含的。

这里写图片描述

使用数据结构来表示class文件的数据结构,如下:

ClassFile {
    u4  magic;
    u2  minor_version;
    u2  major_version;
    u2  constant_pool_count;
    cp_info constant_pool[constant_pool_count-1];
    u2  access_flages;
    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  atrributes_count;
    atrribute_info  attributes[atrributes_count];
}

下面分别介绍每个数据项:

(1)magic(魔数)

每个Java class文件的前4个字节被称为它的魔数:0xCAFEBABE。
作用:分辨Java class文件和非Java class文件。如果一个文件不是以0xCAFEBABE开头,那它就肯定不是Java class文件。

(2)minor_version和major_version

接着下面4个字节就是主、次版本号。
作用:版本号会随着java格式的变化而变化,所以版本号代表的就是java文件的格式规范,通常只有给定了主版本号和一系列的次版本号之后,java虚拟机才能读取class文件,如果class文件的版本号超过了java虚拟机所能处理的有效范围,java虚拟机将不会处理该class文件。

(3)constant_pool_count和constant_pool

java文件中,魔数和版本号后面跟着的就是常量池。
作用:常量池包含了与文件中类和接口相关的常量。它存储了诸如文字字符串、final变量值、类名和方法名的常量。

constant_pool是一个cp_info类型的数组列表。索引从 1 算起,0 默认是给 VM 自己用的,一般不显示 0 这一项,这也是count_pool_count 是常量池数组长度+1的原因。

cp_info类型:

cp_info { 
    u1 tag;
    u1 info[]; 
}

从上面可以看出,每个常量池入口都是从一个长度为一个字节的标志开始的,这个标志指出了列表中该位置的常量类型。一旦Java虚拟机获取并解析这个标志,java虚拟机就会知道在标志后的常量类型是什么。下面列出了常量池的标志。

这里写图片描述

CONSTANT_Utf8_info

它使用一种UTF-8格式的变体来存储一个常量字符串,它可以存储多种字符串:

  • 文字字符串,例如String对象
  • 被定义的类和接口的全限定名
  • 被定义的类的超类的全限定名
  • 被定义的类和接口的父接口的全限定名
  • 由类或者接口声明的任意字段和方法的简单名称和描述符
  • 任何引用的类和接口的全限定名
  • 任何引用的字段和方法的简单名称和描述符
  • 与属性相关的字符串

总而言之,它存储了四类信息类型:文字字符串、被定义的类或者接口的描述、对其他类或接口的符号引用以及属性相关的字符串。

CONSTANT_Utf8_info {
    u1 tag;//值为CONSTANT_Utf8_info(1)
    u2 length;//字节的长度
    u1 bytes[length]//内容
}

特殊字符串:

(1)全限定名
当常量池入口指向类或者接口时,它们给出类或者接口的全限定名,在class文件中,全限定名中的点用斜线取代了。
例如在class文件中,java.lang.Object的全限定名表示为java/lang/Object。

(2)简单名称
字段名和方法名以简单名称(非全限定名)形式出现在常量池入口中。例如,一个指向类java.lang.System所属字段java.io.PrintStrem out的常量池入口有一个形如“out”的字段名。

(3)描述符
字段的描述符给出了字段的类型,方法描述符给出了方法的返回值和方法参数的数量、类型以及顺序。

基本数据类型终结符:

这里写图片描述

数组数据类型:一维数组类型为[,二维数组类型位[[,依次类推。

类数据类型:前面需要加一个L,例如java.lang.Oject为Ljava/lang/Object。

字段描述符的例子:

int x;
long[][] y;
Object[] object;
Hashtable ht;
boolean[][][] is;

这里写图片描述

方法描述符的例子

int getSize() {return 2;}
String getString() {return "";}
public static void main(String[] args) {}
void setSize() {}
void setSize(long t, int l) {}
boolean getBoolean(boolean b, int i, String o, int l) {return true;}

这里写图片描述
这里写图片描述

值得注意的是,实例方法的方法描述符并没有包含作为第一个参数被传给所有实例方法的隐藏this参数。但所有调用实例方法的java虚拟机指令都会隐式传递this参数,另外this永远不会传递给类方法,因为类方法不会被对象调用。

CONSTANT_Integer_info
固定长度的CONSTANT_Integer_info用来存储常量为int类型值,它值存储int值,不存储符号引用。

CONSTANT_Integer_info {
    u1 tag;//值为CONSTANT_Integer_info(3)
    u4 bytes;//int值
}

CONSTANT_Float_info

固定长度CONSTANT_Float_info用来存储常量float类型的值,它只存储float类型值,不存储符号引用。

CONSTANT_Float_info {
    u1 tag;//值为CONSTANT_Float_info(4)
    u4 bytes;//float值
}

CONSTANT_Long_info

固定长度的CONSTANT_Long_info表用于存储long类型变量,它只存储long类型的值,不存储符号引用。

CONSTANT_Long_info {
    u1 tag;//值为CONSTANT_Float_info(5)
    u8 bytes;//long值
}

CONSTANT_Double_info

固定长度的CONSTANT_Double_info表用于存储double类型常量,它只存储double类型的值,不存储符号引用。

CONSTANT_Double_info {
    u1 tag;
    u8 bytes;
}

CONSTANT_Class_info

固定长度的CONSTANT_Class_info表使用符号引用来表示类或者接口,无论执行类、接口、字段,还是方法,所有的符号引用都包含一个CONSTANT_Class_info表。

CONSTANT_Class_info {
    u1 tag;//CONSTANT_Class(7)
    u2 name_index;//包含类或者接口全限定名的CONSTANT_Utf8_info表的索引
}

CONSTANT_String_info

固定长度的CONSTANT_String_info结构用来存储文字字符串值,该值也可以表示类为java.lang.String的实例,它只存储文字字符串值,不存储符号引用。

CONSTANT_String_info {
    u1 tag;//值为CONSTANT_String(8)
    u2 string_index;//包含文字字符串值的CONSTANT_Utf8_info结构的索引
}

CONSTANT_Fieldref_info
固定长度的CONSTANT_Fieldref_info指向字段的符号引用。

CONSTANT_Fieldref_info {
    u1 tag;//CONSTANT_Fieldref(9)
    u2 class_index;//该字段的类或者接口的CONSTANT_Class_info入口索引
    u2 name_and_type_index;//CONSTANT_NameAndType_info入口的索引,该入口提供了简单名称以及描述符
}

CONSTANT_Methodref_info

固定长度的CONSTANT_Methodref_info指向方法的符号引用。

CONSTANT_Methodref_info {
    u1 tag;//CONSTANT_Methodref(10)
    u2 class_index;//该方法的类或者接口的CONSTANT_Class_info入口索引
    u2 name_and_type_index;//CONSTANT_NameAndType_info入口的索引,该入口提供了简单名称以及描述符
}

CONSTANT_InterfaceMethodref_info

固定长度的CONSTANT_InterfaceMethodref_info使用符号引用来描述接口中声明的方法。

CONSTANT_InterfaceMethodref_info {
    u1 tag;//CONSTANT_InterfaceMethodref_info(11)
    u2 class_index;//该方法的接口的CONSTANT_Class_info入口索引
    u2 name_and_type_index;//CONSTANT_NameAndType_info入口的索引,该入口提供了简单名称以及描述符
}

CONSTANT_NameAndType_info

固定长度的CONSTANT_NameAndType_info包含了指向字段或者方法的符号引用。提供了所引用字段或者方法的简单名称和描述符的常量池入口

CONSTANT_NameAndType_info {
    u1 tag;//值为NameAndType_info(12)
    u2 name_index;//字段或者方法的名称,它时一个CONSTANT_Utf8_info入口的索引
    u2 descriptor_index;//字段或者方法的描述,它时一个CONSTANT_Utf8_info入口的索引
}

(4)access_flag

接着常量池后面的就是两个字节的access_flag。
作用:它展示了文件中定义的类或者接口的信息。它指明了文件定义的时类还是接口;指明了类或者接口的声明中使用了哪种修饰符;类和接口是抽象的还是公共的;类的类型还可以为final,final类不可能是抽象的,接口不能位final类型,这个标志位的具体定义如下:

这里写图片描述

(5)this_class

接着两个字节位this_class项,它是一个对常量池的索引。
作用:它表示的就是类名,它是一个对常量池的索引。在this_class位置的常量池入口必须为CONSTANT_Class_info表。下面我们来看看CONSTANT_Class_info是一个什么类型:

CONSTANT_Class_info {
u1 tag;
//tag 取值为 7,代表 CONSTANT_Class_info
u2 name_index;
//name_index 表示代表自己类名的字符串信息位于于常量池数组中哪一个,也就是索引
}

可以看到它的name_index也是指向常量池中的一个索引,对应的就是CONSTANT_Utf8_info类型的项。

CONSTANT_Utf8_info {
u1 tag;
u2 length;
//下面就是存储 UTF8 字符串的地方了
u1 bytes[length];
}

整个过程可以用如下图来表示:

这里写图片描述

(6)super_class

紧接着this_class后面的就是super_class项,它也是一个两个字节的常量池索引。
作用:表示该类的父类名称,它的索引过程跟上面this_class是一样的。对于Object类,super_class的值为0。

(7)interfaces_count和interfaces

它表示文件中,由这个类实现或者扩展的父接口的数量和名称。
作用:nterfaces是一个数组,它包含了对每个类或者接口直接实现的父接口的常量池索引。这个数组只容难哪些由该类implements或者extends的父接口。

(8)fields_count和fields

接着就是对这个类或者接口中所声明的字段的描述了。
作用:fields_count是类变量和实例变量的字段的数量总和。只有在文件中由类或者接口声明的字段才能在fields类表列出,在fields列表中不列出超类或者父接口继承而来的字段。

另外需要注意的是,fields可能包含java源文件中没有叙述的字段,这个时引用java编译器可能会在编译时向类或者接口添加字段。

fields是一个field_info类型的数组:

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

access_flages

声明字段时使用的修饰符存放在字段的access_flages项中。

这里写图片描述

1、只能用于ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED这三个标志中的一个。

2、ACC_FINAL和ACC_VOLATILE不能同时设置。

3、所有接口中声明必须有且只能由ACC_PUBLIC、ACC_STATIC、ACC_FINAL这三个标志。

name_index

它给出了字段简单名称的CONSTANT_Utf8_info的入口索引。

descriptor_index

它提高了给出字段描述符的CONSTANT_Utf8_info的入口索引。

attributes_count和attributes

attributes_count表示attribute_info的数量。

(9)methods_count和methods

接着对应的就是该类或者接口中所声明的方法的描述。
作用:methods_count是一个双字节长度的对于该类或者接口所声明的所有方法的数量,它只是该类或者接口显示定义的方法,不包含从超类或者父类继承的方法。methods_info表示的时详细的信息。

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

access_flages

这里写图片描述

其他跟上面基本一样

(10)attributes_count和attributes

它给出了在该文件中类或者接口所定义的属性的基本信息。
作用:attributes_count表示所有attributes_info的数量之和。每个attrubute_info的第一项是指向常量池中CONSTANT_Utf8_info的索引,表示该属性的名称。

attribute_info {//特别注意,这里描述的 attribute_info 结构体也是具体属性数据结构的通用表达
u2 attribute_name_index;
//attribute_info 的描述,指向常量池的字符串
u4 attribute_length; //具体的内容由 info 数组描述
u1 info[attribute_length];
}

每一种属性都遵循同样的可变长度attribute_info的一般格式。

Java虚拟机规范定义了9种属性。

这里写图片描述

Code属性

可变长度的Code_attribute表定义了方法的字节码序列和其他信息,在所有不是抽象或者本地方法的method_info信息中,都存在一个Code_attribute元素项。

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_info exception_table[exception_table_length];
    u2 attribute_count;
    attribute_info attributes[attribute_count];
}

attribute_name_index:给出包含字符串“Code”的CONSTANT_Utf8_info入口的常量池索引。

attribute_length:给出除去起始6个字节(attribute_name_index和attribute_length:)后,Code属性以字节为单位的长度。

max_stack:表示该方法操作数栈的最大长度

max_locals:给出方法的局部变量所需存储空间的长度。

code_length和code:给出了方法字节码的长度,字节码本身存放在code项中

exception_table_length和exception_table:exception_table是exception_info的列表,每个exception_info结构中描述了一个异常项。exception_table_length给出了exception_table中exception_info的数目。

exception_info {
    u2 start_pc;//代码数组起始到异常处理器起始的偏移量
    u2 end_pc;//代码数组起始到异常处理器结束后一个字节的偏移量
    u2 handler_pc//一条指令从代码数组起始处跳转到异常处理器的第一条指令的偏移量-如果抛出异常被捕获
    u2 catch_type;//异常处理器所捕获异常的类型的CONSTANT_Class_info入口的常量池索引
}

attributes_count和attributes:atrributes_count表示attribute_info的数目。该项中可以Java虚拟机规范所定义的两种属性:LineNumberTable和LocalVariableTable。

ConstantValue属性

固定长度的ConstantValue属性出现在field_info结构中。一个field_info结构中最多只可能出现一个ConstantValue属性。在包含ConstantValue属性的field_info结构的access_flag中必须设定ACC_STATIC标志,也可以设定为ACC_FINAL标志。当虚拟机初始化一个具有ConstantValue属性的字段时,它将一个常量值赋给这个字段。

ConstantValue_attribute {
    u2 attribute_name_index;//给出包含字符串“ConstantValue”的CONSTANT_Utf8_info入口的常量池索引
    u4 attribute_length;//这个值永远为2
    u2 constantvalue_index;//提供常量值入口的常量池索引
}

下面表示常量值属性的常量池入口类型

这里写图片描述

Deprecated属性

固定长度的Desprecated属性存在于field_info、method_info、ClassFile结构的attributes项中。它是一个可选的项。

Deprecated_attribute {
    u2 attribute_name_index;//给出包含“Deprecated”的CONSTANT_Utf8_info入口的常量池索引
    u4 attribute_length;//必须为0
}

Exceptions属性

可变长度的Exception属性列出了方法可能抛出的异常。Exceptions_attributes结构会出现在每一个可能抛出已检出异常方法的method_info结构中。

Exceptions_attribute {
    u2 attribute_name_index;//给出包含字符串“Exceptions”的CONSTANT_Utf8_info入口的常量池索引
    u4 attribute_length;//除去起始6个字节(attribute_name_index和attribute_length)后,Exceptions_attribute的长度
    u2 number_of_exception;
    u2 exception_index_table[number_of_exception];
    //列出了该方法可能抛出的所有已检出的异常
}

InnerClasses属性

可变长度的InnerClass属性ClassFile结构中。

InnerClasses_attribute {
    //给出包含字符串“InnerClasses”的CONSTANT_Utf8_info入口的常量池索引
    u2 attribute_name_index;
    //给出除去起始6个字节(attribute_name_index和attribute_length)后,InnerClasses_attribute的长度
    u4 attribute_length;
    u2 number_of_classes;
    //inner_classes_info类型的数组
    inner_classes_info classes[number_of_classes];
}
inner_class_info {
    u2 inner_class_info_index;
    u2 outer_class_info_index;
    u2 inner_name_index;
    u2 inner_class_access_flags;
}

LineNumberTable属性

可变长度的LineNumberTable属性建立了方法字节码偏移量和源代码行号之间的映射关系。它可能出现在Code_attribute结构的属性项中。

LineNumberTable_attribute {
    //给出了包含字符串“Line_Number_Table”的CONSTANT_Utf8_info入口的常量池索引
    u2 attribute_name_index;
    //给出除去起始6个字节(attribute_name_index和attribute_length)后LineNumberTable_attribute的长度
    u4 attribute_length;
    u2 line_number_table_length;
    line_number_info line_number_table[line_number_table_length];
}
line_number_info {
    // 给出了新行开始时代码数组的偏移量
    u2 start_pc;
    //从start_pc开始的行号
    u2 line_number;
}

LocalVariableTable_attribute

可变长度的LocalVariableTable_attribute属性建立了方法的栈帧中局部变量部分内容与源代码中局部变量名称和描述符之间的映射。它存在与Code_attribute结构的属性项中。

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

SourceFile属性

固定长度的SourceFile属性可能存在与ClassFile结构的属性项中,它是一个可选项,它提供了产生class文件的源代码文件的名称。

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

Synthetic属性

固定长度的Synthetic属性可能存放于field_info、method_info和ClassFile结构的attributes项中,它是一个可选项,它指明了编译器所产生的字段、方法或者类型。

Synthetic {
    u2 attribute_name_index;
    u4 attribute_length;
}
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:300878次
    • 积分:5671
    • 等级:
    • 排名:第4593名
    • 原创:240篇
    • 转载:28篇
    • 译文:22篇
    • 评论:41条
    成长轨迹
    目前研究方向:Android,IOS,React Native,机器学习
    • 2014年10月 转投Android
    • 2015年 7月 百度实习并尝试写博客
    • 2016年 11月 开始机器学习相关学习

    微信公众号:DroidMind(欢迎关注)