java类加载-简单总结

前言

这几天简单阅读了《深入理解Java虚拟机》的类加载章节,简单总结了一些内容,类加载器的部分之后会补充🖊

类的生命周期

加载→验证→准备→解析→初始化===>使用===>卸载

加载

(不要和类加载搞混)加载阶段虚拟机所完成的3件事

  1. 通过类名定义此类的二进制字节流
  2. 将静态结构存储结构转化进方法区的运行时数据结构
  3. 生成java.lang.Class对象,存入方法区,作为该类入口

验证

通过验证确保Class文件的字节流包含的信息符合当前虚拟机的要求,确保其不会威胁虚拟机自身

准备

正式为类分配方法区内存空间(不是指对象),并设置类变量初始值(0值)。(这里仅仅分配static类变量,实例变量在实例对象时加入堆)

解析

将常量池的符号引用替换为直接引用

初始化

这是类加载的最后一步,这个阶段才真正开始执行代码,执行类构造器<clinit> ()(由所有类变量赋值操作和静态语句块合并成)。

  • 类构造器的合并是按语句出现顺序排序的,静态语句块只能访问到定义在静态语句块之前的变量。(定义在其之后的变量只能赋值不能访问)如下所示
1
2
3
4
5
6
7
public class Test {
static {
i = 0; // 可以给变量赋值
System.out.println(i); // 这是错误的,不可以访问定义在static语句块之后的变量
}
static int i = 1;
}
  • 父类的类构造器一定会先执行完毕于子类的类构造器(也意味着其静态语句块先于子类的变量赋值操作)
  • 接口没有静态语句块,但还有变量初始化的赋值操作,也会生成\()方法,但其不需要先执行父接口的类构造器(因为接口只有真正引用到了父接口时,父接口才会初始化)

类加载的时机

类中有且仅有4种主动引用情况执行类初始化,其他情况(被动引用)都不会初始化类

  1. 遇到new、getstatic、putstatic、invokestatic这4个字节码指令
    • new:使用new关键字实例化对象。
    • getstatic:读引用一个类的静态字段。(已经在编译期把结果放入常量池的final静态字段除外)
    • putstatic:写引用一个类的静态字段。(已经在编译期把结果放入常量池的final静态字段除外)
    • invokestatic:调用一个类的静态方法。(已经在编译期把结果放入常量池的final静态常量除外)
  2. 初始化类时,若父类也未初始化,先初始化父类
    • 接口初始化:只有真正引用到了父接口时,才会初始化。
  3. 反射调用类
  4. 虚拟机启动时会先初始化包含main()方法的类

演示几个不会触发类初始化的被动引用示例:

下面有三个示例

  • 通过类引用final静态常量,因为final静态常量已经在编译期进入常量池,本质上不会调用到类,不触发初始化
  • 通过子类引用父类的静态字段,不会导致子类初始化
  • 数组声明不触发初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SuperClass {

static {
System.out.println("SuperClass init.");
}

// 引用value时,会初始化这个SuperClass类(这里的不是常量)
public static int value = 1;

// 1. 引用Final_Value时,不会初始化SuperClass
// 因为static final变量在编译期就已经放到了常量池里,本质上没直接引用到定义常量的类
public static final int Final_Value = 2;
}

// 子类
public class SonClass extends SuperClass {
static {
System.out.println("SonClass init.");
}
}
1
2
3
4
5
6
7
public class test1 {
public static void main(String [] args) {
// 1. 引用类的Final_Value时,不会触发初始化
// 因为static final变量在编译期就已经放到了常量池里,本质上没直接引用到定义常量的类
System.out.println(SuperClass.Final_Value);
}
}
1
2
3
4
5
6
public class test2 {
public static void main(String [] args) {
// 2. 通过子类引用父类的静态字段,不会导致子类的初始化!
System.out.println(SonClass.value);
}
}
1
2
3
4
5
6
public class test3 {
public static void main(String [] args) {
// 3. 通过数组定义类,很明显不会触发初始化过程
SuperClass[] sca = new SuperClass[10];
}
}

本文标题:java类加载-简单总结

文章作者:Aaron.H

发布时间:2018年08月02日 - 19:08

最后更新:2018年09月07日 - 08:09

原始链接:https://uncleaaron.github.io/Blog/Java/java类加载-简单总结/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。