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

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

二、各个字段详解

使用下面的类进行说明:

[java] view plain copy

  1. package com.test;
  2. public class Test {
  3. private int m;
  4. public int getM(){
  5. return m + 1;
  6. }
  7. }

编译后的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

  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

  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

  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. LocalVariableTable:
  13. Start  Length  Slot  Name   Signature
  14. 0      5      0    this       Lcom/test/Test;
  15. public int getM();
  16. Code:
  17. Stack=2, Locals=1, Args_size=1
  18. 0:   aload_0
  19. 1:   getfield        #18; //Field m:I
  20. 4:   iconst_1
  21. 5:   iadd
  22. 6:   ireturn
  23. LineNumberTable:
  24. line 7: 0
  25. LocalVariableTable:
  26. Start  Length  Slot  Name   Signature
  27. 0      7      0    this       Lcom/test/Test;
  28. }

8.属性表集合

在Class文件、属性表、方法表中都可以包含自己的属性表集合,用于描述某些场景的专有信息

与Class文件中其它数据项对长度、顺序、格式的严格要求不同,属性表集合不要求其中包含的属性表具有严格的顺序,并且只要属性的名称不与已有的属性名称重复,任何人实现的编译器可以向属性表中写入自己定义的属性信息。虚拟机在运行时会忽略不能识别的属性,为了能正确解析Class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的9项属性:


属性名称


使用位置


含义


Code


方法表


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


ConstantValue


字段表


final关键字定义的常量值


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属性:通知虚拟机自动为静态变量赋值,只有被static关键字修饰的变量(类变量)才可以使用这项属性。其结构如下:


类型


名称


数量


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标志,对final关键字的要求是Javac编译器自己加入的要求),并且该变量的数据类型为基本类型或字符串类型,就生成ConstantValue属性进行初始化;否则在类构造器<clinit>方法中进行初始化

Exceptions属性:列举出方法中可能抛出的受查异常(即方法描述时throws关键字后列出的异常),与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

原文地址:https://www.cnblogs.com/timlong/p/8143839.html

时间: 2024-10-23 21:59:23

Class文件内容解析的相关文章

文件内容解析

下面是我的第一个React Native程序,也是自己对该程序的文件内容解析一些理解,注释很清楚哟,相信你一看就懂!!!! /** * Sample React Native App * https://github.com/facebook/react-native * @flow */ // 1.引入组件 import React, { Component } from 'react'; import { AppRegistry, // 注册 StyleSheet, // 样式 Text,

Python 之 文件内容解析(1)

说明: 有一个文件joke.txt,其内容如下: 老师:波涛汹涌的偏旁都是三点水.小明来举个相似的例子吧:小明:馅饼馄饨?老师:换一个吧.小明:玩玻璃球.老师:不好.小明:...没法洗澡?老师:有本事来一个六个字的?小明:哦吗咪吗咪哄.老师:来个一百字的!小明:哈哈哈哈哈哈哈哈...老师:滚出去!!! 将其以如下方式输出: 老师 说: 波涛汹涌的偏旁都是三点水.小明来举个相似的例子吧:小明 说: 馅饼馄饨?老师 说: 换一个吧.小明 说: 玩玻璃球.老师 说: 不好.小明 说: ...没法洗澡?

JVM系列文章(三):Class文件内容解析

作为一个程序员,仅仅知道怎么用是远远不够的.起码,你需要知道为什么可以这么用,即我们所谓底层的东西. 那到底什么是底层呢?我觉得这不能一概而论.以我现在的知识水平而言:对于Web开发者,TCP/IP.HTTP等等协议可能就是底层:对于C.C++程序员,内存.指针等等可能就是底层的东西.那对于Java开发者,你的Java代码运行所在的JVM可能就是你所需要去了解.理解的东西. 我会在接下来的一段时间,和读者您一起去学习JVM,所有内容均参考自<深入理解Java虚拟机:JVM高级特性与最佳实践>(

filebeat相关registry文件内容解析

filebeat的registry文件中存放的是被采集的所有日志的相关信息. linux中registry中一条日志记录的内容如下 {"source":"/var/log/messages","offset":5912,"FileStateOS":{"inode":38382035,"device":64768},"timestamp":"2017-03-1

Java解析文件内容

本文主要实现对.chk文件的解析,将其内容读出来,存入到一个Map中,文件内容实例为: A0500220140828.CHK A05002 |34622511 |373532879 |3 识别分隔符|,代码如下所示: 1 package com.src.factory; 2 3 import java.io.BufferedReader; 4 import java.io.File; 5 import java.io.FileReader; 6 import java.io.IOExceptio

PHP如何正确读取文件内容?解析

PHP 读取文件的多种方法,一起来看看吧. 处理诸如 PHP 之类的现代编程语言的乐趣之一就是有大量的选项可用.PHP 可以轻松地赢得 Perl 的座右铭“There's more than one way to do it”(并非只有一种方法可做这件事),尤其是在文件处理上.但是在这么多可用的选项中,哪一种是完成作业的最佳工具?当然,实际答案取决于解析文件的目标,因此值得花时间探究所有选项. 回页首 传统的 fopen 方法 fopen 方法可能是以前的 C 和 C++ 程序员最熟悉的,因为如

通过上传的APK文件,解析APK文件内容,获取应用权限包名等

通过上传的APK文件,解析APK文件内容,获取应用权限包名等 1工具导入: 下载aapt.exe反编译执行程序,放入Java工程资源文件夹下(具体路径自己可定义) 2编写ApkUtil类,获取apk文件信息 public class ApkUtil { public static final String VERSION_CODE = "versionCode"; public static final String VERSION_NAME = "versionName&q

深入学习python解析并读取PDF文件内容的方法

这篇文章主要学习了python解析并读取PDF文件内容的方法,包括对学习库的应用,python2.7和python3.6中python解析PDF文件内容库的更新,包括对pdfminer库的详细解释和应用.主要参考了一些已有的博客内容,代码. 主要思路是首先利用一个做项目的形式,描述所做的问题,运行环境,和需要安装的库,然后写代码,此代码是在python2.7中运行,然后写出在python3.6中运行的代码,并详细解释python2.7和python3.6中python库的一些不同之处,最后详细的

PHP读取Excel文件内容

PHP读取Excel文件内容 项目需要读取Excel的内容,从百度搜索了下,主要有两个选择,第一个是PHPExcelReader,另外一个是PHPExcel. PHPExcelReader比较轻量级,仅支持Excel的读取,实际上就是一个Reader.但是可惜的是不能够支持Excel 2007的格式(.xlsx). PHPExcel比较强大,能够将内存中的数据输出成Excel文件,同时还能够对Excel做各种操作,下面主要介绍下如何使用PHPExcel进行Excel 2007格式(.xlsx)文