一、class文件

本文详细介绍了JavaClass文件的结构,包括魔数、版本号、常量池、访问控制标志、类索引、父类索引、接口索引、字段表集合、方法表集合以及属性表集合。特别强调了常量池的作用和内容,以及方法和字段的访问控制标志。此外,还讨论了Code、LineNumberTable和LocalVariableTable等重要属性。
摘要由CSDN通过智能技术生成

目录

概述:

解析过程:

一、魔数:

二、class文件版本号(次版本号和主版本号):

三、常量池(常量池个数和常量池):

四、访问控制

五、类索引、父类索引与接口索引集合(this_class、super_class 、interfaces_count 和 interfaces)

六、字段表集合(fields_count 和 fields)例子有误差,后续复习的时候给补上去

七、方法表集合(methods_count 和 methods)例子有误差,后续复习的时候给补上去

八、属性表集合(attributes_count 和 attributes)

辅助一些说明:

问题:


概述:

class文件:二进制流文件,主要有两种数据类型。

问题1: 

是不是只有java编译器才能完成java到class文件的编译过程?

答案:不是,其他文件被编译后也可以生成class文件,比如groovy文件也可以被编译成class文件。

知识点1:

两种数据类型:

1)无符号数:u1,u2,u4,u8分别代表一个字节,两个字节,四个字节,八个字节。每个字节代表了一个数字,就是一个无符号数。

 2)表:以“_info”结尾,由多个符号数和其他表结构构成的符合数据类型。

解析过程:

有一个格式表:分为好些个字段

文件解析:

表格一:

英文名

中文名

类型

个数

magic

魔数

u4

1

minor_version

次版本号

u2

1

major_version

主版本号

u2

1

constant_pool_count

常量池个数

u2

1

constant_pool

常量池

cp_info

constant_pool_count-1

access_flags

访问标志

u2

1

this_class

类索引

u2

1

super_class

父类索引

u2

1

interfaces_count

接口计数器

u2

1

interfaces

接口索引集合

u2

interfaces_count

fields_count

字段计数器

u2

1

fields

字段表集合

fields_info

fields_count

methods_count

方法计数器

u2

1

methods

方法表集合

method_info

methods_count

attributes_count

属性计数器

u2

1

attributes

属性表集合

attribute_info

attributes_count

例子:

一、魔数:

它的唯一作用是用来确定该文件是否为一个能被虚拟机接受的class文件,说白了就是这个cafebabe(咖啡贝贝)代表是这个文件是一个class文件。

二、class文件版本号(次版本号和主版本号):

次版本号占用了两个字节:一般是0x0000

主版本号占用了两个字节:0x0034,转换为十进制就是52,使用了JDK1.8编译

(JDK1.6是50,JDK1.7是51,JDK1.8是52)

三、常量池(常量池个数和常量池):

由于常量池中常量的数目不是固定的,所以在常量池入口首先使用一个2字节长的无符号数constant_pool_count来代表常量池计数值。

      常量池个数:一般是算出来后具体个数后再减一,就是常量池的个数。

在例子中是00 2c 转换为十进制就是44,那么常量池的个数就是43。

常量池主要存放两大类常量:一个是字面量,一个是符号引用。

字面量:int a = 5; =号右边的数据就是字面量。这个有待考量

符号引用:包含了三类常量,一个是类和接口的全限定名,一个是字段的名称和描述符,一个是方法的名称和描述符。描述符指的就是private、protected、public。描述符有待考量。

常量池:表类型数据集合,即常量池中每一项常量都是一个表,共有11种结构各不相同的表结构数据。这11种表都有一个共同的特点,即均由一个u1类型的标志位开始,可以通过这个标志位来判断这个常量属于哪种常量类型,常量类型及其数据结构如下表所示:

JDK1.7后又加入三种常量类型:

知识点3:

init 和 clinit

init:实例化初始化方法

clinit:类和接口的初始化

问题二:类名应该有多长?

两个字节的长度,256个字母,全限定名。

四、访问控制

这个标志用于识别类或接口层次的访问信息。

      

这两个字节代表了访问控制。

ACC_PUBLIC 且 ACC_SUPER   0x0001|0x0020 = 0x0021

五、类索引、父类索引与接口索引集合(this_class、super_class 、interfaces_count 和 interfaces)

class文件由类索引、父类索引和接口索引集合来确定该类的集成关系

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类型的常量中的全限定名字符串

    

 这里的0x000d代表的是类索引,十进制就是13,从常量池中查13号常量,对应的就是  com/TestJvm

这里的0x000e代表的是父类索引,十进制就是14,从常量池中查14号常量,对应的就是java/lang/Object

这里的0x0000代表的是接口索引计数器,十进制就是0,索引interfaces接口索引集合为空,不会占用任何字节。如果有接口,那么根据表格一看出,一个接口占用了两个字节。

六、字段表集合(fields_count 和 fields)例子有误差,后续复习的时候给补上去

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

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

在Java中一般通过如下几项修饰一个字段:字段作用域(public、protected、private修饰符)、是类级别变量还是实例级别变量(static修饰符)、可变性(final修饰符)、并发可见性(volatile修饰符)、可序列化与否(transient修饰符)、字段数据类型(基本类型、对象、数组)以及字段名称。在字段表中,变量修饰符使用标志位表示,字段数据类型和字段名称则引用常量池中常量表示,字段表格式如下表所示:

        access_flags数据含义对应的表如下:

推出来之后就是下面的,和代码里定义的一样。

        int i

        int j

        static int num

字段表包含的固定数据项到descriptor_index结束,之后跟随一个属性表集合用于存储一些附加信息:attributes_count(属性计数器,占2字节,0x0000,所以该字段没有额外需要描述的信息)和attributes(属性表集合,详细说明见后面“八,属性表集合”)

字段表集合中不会列出从父类或父接口中继承的字段,但是可能列出原本Java代码之中不存在的字段,如:内部类为了保持对外部类的访问性,自动添加指向外部类实例的字段

Java语言中字段是不能重载的,2个字段无论数据类型、修饰符是否相同,都不能使用相同的名称;但是对于字节码,只要字段描述符不同,字段重名就是合法的

七、方法表集合(methods_count 和 methods)例子有误差,后续复习的时候给补上去

     

methods_count:方法表计数器,即方法表集合中的方法表数据个数。占2字节,其值为0x0005,即测试类中有5个方法。

methods:方法表集合,一组方法表类型数据的集合。方法表结构和字段表结构一样:

      access_flags数据含义对应的表如下:

第一个方法:(由编译器自动添加的默认构造方法

     

00 01  public 

      00 13  <init>

00 14  ()V

00 01  代表这个方法的属性表集合有一个属性

接下来的就是一个属性表集合 

00 15 转换十进制后从常量池中取出#21对应的数,就是code

00 00 00 26 表示code属性值字节长度为38个

00 02 该方法的操作数栈的深度最大值为2

00 01 该方法的局部变量占用空间为1

00 00 00 0a 代表的是10个字节

后面紧接着10个字节为 2a b7 00 01 2a 04 b5 00 02 b1,这10个字节为该方法编译后生成的字节码指令(可查询虚拟机字节码指令表)

接下来的两个字节00 00  表示code属性异常表集合为空。为空则代表异常属性表集合就是空的,不占用任何字节。

接下来的两个字节 00 01 表示code属性带有一个属性。

接下来的两个字节 00 16 转换为十进制后就是22,#22就是LineNumberTable

接下来的四个字节 00 00 00 0a 代表的是LineNumberTable属性占用的字节数。

接下来的两个字节 00 02 说明该line_number_table有两个line_number_info表,

下面的四个字节分别对应了00 00 和00 08 代表了一个对应关系,这是第一个line_number_info表

下面的四个字节分别对应了00 04 和00 09 代表了一个对应关系,这是第二个line_number_info表

到此第一个方法结束。

以此类推推出下一个方法。

八、属性表集合(attributes_count 和 attributes)

    在Class文件、字段表、属性表、方法表中都可以包含自己的属性表集合,用于描述某些场景的专有信息。与Class文件中其它数据项对长度、顺序、格式的严格要求不同,属性表集合不要求其中包含的属性表具有严格的顺序,并且只要属性的名称不与已有的属性名称重复,任何人实现的编译器可以向属性表中写入自己定义的属性信息

属性表结构(统称的)

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u1

info

attribute_length

其中attribute_name_index 属性名索引值,该值转换十进制,从常量池里找到相对应的属性。

attribute_length 代表的是属性的长度

info 这个代表的是具体的属性,每一个是一个字节,长度是attribute_length

其中根据attribute_name_index找出相对应的属性,有九种属性:

属性名称

使用位置

含义

Code

方法表

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

ConstantValue

字段表

final关键字定义的常量值

Deprecated

类文件、字段表、方法表

被声明为deprecated的方法和字段

Exceptions

方法表

方法抛出的异常

InnerClasses

类文件

内部类列表

LineNumberTale

Code属性

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

LocalVariableTable

Code属性

方法的局部变量描述

SourceFile

类文件

源文件名称

Synthetic

类文件、方法表、字段表

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

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

1、code属性

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

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将拒绝编译。

其中code属性里可以带exception的,这个exception和属性exceptions是两个概念,一个是方法里级别的,一个是方法级别的。其中exception_table的结构是:

类型

名称

说明

数量

u2

start_pc

开始处 包含

1

u2

end_pc,

结束处 不包含

1

u2

handler_pc

处理异常的代码的开始处

1

u2

catch_type

会被处理的异常类型,指向常量池的一个异常类,当为0的时候,表示处理任何异常

1

例子中:

如果第0行到第4行发生异常,且异常类型为Exception,则跳转到第9行执行;如果第0行到第4行发生其他异常,则跳转到第27行执行;如果跳转到第9行执行了,在执行第22行过程中,如果发生任何异常,则跳转到第27行。

2、LineNumberTale属性

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

line_number_table是一组line_number_info类型数据的集合,其所包含的line_number_info类型数据的数量为line_number_table_length,line_number_info结构如下:

 不生成该属性的最大影响是:1,抛出异常时,堆栈将不会显示出错的行号;2,调试程序时无法按照源码设置断点。所以一般都是带有这个属性的,否则debug调试的时候,根本确定不了出错代码的位置,就是返回位置,那也是字节码的位置。

3、LocalVariableTable属性

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

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

       

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

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

4、Exceptions属性

列举出方法中可能抛出的受查异常(即方法描述时throws关键字后列出的异常),与Code属性平级,与Code属性包含的异常表不同,其结构为:

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

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

辅助一些说明:

  1. 全限定名:将类全名中的“.”替换为“/”,为了保证多个连续的全限定名之间不产生混淆,在最后加上“;”表示全限定名结束。例如:"com.test.Test"类的全限定名为"com/test/Test;"
  2. 描述符:描述字段的数据类型、方法的参数列表(包括数量、类型和顺序)和返回值。根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符表示,而对象类型则用字符L加对象全限定名表示

         

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

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

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

           4、参考的文章有

JVM笔记5:Class文件结构_sean-zou的博客-CSDN博客

jvm原理(27)Java字节码方法表与属性表深度剖析_魔鬼_的博客-CSDN博客

https://www.cnblogs.com/GarfieldEr007/p/9943528.html

5、辅助工具:

IDEA:可以安装jclasslib插件;

javap -v  xxx.class;

    分析的工具:用notepad++中的hexEditor来看class文件的十六进制 8bit

    详见上传的文件

          6、常见的字节码

             JVM虚拟机字节码指令表:https://www.cnblogs.com/GarfieldEr007/p/9943528.html

问题:

  1. 通过文件字节码来分析try catch finally执行顺序,这是后续的工作
  2. 讲述常量池的时候,说常量池存储的字面量和描述符有待考量这个
  3. 一个类文件里面有具体的数值,或者去new对象,那么这个数字和对象在文件哪表现出来
  4. 经常的分析下class文件
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值