这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战
类文件结构
java源代码经过javac编译后生成.class文件,在经过jvm即可执行代码。 类文件结构中有两种数据类型:
- 无符号数: 如u1,u2,u3,u4、u8分别标示一个字节、两个字节、三个字节、四个字节、八个字节。无符号数可以表示数字、以及编码后的字符串。
- 表: 类似结构体的结构,由多个表后者无符号数构成。
.class文件结构
下面编写一个类文件查看
package com.sanjin.jvm;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author: jiangxch
* @date: 2021/4/11 上午4:30
*/
public class NewObjectTest {
public static void main(String[] args) throws InterruptedException {
Object object = new NewObjectTest();
}
}
编译为.class文件后,通过vscode查看十六进制。
按照class文件数据结构逐个分析:
magic u4
CA FE BA BE
四字节cafebabe魔数,用来判断文件是否是一个class文件,如果jpg,png都有自己的魔数。当然这个数字也能够伪造。
minor_version/mgjor_version
2字节副版本号+2字节主版本号,版本号用来标识当前.class可以被哪些版本的jvm解析,高版本的class文件无法被低版本的jvm解析。
minor_version=00 00=0
major_version=00 34=52(使用jdk8编译的)
constant_pool_count
constant_pool_count=00 13=19
constant_pool
常量池,常量池在.class占据了很大一部分空间,主要存储两种类型常亮:
- 字面量: 即Java中的常亮,如字符串,标示为final的常量值等
- 符号引用: 当通过import引入其它类,调用其它类的方法时,因为此时不不知道这些方法的跳转地址,所以在编译时使用符号引用代替,比如调用方法就使用方法签名标示,在JVM加载.class文件时,会从常量池获得对应的符号引用,再在类创建时或运行时解析、找到其真正的内存地址。
主要包含下面几种符号引用:
- 被模块导出或者开放的包,如
package com.xxx.jjj - 类和接口的全限定名,如
com.sanjin.HelloWorld - 字段的名称和描述符,如
private int a - 方法的名称和描述符,如
private static void sayHelo(int a) - 方法句柄和方法类型
- 动态调用点和动态常亮
constant_pool结构为cp_info表,共有19-1=18个cp_info表,每个cp_info表结构起始第一个字节是标志位,根据标志位的不同,每个常量池又代表不同的含义。
- Utf8_info: 字符串,如 "NewObjectTest.java"
- Integer_info: final static 修饰的整数。
- Class_info: 类的符号引用
access_flags
常量池之后即为类的访问标志
具备多个访问标志,进行或操作即可。
this_class/super_class/interfaces_count/interfaces
这三个东西用来确定类的继承关系。
this_class和super_class指向CONSTANT_Class_info常量池索引。用javap -v XXX.class编译后可以看到CONSTANT_Class_info即为Class的符号引用。
因为Java支持多实现,所以接口有数量定义。interfaces存储的也是CONSTANT_Class_info索引。
fields_count/fields
字段表。fileds_count标示字段数量。fields类型为表 field_info,其结构如下:
access_flags类型为:
字段的定义:
I标示int类型: 注意这几种类型不同:
method_count/methods
方法表示。 methods数据结构为表:method_info:
access_flags的类型有:
name_index标示方法的简单名称。
descriptor_index表示方法的入参以及返回值类型:
括号中为入参类型,括号右侧为返回值类型。
除了方法的定义,还有方法代码的定义,存放在随后的artribute_info属性表中,其表结构如下:
字段、方法、类可能都会有属性表。比如字段后面可能会跟着ConstantValue(当定义final 常亮就会存在)。方法后面则是方法对应的虚拟机字节码。
方法表对应的 attribute_name 是 "Code",Code表的结构为:
其中code即为虚拟机的字节码,也是JVM执行的代码。
总结
class文件结构非常枯燥,这篇也写的非常水,之前看书时跳过去了,但跟着书自己动手编译、翻译实践一遍,会收货很多,尤其是它协议的定制,是不是与socket协议的定义有着异曲同工之妙。