类的动态加载
类的动态加载
类加载器 ClassLoader
- 加载 Class 文件
以这段简单代码为例
1 | Student student = new Student(); |
我们知道,Student 本身其实是一个抽象类,是通过 new 这个操作,将其实例化的,类加载器做的便是这个工作
ClassLoader 的工作如图所示
几种加载器
- 引导类加载器
引导类加载器(BootstrapClassLoader),底层原生代码是 C++ 语言编写,属于 JVM 一部分
不继承java.lang.ClassLoader
类,也没有父加载器,主要负责加载核心 java 库(即 JVM 本身),存储在/jre/lib/rt.jar
目录当中。(同时处于安全考虑,BootstrapClassLoader
只加载包名为java
、javax
、sun
等开头的类)
- 扩展类加载器(ExtensionsClassLoader)
扩展类加载器(ExtensionsClassLoader) 由sun.misc.Launcher$ExtClassLoader
类实现,用来在/jre/lib/ext
或者java.ext.dirs
中指明的目录加载 java 的扩展库。Java虚拟机会提供一个扩展库目录,此加载器在目录里面查找并加载 java 类
- 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 | // 代码块的启动器 |
场景二:不实例化对象直接调用静态方法,会先调用类中的静态代码块,然后调用静态方法
1 | // 代码块的启动器 |
场景三:在对静态成员变量赋值前,会调用静态代码块
1 | // 代码块的启动器 |
场景四: 使用 class 获取类,并不会加载类,也就是什么也不会输出
1 | // 代码块的启动器 |
场景五、使用 forName 获取类 ,我们写三种forName
的方法调用
1 | // 代码块的启动器 |
Class.forName(className)
和Class.forName(className, true, ClassLoader.getSystemClassLoader())
等价,这两个方法都会调用类中的静态代码块,如果将第二个参数设置为false
,那么就不会调用静态代码块