类加载器(Class Loader),顾名思义就是把类文件加载到虚拟机中,正常点的描述:“通过一个类的全限定名来获取描述该类的二进制字节流”。
类加载器是Java语言的一项创新,最开始是为了满足Java Applet的需求而设计的。类加载器目前在层次划分、程序热部署和代码加密等领域经常使用。
JVM为我们默认提供了系统类加载器(这里针对JDK1.8),包括:
Bootstrap ClassLoader(系统类加载器)
Extension ClassLoader(扩展类加载器)
Application ClassLoader(应用程序类加载器)
Customer ClassLoader(自定义加载器)
先看一张图
从这个图可能会认为类加载器的父子是继承关系,其实不然,类加载器的这种分层(优先级定义的父子)关系是为了安全的加载类库设计的,并没有继承关系,只是为了安全的加载类而设定的逻辑关系。
类加载器的分层既然是为了安全,那就应该有指定的范围,自定义加载器在加载类时会委托AppClassLoader去加载,而AppClassLoader又会委托ExtClassLoader去加载,ExtClassLoader又会委托BootstrapClassLoader去加载,这就是上图中的红色箭头过程。
加载的时候一定是先从Bootstrap加载器开始,如果检查要加载的类没有找到,则又会让子类ExtClassLoader去加载,依次类推,直到最后由自定义类加载器加载或报ClassNotFoundException错误。
那么每种类加载器负责的范围是什么呢?
sun.misc.Launcher类中源码对这三类加载器负责的范围做了说明
package sun.misc;
public class Launcher {
private static URLStreamHandlerFactory factory = new Launcher.Factory();
private static Launcher launcher = new Launcher();
① private static String bootClassPath = System.getProperty("sun.boot.class.path");
private ClassLoader loader;
private static URLStreamHandler fileHandler;
...
...
...
static class AppClassLoader extends URLClassLoader {
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
② final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
...
...
...
public ExtClassLoader(File[] var1) throws IOException {
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
private static File[] getExtDirs() {
③ String var0 = System.getProperty("java.ext.dirs");
File[] var1;
if (var0 != null) {
StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
int var3 = var2.countTokens();
var1 = new File[var3];
for(int var4 = 0; var4 < var3; ++var4) {
var1[var4] = new File(var2.nextToken());
}
} else {
var1 = new File[0];
}
return var1;
}
注意上面①②③位置的标注,用本地的JDK验证下,结果刚好符合预期。
public class test{
public static void main(String[] args){
String classPath = System.getProperty("sun.boot.class.path");
System.out.println(classPath.replaceAll(";",System.lineSeparator()));
System.out.println("------------------------------------------");
String extPath = System.getProperty("java.ext.dirs");
System.out.println(extPath.replaceAll(";",System.lineSeparator()));
System.out.println("------------------------------------------");
String appPath = System.getProperty("java.class.path");
System.out.println(appPath.replaceAll(";",System.lineSeparator()));
}
}
下面举一个小例子进行加载器之间的关系说明
在非classpath目录下我编译了一个类myname.class,对应的JAVA源码如下
public class myname{
public myname(){
}
public void printClassName(){
System.out.println("myname class's ClassLoader is:"+this.getClass().getClassLoader());
System.out.println("=============");
}
}
下面自定义了一个类加载器
package com.example.demo;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MyClassLodder extends ClassLoader{
@Override
protected Class findClass(String name) throws ClassNotFoundException {
File f = new File("E:\\projs\\demo\\target\\classes\\com\\example\\demo\\",name.concat(".class"));
try {
FileInputStream fileInputStream = new FileInputStream(f);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int index = 0;
while((index=fileInputStream.read()) != -1){
byteArrayOutputStream.write(index);
}
byte[] bytes = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
fileInputStream.close();
return defineClass(name,bytes,0,bytes.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
MyClassLodder myClassLodder = new MyClassLodder();
Class clazz = myClassLodder.loadClass("myname");
String s = "printClassName";
Method method = clazz.getMethod(s);
Object o = clazz.newInstance() ;
method.invoke(o);
System.out.println("myClassLodder类的加载器是:"+myClassLodder.getClass().getClassLoader());
System.out.println("myClassLodder类的父加载器是:"+myClassLodder.getClass().getClassLoader().getParent());
System.out.println("myClassLodder类的父加载器的加载器是:"+myClassLodder.getParent().getClass().getClassLoader());
System.out.println("myClassLodder类的父加载器的父加载器的父加载器是:"+myClassLodder.getParent().getClass().getClassLoader().getParent());
}
}
打印结果如下:
myname class's ClassLoader is:com.example.demo.MyClassLodder@3cd1a2f1
=============
myClassLodder类的加载器是:sun.misc.Launcher$AppClassLoader@18b4aac2
myClassLodder类的父加载器是:sun.misc.Launcher$ExtClassLoader@49476842
myClassLodder类的父加载器的加载器是:null
Exception in thread "main" java.lang.NullPointerException
at com.example.demo.MyClassLodder.main(MyClassLodder.java:40)
Process finished with exit code 1
最后一行代码为什么报错呢,是因为最高级的BootstrapClassLoader是本地方法,是用C++写的,没有对应的父加载器。