1.概述
Java类加载器负责加载所有的类,系统会为所有被载入内存的类生成一个java.lang.Class实例。对于同一个类,一旦被加载如内存中,就不会被再次加载。JVM使用一个类的权限的类名和该类的加载器唯一地标识一个类。因此即使两个类的包名、类名完全相同,但是使用不同的类加载器加载,这两个类也会被认为是不同的。 当程序首次使用某个类时,如果该类还未被加载,则系统会通过以下三个步骤加载该类:
- 加载:查找和载入Class字节码文件
- 连接:执行校验、准备和解析三个步骤 2.1校验:检查载入的Class文件的正确性,并和其他类协调一致 2.2准备:为类的静态变量分配内存,并设置默认初始值 2.3解析:将类的符号引用转换成直接引用
- 初始化:对类的静态变量、静态代码块执行初始化工作
有时,也将上述三个步骤统称为类的加载。
2.类加载器的层次结构
当JVM启动时,会形成由三个类加载器组成的初始化类加载器层次结构:
- Bootstrap CLassLoader:根类加载器,也称为引导类加载器,负责加载Java的核心类,如JRE目标下的tr.jar、charsets.jar等。根类加载器并不是ClassLoader的子类,它采用C 编写,在Java程序中无法获得。
- Extension ClassLoader:扩展类加载器,它负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext)下的jar包。
- Application ClassLoader:应用类加载器,也称为System ClassLoader(系统类加载器),主要负责加载classpath路径下的jar包。
三个类加载器之间存在父子层级关系,即Bootstrap ClassLoader是Extension ClassLoader的父类加载器,Extension ClassLoader是Application ClassLoader的父类加载器。这里类加载器的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。 默认情况下,Java程序使用Application ClassLoader加载一个类。示例程序如下:
代码语言:javascript复制public class CLassLoaderTest {
public static void main(String[] args) {
//获取当前线程下的ClassLoader
ClassLoader current = Thread.currentThread().getContextClassLoader();
System.out.println("current:" current);
//获取父类加载器
System.out.println("parent:" current.getParent());
System.out.println("grandparent:" current.getParent().getParent());
}
}
输出结果:
代码语言:javascript复制current:sun.misc.Launcher$AppClassLoader@56e88e24
parent:sun.misc.Launcher$ExtClassLoader@3dcc0a0f
grandparent:null
由此可见,Java程序默认采用Application ClassLoader,Extension ClassLoader为Application ClassLoader的父类加载器。由于Bootstrap CLassLoader由C 编写,在程序中无法获取,所以返回null。
3.类加载机制
JVM采用如下三种类加载机制:
- 全盘委托:当一个类加载器负责加载某个类时,该类所依赖和引用的其他类也将由该类加载器负责加载,除非显式指定另一个类加载器。
- 父类委托:加载一个类时,首先会让其父类加载器进行加载,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载。这一机制可以保证类加载的安全性。
- 缓存机制:JVM会缓存所有已经加载过的类,当程序使用某个类时,会首先在缓存中搜寻该类,只有缓存中不存在时才会加载。这也是为什么修改了一个类后,只有重启JVM才会生效。
为了更好地说明JVM的类加载机制,对ClassLoader的loadClass()方法的源码分析如下:
代码语言:javascript复制protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//首先,检查该类是否已经被加载,如果已经加载过,则直接返回该Class(缓存机制)
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果该类未被加载,首先尝试获取父加载器,如果父加载器不为null,则使用父加载器进行加载(父类委托机制)
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//如果父加载器为空,说明当前加载器为ExtensionClassLoader 或 BootstrapCLassLoader,则使用根类加载器进行加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//如果使用根类加载器加载失败,则抛出 ClassNotFoundException
}
if (c == null) {
//如果父加载器未能成功加载,则使用当前的类加载器进行加载
long t1 = System.nanoTime();
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}