第6章 类文件结构

时间:2021-11-29 10:19:34

概述:

类文件就是二进制和源码的中介

其实java可以被编译成.class文件、JRubyGroovy等都可以编译成.class文件,然后都java虚拟机上运行

1、Class类文件的结构

采用是8位字节为基础单位的二进制流,没有空隙,按顺序,

Class 文件格式采用一种类似于C语言结构,分为无符号树和表

无符号数属于基本的数据类型,以u1 u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、或者按照UTF-8编码构成字符串值。

2、魔数与Class文件的版本

2.1 、头4个字节称为魔数(MagicNumber):是否能被虚拟机接受的class文件, Class魔数为 : 0xCAFEBABE

2.2、4个字节存储是Class文件版本号:第5和第6字节是次版本号(Minor Version) 第7和第8个字节是主版本号(Major Version)

java版本从45开始 JDK 1.1 支持版本号为 45.0~45.65535  JDK1.7 的主版本号为51.0

写一个java编译成class文件,用winhex打开

public class ShowTime

{
private int m;

public int inc(){
return m + 1;
}
}

字节码

第6章 类文件结构

2.3、常量池

紧接版本之后就是常量池入口

第一个为u2类型的数据,代表常量池容量计数值(constant_pool_count) 从1 开始的

0x0013 十进制为19,代表18个常量 索引从1~18

常量池放入两大类常量:字面量和符号引用

字面量:如文本字符串、被声明为final的常量值等,

符号引用: 类和接口的权限定名、字段的名称和描述符、方法的名称和描述符

常量池中的每一项常量都是一个表,共有11中结构各不相同的表结构数据,第一个是一个u1类型的标志位(tag, 取值为1至12,缺少标志为2的数据类型)代表是这个常量是那种常量类型

类型 标志 描述
CONSTANT_Utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整型字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整型字面量
CONSTANT_Double_info 6 双精度浮点型字面量
CONSTANT_Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的部分符号引用


第6章 类文件结构

第6章 类文件结构



在列为A表示 0x0A 代表十进制也就是CONSTANT_Methodref_info, 可以看到它将引用CONSTANT_Class_info,第四个常量 和 CONSTANT_NameAndType第15常量

第二个常量 0x09 ,即十进制9, 也就是CONSTANT_Fieldref_info, 可以它引用第3个CONSTANT_Class_info和第16(0x10)个CONSTANT_NameAndType

其余可以通过jdk自带工具算出:javap -verbose

D:\data\spittr>javap -verbose TestClass
Classfile /D:/data/spittr/TestClass.class
Last modified 2017-7-26; size 275 bytes
MD5 checksum 2549ac0e12d6f173f679851de1545d98
Compiled from "TestClass.java"
public class TestClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#16 // TestClass.m:I
#3 = Class #17 // TestClass
#4 = Class #18 // java/lang/Object
#5 = Utf8 m
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 inc
#12 = Utf8 ()I
#13 = Utf8 SourceFile
#14 = Utf8 TestClass.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = NameAndType #5:#6 // m:I
#17 = Utf8 TestClass
#18 = Utf8 java/lang/Object
{
public TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0

可以看出,咱们推算是正确的。

2.4、访问标志

常量池结束紧接着的2个字节代表访问标志(access_flags

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public类型
ACC_FINAL 0x0010 是否被声明为final,只有类可设置
ACC_SUPER 0x0020 是否允许使用invokespecial字节码指令,JDK1.2之后编译出来的类的这个标志为true
ACC_INTERFACE 0x0200 标志这个是一个接口
ACC_ABSTRACT 0x0400 是否为abstract类型,对于接口或抽象类来说,此标志值为true,其他值为false
ACC_SYNTHETIC 0x1000 标志这个类并非由用户产生的
ACC_ANNOTATION 0x2000 标识这个一个注解
ACC_ENUM 0x4000 标识这是一个枚举

根据上面编译可以找到access_flags对应位置

access_flags中一共有32个标志为可以使用,当前只定义了其中的8个,可以进行或运算进行组合

TestClass这个类被public关键之修饰但没有被声明为final和abstract,ACC_PUBLICACC_SUPER标志为true,其它为false

access_flags的值: 0x0001|0x0020 = 0x0021

第6章 类文件结构

2.5 类索引、父类索引与接口索引集合

类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interface)是一组u2类型的数据的集合,Class文件中由这三个数据来确定这个类继承关系,类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类全限定名,由于java语言不允许多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的java类都有父类,因此除了java.lang.Object外,所有java类的父类索引都不为0.

2.6、字段表集合

字段包括了类级变量或实例级变量,但是不包括在方法内部声明的变量。

包含的信息:字段的作用域(public privateprotected)类级别还是实例级变量(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

字段访问标志

字段访问标志
标志名称 标志值 含义
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

ACC_PUBLICACC_PRIVATEACC_PROTECTED不能同时选择

ACC_FINAL ACC_VOLATILE 不能同时选择

接口字段必须有ACC_PUBLICACC_STATICACC_FINAL

name_index:简单名称

descriptor_index:方法的描述符

简单名称:没有类型和参数修饰的方法或字段名称 ,这个类中的inc()方法和字段m 的简单名称分别为inc 和m

描述字段:用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值

标志字符 含义
B 基本类型byte
C 基本类型char
D 基本类型double
F 基本类型float
I 基本类型int
J 基本类型long
S 基本类型short
Z 基本类型boolean
V 特殊类型void
L 对象类型 如 Ljava/lang/Object

java.lang.String[][] 二维数组 被记录为"[[Ljava/lang/String;"    int[]  被记录为“[I”

用于描述符来描述方法时,按照先参数列表,后返回值得顺序描述,参数列表按照参数严格顺序放在一组小括号之内。例如void inc() 描述符为 “()V”

方法java.lang.String.toString()的描述符为 "()Ljava/lang/String", 方法 int indexOf(char[] source,int sourceOffset, int sourceCount,char[] target, int targetOffset,int targetCount, int fromIndex) 的描述符为“([CII[CIII)I”

2.7 方法表集合

方法表的结构与字段表结构一样

字段访问标志
标志名称 标志值 含义
ACC_PUBLIC 0x0001 方法是否public
ACC_PRIVATE 0x0002 方法是否private
ACC_PROTECTED 0x0004 方法是否protected
ACC_STATIC 0x0008 方法是否static
ACC_FINAL 0x0010 方法是否为final
ACC_SYNCHRONIZED 0x0040 方法是否sychronized
ACC_BRIDGE 0x0080 方法是否是由编译器产生的桥接方法
ACC_VARARGS 0x1000 方法是否接受不定参数
ACC_NATIVE 0x4000 方法是否为native
ACC_ABSTRACT 0x0080 方法是否为Abstract
ACC_STRICT 0x1000 方法是否为strictfp
ACC_SYNTHETIC 0x4000 方法是否有编译器自动产生

方法中代码存在于code 中

2.8属性表集合

不严格按照顺序了,只要不重名就可以了

虚拟机规范预定的属性
属性名称 使用位置 含义
Code 方法表 java代码编译成的字节码指令
ConstantValue 字段表 final关键字定义的常量值
Deprecated 类、方法表、字段表 被表明为deprecated的方法和字段
Exceptions 方法表 方法抛出的异常
InnerClasses 类文件 内部类列表
LineNumberTable Code 属性 Java源码的行号与字节码指定的对应关系
LocalVariableTable Code属性 方法的局部变量描述
SourceFile 类文件 源文件名称
Synthetic 类、方法表、字段表 标识方法或字段为编译器自动生成的

属性表结构
类型 名称 数量
u2 attribute_name_index 1
u2 attribute_length 1
u1 info attribute_length

2.8.1 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

attribute_name_index是一项指向CONSTANT_Utf8_info类型常量的引用,常量值固定为“Code”,它代表了该属性的名称

attribute_length指示的属性值的长度,由于属性名称索引和属性长度一共占6个字节,所有最后减去6个字节

max_stack代表了操作数栈深度的最大值。

max_locals代表了局部变量表所需的存储空间。单位是Slot 32位用一个Slot 64位用两个Slot

code_length 和code用来存储java源程序编译后生成的字节码指令

code_length 虽然是u4类型的长度值 理论上达到2的32次方-1 ,实际上限制一个方法不允许超过65535条,也就是方法太长,java虚拟机拒绝编译

异常表

属性表结构
类型 名称 数量
u2 start_pc 1
u2 end_pc 1
u2 handler_pc 1
u2 catch_type 1
JVM虚拟机基于栈模型

2.8.2 Exceptions属性

Exceptions属性的作用是列出方法可能抛出的检查异常(Checked Exceptions)也就是方法描述时在throws关键字后面列举的异常。

属性表结构
类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 number_of_exceptions 1
u2 exception_index_table number_of_exception

2.8.3、LineNumberTable属性

描述java源码行号与字节码行号(字节码的偏移量)之间的对应的关系,它并不是运行时必须的属性,但是默认会生成Class文件之中,可以在javac带参数-g:none或-g:lines选项来取消或要求生成这项信息,如果不生成,抛出异常不会指定行号,也就不可以设置断点进行调试

LineNumberTable属性结构
类型 名称 数量
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

2.8.4 、LocalVariableTable属性

描述栈帧中局部变量表中的变量与java源码中定义变量之间的关系

默认也不会生成Class文件之中,可以通过-g:none-g:vars 设置 ,调试的时候的参数变量没有值

LocalVariableTable属性结构
类型 名称 数量
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_info项目结构
类型 名称 数量
u2 start_pc 1
u2 length 1
u2 name_index 1
u2 descriptor_index 1
u2 index 1

2.8.5/ SourceFile属性

用于记录生成这个Class文件的源码文件名称,这个属性可选的 通过 -g:none-g:source关闭或要求生成这项信息。内部类例外

SourceFile属性结构
类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 sourcefile_index 1


2.8.6、ConstantValue属性

用于通知虚拟机自动为静态变量赋值。

ConstantValue属性结构
类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 constantvalue_index 1

2.8.7 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_info表结构
类型 名称 数量
u2 inner_class_info_index 1
u2 outer_class_info_index 1
u2 inner_name_index 1
u2 inner_class_access_flags 1

inner_class_access_flags是内部类的访问标志

inner_class_access_flags标志
标志名称 标志值 含义
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_ABSTRACT 0x0400 内部类是否为abstract
ACC_SYNTHETIC 0x1000 内部类是否并非用户代码产生的
ACC_ANNOTATION 0x2000 内部类是否一个注解
ACC_ENUM 0x4000 内部类是否是一个枚举

总算勉强写完了。。。。。。