概述:
类文件就是二进制和源码的中介
其实java可以被编译成.class文件、JRuby,Groovy等都可以编译成.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;
}
}
字节码
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 | 字段或方法的部分符号引用 |
在列为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_PUBLIC、ACC_SUPER标志为true,其它为false
access_flags的值: 0x0001|0x0020 = 0x0021
2.5 类索引、父类索引与接口索引集合
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interface)是一组u2类型的数据的集合,Class文件中由这三个数据来确定这个类继承关系,类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类全限定名,由于java语言不允许多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的java类都有父类,因此除了java.lang.Object外,所有java类的父类索引都不为0.
2.6、字段表集合
字段包括了类级变量或实例级变量,但是不包括在方法内部声明的变量。
包含的信息:字段的作用域(public 、private、protected)类级别还是实例级变量(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_PUBLIC、ACC_PRIVATE、ACC_PROTECTED不能同时选择
ACC_FINAL 、 ACC_VOLATILE 不能同时选择
接口字段必须有ACC_PUBLIC、ACC_STATIC、ACC_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属性
类型 | 名称 | 数量 |
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 |
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选项来取消或要求生成这项信息,如果不生成,抛出异常不会指定行号,也就不可以设置断点进行调试
类型 | 名称 | 数量 |
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 设置 ,调试的时候的参数变量没有值
类型 | 名称 | 数量 |
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 |
类型 | 名称 | 数量 |
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关闭或要求生成这项信息。内部类例外
类型 | 名称 | 数量 |
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | sourcefile_index | 1 |
2.8.6、ConstantValue属性
用于通知虚拟机自动为静态变量赋值。
类型 | 名称 | 数量 |
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | constantvalue_index | 1 |
2.8.7 innerClasses属性
记录内部类与宿主类之前的关联
类型 | 名称 | 数量 |
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | number_of_classes | 1 |
inner_classes_info | inner_classes | number_of_classes |
类型 | 名称 | 数量 |
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是内部类的访问标志
标志名称 | 标志值 | 含义 |
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 | 内部类是否是一个枚举 |
总算勉强写完了。。。。。。