类结构和类加载

1. 类文件结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ClassFile {
u4 magic; //Class 文件的标志
u2 minor_version;//Class 的小版本号
u2 major_version;//Class 的大版本号
u2 constant_pool_count;//常量池的数量
cp_info constant_pool[constant_pool_count-1];//常量池
u2 access_flags;//Class 的访问标记
u2 this_class;//当前类
u2 super_class;//父类
u2 interfaces_count;//接口
u2 interfaces[interfaces_count];//一个类可以实现多个接口
u2 fields_count;//Class 文件的字段属性
field_info fields[fields_count];//一个类会可以有多个字段
u2 methods_count;//Class 文件的方法数量
method_info methods[methods_count];//一个类可以有个多个方法
u2 attributes_count;//此类的属性表中的属性数
attribute_info attributes[attributes_count];//属性表集合
}

1.1 魔数(amgic)

唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。即进行类型识别。使用魔数而不使用文件拓展名是为了安全考虑。

1
u4             magic; //Class 文件的标志

1.2 版本号(minor_version & major_version)

高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件。

1
2
u2             minor_version;//Class 的小版本号
u2 major_version;//Class 的大版本号

1.3 常量池

1
2
u2             constant_pool_count;//常量池的数量
cp_info constant_pool[constant_pool_count-1];//常量池

constant_pool_count表示常量池的容量,索引从1开始,到他的数量-1.第0项常量表示不引用任何常量,默认为空。

0x0016:表示为十进制是22,表示有21个常量,索引从1-21
主要存放两类常量:字面量和符号引用
每一项常量都是一个表,表开始是u1类型的标志位。

1.4 访问标志

识别类或接口层次的访问消息。

1
u2             access_flags;//Class 的访问标记

1.5 类索引,父类索引和接口索引集合

当前类要设置全限名,所有的都有父类,除了java.lang.Object,接口可以多实现。

1
2
3
4
u2             this_class;//当前类
u2 super_class;//父类
u2 interfaces_count;//接口
u2 interfaces[interfaces_count];//一个类可以实现多个接口

1.6 字段表集合

1
2
u2             fields_count;//Class 文件的字段属性
field_info fields[fields_count];//一个类会可以有多个字段

字段表格式:

  • access_flags: 字段的作用域(public ,private,protected修饰符),是实例变量还是类变量(static修饰符),可否被序列化(transient 修饰符),可变性(final),可见性(volatile 修饰符,是否强制从主内存读写)。
  • name_index: 对常量池的引用,表示的字段的名称;
  • descriptor_index: 对常量池的引用,表示字段和方法的描述符;
  • attributes_count: 一个字段还会拥有一些额外的属性,attributes_count 存放属性的个数;
  • attributes[attributes_count]: 存放具体属性具体内容。
    字段表标志位:

1.7 方法表集合

1
2
u2             methods_count;//Class 文件的方法数量
method_info methods[methods_count];//一个类可以有个多个方法

方法表结构:

方法表标志位:

2. 类加载机制

类的生命周期:

类加载的时机:

  • 创建类的实例,也就是new一个对象。
  • 访问类的静态方法或者静态变量(包含静态变量赋值)。
  • 使用Class.forName()反射类。
  • 子类初始化的时候。
  • JVM启动时标明的启动类。

2.1 加载

  1. 通过一个类的全限定名来获取定义此类的二进制字节流。
  2. 将这个流的静态存储结构转换为方法区运行时数据结构。
  3. 生成一个Class对象
    jvm是懒加载,所以只有使用到类时才会加载,例如调用类的main()方法,new对象等等 ,主类在运行过程中如果使用到其它类,会逐步加载这些类。

2.2 验证

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

2.3 准备

为类中定义的变量(被static修饰过的变量)分配内存并设置类变量初始值。此阶段不包含实例变量的赋值。

2.4 解析

将符号引用转换为直接引用。

  • 符号引用: 描述对象,包括如下三种:
    类和接口的全限定名
    字段的名称和描述符
    方法的名称和描述符
  • 直接引用:
    变量有一个内存地址来标识,如果我们用一个指针指向这个内存地址,这个指针就是直接引用。
    等我们需要用到这个变量的时候,就可以直接通过指针指向的地址找到。
    而我们在加载类的时候,解析代码并指向内存某个地址,然后将符号引用 obj和这个内存地址进行映射的过程,就是解析这个步骤要做的事,也叫做符号引用转换为直接引用。

2.5 初始化

(1)对类的静态变量初始化为指定的值
int initData = 666
(2)执行静态代码块
<clinit>()

3. 类加载器

3.1 类加载器的种类

  • 启动类加载器(Bootstrap ClassLoader):负责加载Java类的核心类(<JAVA_HOME>\lib目录下,能被-Xbootclasspath参数所指定路径存放的),是用C++代码实现的,无法被java代码直接引用。
  • 扩展类加载器(Extensions ClassLoader):负责加载JRE的扩展目录lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为Null。
  • 应用程序类加载器(Application ClassLoader): 负责加载用户类路径 classpath 上所有的 jar 包和 .class 文件。

3.2 双亲委派模型

工作过程:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的夹杂请求最终都应该传送到顶层的启动类加载器中,只有父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
简而言之:自下而上请求,自上而下加载
优势:
父类加载器成功加载则返回,子类加载器不会再加载,防止了重复加载。
防止核心API库被随意篡改。比如有一个要加载java.lang.Integer类的请求,通过双亲委派进制加载传递到启动类加载器,在在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,可以防止核心API被随意篡改。