定义:类加载器
ClassLoader作用:把class加载到jvm,程序可正常运行。
jvm启动时,并不会一次性加载所有的class,而是选择动态选择加载(防止一次性加载太多,内存压力太大)。
Java语言自带的三个类加载器:
Bootstrap ClassLoader :
最顶层加载器,主要加载核心类库(%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等)。
Extention ClassLoader:
扩展类加载器(%JRE_HOME%\lib\ext目录下的jar包和class文件)
Appclass Loader:
也称为SystemAppClass ,加载当前应用的classpath的所有类
执行顺序:从上到下依次执行。
通过sun.misc.Launcher类的源码,可以的到相关信息:
1,Launcher初始化了ExtClassLoader和AppClassLoader。
2,Launcher中并没有看见BootstrapClassLoader,但通过System.getProperty("sun.boot.class.path")得到了字符串bootClassPath,这个应该就是BootstrapClassLoader加载的jar包路径。这个路径的内容全是jre目录下的jar与class文件
BootstrapClassLoader 测试
System.out.println(System.getProperty("sun.boot.class.path"));
得到:
C:\Program Files\Java\jre1.8.0_91\lib\resources.jar;
C:\Program Files\Java\jre1.8.0_91\lib\rt.jar;
C:\Program Files\Java\jre1.8.0_91\lib\sunrsasign.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jsse.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jce.jar;
C:\Program Files\Java\jre1.8.0_91\lib\charsets.jar;
C:\Program Files\Java\jre1.8.0_91\lib\jfr.jar;
C:\Program Files\Java\jre1.8.0_91\classes
ExtClassLoader 测试
System.out.println(System.getProperty("java.ext.dirs"));
得到:
C:\Program Files\Java\jre1.8.0_91\lib\ext;C:\Windows\Sun\Java\lib\ext
AppClassLoader 测试
System.out.println(System.getProperty("java.class.path"));
打印得到:
D:\workspace\ClassLoaderDemo\bin
每个类加载器都有一个父加载器
ClassLoader cl = Test.class.getClassLoader();
System.out.println("ClassLoader is:"+cl.toString());
System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());
得到:
ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742
AppClassLoader的父加载器是ExtClassLoader,
可以通过getParent()方法获取。
父加载器不是父类
static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}
ExtClassLoader和AppClassLoader同样继承自URLClassLoader。
getParent()方法在ClassLoader当中。
getParent分为两种情况:
1.如果构造方法当中有赋值指定,则parent为指定。
2.如果创建时没有指定,则通过Launcher.getClassLoader获取,则默认为AppClassLoader。
AppClassLoader的parent是ExtClassLoader,ExtClassLoader的parent是null.
Bootstrap ClassLoader是由C/C++编写,并不是一个java类,无法在java代码中获得引用。它没有父类也就是没有父容器,它可以作为ClassLoader的父加载器。
双亲委托:
双亲委托
一个类加载器加载class或resouces时,是通过委托模式。
首先判断是否已经加载成功,
如果么有(先不自己找),通过父加载器,然后递归下去,直到BootstrapClassLoader,
找到直接返回,
没有找到则一级级(ExtClassLoader->AppClassLoader-->...)返回直到找到对象。
- 一个AppClassLoader查找资源时,先看看缓存是否有,缓存有从缓存中获取,否则委托给父加载器。
- 递归,重复第1部的操作。
- 如果ExtClassLoader也没有加载过,则由Bootstrap ClassLoader出面,它首先查找缓存,如果没有找到的话,就去找自己的规定的路径下,也就是sun.mic.boot.class下面的路径。找到就返回,没有找到,让子加载器自己去找。
- Bootstrap ClassLoader如果没有查找成功,则ExtClassLoader自己在java.ext.dirs路径中去查找,查找成功就返回,查找不成功,再向下让子加载器找。
- ExtClassLoader查找不成功,AppClassLoader就自己查找,在java.class.path路径下查找。找到就返回。如果没有找到就让子类找,如果没有子类会怎么样?抛出各种异常。
加载详情图:
重要方法:
loadClass()
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检测是否已经加载
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//父加载器不为空则调用父加载器的loadClass
c = parent.loadClass(name, false);
} else {
//父加载器为空则调用Bootstrap Classloader
//这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//父加载器没有找到,则调用findclass
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//调用resolveClass(),生成最终的Class对象。
resolveClass(c);
}
return c;
}
}
上面的代码也解释了双亲委托机制。
自定义classLoader建议:覆盖findClass()方法,而不要直接改写loadClass()方法。
用途:
这些类加载器默认只能加载指定目录的jar或资源文件。当你想要突破内置加载器的限制时,可以玩出花样来。
如果要想动态加载某些磁盘的资源,或通过网络下载class后加载,这样就可以自定义classLoader去做这些事。
步骤:
1. 编写一个类继承自ClassLoader抽象类。
2. 复写它的findClass()方法。
3. 在findClass()方法中调用defineClass()。
defineClass()能将class二进制内容转换成Class对象,如果不符合要求的会抛出各种异常。
注意点:一个ClassLoader在创建时如果没有指定parent,则parent默认为AppClassLoader,这样就能够保证它能访问系统内置加载器加载成功的class文件。
常见用途:将一个class用特定规则加密,然后在自定义的ClassLoader进行解密后在程序中加载使用。只有在我们自定义的加载器里能解密,提高了程序安全性。
Context ClassLoader 线程上下文类加载器
每个Thread都有一个相关联的ClassLoader,默认是AppClassLoader。并且子线程默认使用父线程的ClassLoader除非子线程特别设置。(Thread.currentThread().setContextClassLoader(xxxLoader);)
总结:
1. ClassLoader用来加载class文件的。
2. 系统内置的ClassLoader通过双亲委托来加载指定路径下的class和资源。
3. 可以自定义ClassLoader一般覆盖findClass()方法。
4. ContextClassLoader与线程相关,可以获取和设置,可以绕过双亲委托的机制。
详情参考:http://blog.csdn.net/briblue/article/details/54973413