类加载器(Class Loader)
在 Java 中,默认提供的三种类加载器,分别是 BootStrapClassLoader(启动类加载器)、ExtClassLoader(扩展类加载器)、AppClassLoader(应用程序类加载器)。
同时,我们也可以定义自己的类加载器 (CustomClassLoader),那么它的 parent 肯定就是 AppClassLoader(应用程序类加载器)了。类加载器的这种层次关系称为双亲委派模型(Parents Delegation Model)。
- BootStrapClassLoader(启动类加载器):它由 C++ 实现的,在 Java 程序中不能显式地获取到。它负责加载存放在
%JAVA_HOME%/jre/lib
、%JAVA_HOME%/jre/classes
以及 -Xbootclasspath 参数指定的路径中的类。 - ExtClassLoader(扩展类加载器):它是由sun.misc.Launcher$ExtClassLoader 实现,负责加载
%JAVA_HOME%/jre/lib/ext
路径下的所有classes
目录以及java.ext.dirs
系统变量指定的路径中的类库。 - AppClassLoader(应用程序类加载器):由sun.misc.Launcher$AppClassLoader 来实现,它负责加载用户类路径(ClassPath)中的类,开发者可以直接使用该类加载器。一般来说,开发者自定义的类就是由应用程序类加载器加载的。
分析
ExtClassLoader(扩展类加载器) 作为类加载器,但它也是一个Java类,是由 BootStrapClassLoader(启动类加载器)来加载的,所以,ExtClassLoader(扩展类加载器)的 parent 是 BootStrapClassLoader(启动类加载器)。但是由于 BootStrapClassLoader(启动类加载器)是 C++ 实现的,我们通过 ExtClassLoader.getParent
获取到的是 null。同样地,AppClassLoader(应用程序类加载器)是由 ExtClassLoader(扩展类加载器)加载,AppClassLoader(应用程序类加载器)的parent是 ExtClassLoader(扩展类加载器)。
类加载器加载类的隔离问题
每个类加载器都有一个自己的命名空间,用来标示它加载的类。
当一个类加载器加载一个类时,它会通过保存在命名空间里的**类全局限定名(Fully Qualified Class Name)**进行搜索,来检测这个类是否已经被加载了。
JVM
及 Dalvik
对类唯一的识别是 ClassLoader id
+ PackageName
+ ClassName
,所以一个运行程序中是有可能存在两个包名和类名完全一致的类的。并且如果这两个”类”不是由同一个类加载器加载的时,是无法将一个类的实例强转为另外一个类的,这就是类加载器隔离。
双亲委托 是类加载器类一致问题的一种解决方案,也是 Android
差价化开发和热修复的基础。
自定义类加载器
一般地,在ClassLoader
方法的loadClass
方法中已经给开发者实现了双亲委派模型,在自定义类加载器的时候,只需要复写findClass
方法即可。
public class CustomClassLoader extends ClassLoader {
private String root;
public CustomClassLoader(String root) {
this.root = root;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] loadClassData(String name) {
String fileName = root + File.separatorChar
+ name.replace('.', File.separatorChar)
+ ".class";
try {
InputStream ins = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length;
while ((length = ins.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
新建一个类com.xiao.U
,编译成class文件,放到桌面,来测试一下:
public class Test {
public static void main(String[] args) {
CustomClassLoader customClassLoader = new CustomClassLoader("C:\\Users\\PC\\Desktop");
try {
Class clazz = customClassLoader.loadClass("com.xiao.U");
Object o = clazz.newInstance();
System.out.println(o.getClass().getClassLoader());
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
}
打印结果:
CustomClassLoader@1540e19d
自定义类加载器在可以实现服务端的热部署,在移动端比如android也可以实现热更新。
Reference
- 深入理解JVM类加载机制 - https://juejin.im/post/5a1d5f286fb9a045132a7100
- Java虚拟机 —— 类的加载机制 - https://juejin.im/post/59c4dd9e5188257e876a1aee