类的动态加载

类加载器 ClassLoader

  • 加载 Class 文件

以这段简单代码为例

1
Student student = new Student();

我们知道,Student 本身其实是一个抽象类,是通过 new 这个操作,将其实例化的,类加载器做的便是这个工作

ClassLoader 的工作如图所示

image-20230309162153489
几种加载器
  1. 引导类加载器

引导类加载器(BootstrapClassLoader),底层原生代码是 C++ 语言编写,属于 JVM 一部分

不继承java.lang.ClassLoader类,也没有父加载器,主要负责加载核心 java 库(即 JVM 本身),存储在/jre/lib/rt.jar目录当中。(同时处于安全考虑,BootstrapClassLoader只加载包名为javajavaxsun等开头的类)

  1. 扩展类加载器(ExtensionsClassLoader)

扩展类加载器(ExtensionsClassLoader) 由sun.misc.Launcher$ExtClassLoader 类实现,用来在/jre/lib/ext或者java.ext.dirs中指明的目录加载 java 的扩展库。Java虚拟机会提供一个扩展库目录,此加载器在目录里面查找并加载 java 类

  1. App类加载器(AppClassLoader)

App类加载器/系统类加载器(AppClassLoader),由sun.misc.Launcher$AppClassLoader实现,一般通过通过(java.class.path或者Classpath环境变量)来加载 Java 类,也就是我们常说的 classpath 路径。通常我们是使用这个加载类来加载 Java 应用类,可以使用ClassLoader.getSystemClassLoader()来获取它

双亲委派模型

如果一个类加载器收到类加载请求,会首先把加载请求委派给父类加载器完成,每个层次的类加载器都是这样,最终所有的加载请求都传动到最根的启动类加载器来完成,如果父类加载器无法完成该加载请求(即自己加载的范围内找不到该类),子类加载器才会尝试自己加载

这样的双亲委派模型有个好处,就是所有的类都尽可能由顶层的类加载器加载保证了加载的类的唯一性。如果每个类都随机由不同的类加载器加载,则类的实现关系无法保证,对于保证Java程序的稳定运行意义重大

各场景下代码块加载顺序

这里的代码块主要指的是这四种

  • 静态代码块:static{}
  • 构造代码块:{}
  • 无参构造器:ClassName()
  • 有参构造器:ClassName(String name)

场景一:通过new关键字实例化的对象,先调用静态代码块,然后调用构造代码块,最后根据实例化方式不同,调用不同的构造器

1
2
3
4
5
6
// 代码块的启动器
public class Main {
public static void main(String[] args) {
Person person = new Person();
}
}

场景二:不实例化对象直接调用静态方法,会先调用类中的静态代码块,然后调用静态方法

1
2
3
4
5
6
// 代码块的启动器
public class Main {
public static void main(String[] args) {
Person.staticAction();
}
}

场景三:在对静态成员变量赋值前,会调用静态代码块

1
2
3
4
5
6
// 代码块的启动器
public class Main {
public static void main(String[] args) {
Person.staticVar = 1;
}
}

场景四: 使用 class 获取类,并不会加载类,也就是什么也不会输出

1
2
3
4
5
6
// 代码块的启动器
public class Main {
public static void main(String[] args) {
Class c = Person.class;
}
} //没有输出

场景五、使用 forName 获取类 ,我们写三种forName的方法调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 代码块的启动器
public class Main {
public static void main(String[] args) throws ClassNotFoundException{
Class.forName("src.DynamicClassLoader.Person");
}
} //输出静态代码块

// 代码块的启动器
public class Main {
public static void main(String[] args) throws ClassNotFoundException{
Class.forName("src.DynamicClassLoader.Person", true, ClassLoader.getSystemClassLoader());
}
} //输出静态代码块

// 代码块的启动器
public class Main {
public static void main(String[] args) throws ClassNotFoundException{
Class.forName("src.DynamicClassLoader.Person", false, ClassLoader.getSystemClassLoader());
}
} //没有输出

Class.forName(className)Class.forName(className, true, ClassLoader.getSystemClassLoader())等价,这两个方法都会调用类中的静态代码块,如果将第二个参数设置为false,那么就不会调用静态代码块