一.类加载器
类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远超类加载阶段。对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
我们可以通过使用 instanceof 关键字做对象所属关系判定,比如以下代码:
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
//获取到某个类的类名,比如com.coder404.demo4.ClassLoaderTest,截取拼接后称为 ClassLoaderTest.class
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
//获取字节流
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
//如果没有找到该文件,交给上级加载器进行加载
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
//将字节数组转换为类Class的实例。 在类可以使用之前,它必须被解析。
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj = myLoader.loadClass("com.coder404.demo4.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof com.coder404.demo4.ClassLoaderTest);
}
}
运行结果:
class com.coder404.demo4.ClassLoaderTest
false
以上代码我们通过创建一个自定义类加载器去加载了一个名为“com.coder404.demo4.ClassLoaderTest”的类,并实例化了这个对象。
两行输出结果中我们可以看到:第一行可以看到这个对象确实是com.coder404.demo4.ClassLoaderTest实例化出来的,但在第二行的输出中却发现这个对象与类做所属类型检查的时候返回了false。这是因为Java虚拟机中同时存在了两个ClassLoaderTest类,一个是由虚拟机的应用程序类加载器所加载的,另外一个是由我们自定义的类加载器加载的,虽然它们都来自同一个Class文件,但在Java虚拟机中仍然是两个互相独立的类,做对象所属类型检查时的结果自然为false。
二.Java的类加载器分为以下几种:
名称 | 加载哪的类 | 说明 |
---|---|---|
Bootstrap ClassLoader | JAVA_HOME/jre/lib | 无法直接访问 |
Extension ClassLoader | JAVA_HOME/jre/lib/ext | 上级为 Bootstrap,显示为 null |
Application ClassLoader | classpath | 上级为 Extension |
自定义类加载器 | 自定义 | 上级为 Application |
(1)启动类加载器
Bootstrap ClassLoader主要是负责加载机器上安装的Java目录下的核心类文件,也就是JDK安装目录下jre目录下的lib目录,里面存放了一些Java运行所需要的核心的类库。
当JVM启动的时候,会首先依托启动类加载器加载我们lib目录下的核心类库。
(2)扩展类加载器
Extension ClassLoader主要是加载jre目录下的lib目录下的ext中的文件,这里面的类用来支撑我们系统的运行。
(3)应用程序类加载器
Application ClassLoader该类加载器主要是加载“classpath”环境变量所指定的路径中的类,可以理解为加载我们自己写的Java代码
(4)自定义类加载器
除了以上三种以外,也可以自定义类加载器,根据具体的需求来加载对应的类。
三.双亲委派机制
所谓的双亲委派,就是指调用类加载器的 loadClass 方法时,查找类的规则
注意
这里的双亲,翻译为上级似乎更为合适,因为它们并没有继承关系
结构图如下:
基于这个亲子层级机构,就有一个双亲委派机制:先找“父亲”去加载,不行的话再由儿子来加载,这样的话可以避免多层级的加载器结构重复加载某些类。
类加载器中的核心方法 loadClass 源码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 检查该类是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 2. 有上级的话,委派上级 loadClass
c = parent.loadClass(name, false);
} else {
// 3. 如果没有上级了(ExtClassLoader),则委派 BootstrapClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime();
// 4. 每一层找不到,调用 findClass 方法(每个类加载器自己扩展)来加载
c = findClass(name);
// 5. 记录耗时
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
测试代码:
public class Demo4 {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> aClass = Demo4.class.getClassLoader()
.loadClass("com.coder404.demo4.ToLoaded");
System.out.println(aClass.getClassLoader());
}
}
执行流程为:
sun.misc.Launcher$AppClassLoader
//1 处, 开始查看已加载的类,结果没有sun.misc.Launcher$AppClassLoader` // 2 处,委派上级 `sun.misc.Launcher$ExtClassLoader.loadClass()
sun.misc.Launcher$ExtClassLoader
// 1 处,查看已加载的类,结果没有sun.misc.Launcher$ExtClassLoader
// 3 处,没有上级了,则委派BootstrapClassLoader
查找BootstrapClassLoader
是在 JAVA_HOME/jre/lib 下找 ToLoaded 这个类,显然没有sun.misc.Launcher$ExtClassLoader` // 4 处,调用自己的 findClass 方法,是在 JAVA_HOME/jre/lib/ext 下找 ToLoaded 这个类,显然没有,回到 `sun.misc.Launcher$AppClassLoader
的 // 2 处- 继续执行到
sun.misc.Launcher$AppClassLoader
// 4 处,调用它自己的 findClass 方法,在 classpath 下查找,找到了