- Bootstrap ClassLoader/启动类加载器主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作。
- Extension ClassLoader/扩展类加载器
主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作。 - System ClassLoader/系统类加载器
主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作。 - User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)
在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性。
类加载器的特性:
- 每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类。
- 为了实现java安全沙箱模型顶层的类加载器安全机制, java默认采用了 " 双亲委派的加载链 " 结构。
类图中, BootstrapClassLoader是一个单独的java类, 其实在这里, 不应该叫他是一个java类。因为,它已经完全不用java实现了。它是在jvm启动时, 就被构造起来的, 负责java平台核心库。
自定义类加载器加载一个类的步骤
ClassLoader 类加载逻辑分析, 以下逻辑是除 BootstrapClassLoader 外的类加载器加载流程:
- // 检查类是否已被装载过
- Class c = findLoadedClass(name);
- if (c == null ) {
- // 指定类未被装载过
- try {
- if (parent != null ) {
- // 如果父类加载器不为空, 则委派给父类加载
- c = parent.loadClass(name, false );
- } else {
- // 如果父类加载器为空, 则委派给启动类加载加载
- c = findBootstrapClass0(name);
- }
- } catch (ClassNotFoundException e) {
- // 启动类加载器或父类加载器抛出异常后, 当前类加载器将其
- // 捕获, 并通过findClass方法, 由自身加载
- c = findClass(name);
- }
- }
线程上下文类加载器
java默认的线程上下文类加载器是 系统类加载器(AppClassLoader)。
- // Now create the class loader to use to launch the application
- try {
- loader = AppClassLoader.getAppClassLoader(extcl);
- } catch (IOException e) {
- throw new InternalError(
- "Could not create application class loader" );
- }
- // Also set the context class loader for the primordial thread.
- Thread.currentThread().setContextClassLoader(loader);
- // Now create the class loader to use to launch the application
- try {
- loader = AppClassLoader.getAppClassLoader(extcl);
- } catch (IOException e) {
- throw new InternalError(
- "Could not create application class loader" );
- }
- // Also set the context class loader for the primordial thread.
- Thread.currentThread().setContextClassLoader(loader);
// Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader" );
}
// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);
以上代码摘自sun.misc.Launch的无参构造函数Launch()。
使用线程上下文类加载器, 可以在执行线程中, 抛弃双亲委派加载链模式, 使用线程上下文里的类加载器加载类.
典型的例子有, 通过线程上下文来加载第三方库jndi实现, 而不依赖于双亲委派.
大部分java app服务器(jboss, tomcat..)也是采用contextClassLoader来处理web服务。
还有一些采用 hotswap 特性的框架, 也使用了线程上下文类加载器, 比如 seasar (full stack framework in japenese).
线程上下文从根本解决了一般应用不能违背双亲委派模式的问题.
使java类加载体系显得更灵活.
随着多核时代的来临, 相信多线程开发将会越来越多地进入程序员的实际编码过程中. 因此,
在编写基础设施时, 通过使用线程上下文来加载类, 应该是一个很好的选择。
当然, 好东西都有利弊. 使用线程上下文加载类, 也要注意, 保证多根需要通信的线程间的类加载器应该是同一个,
防止因为不同的类加载器, 导致类型转换异常(ClassCastException)。
为什么要使用这种双亲委托模式呢?
- 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
- 考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。
java动态载入class的两种方式:
- implicit隐式,即利用实例化才载入的特性来动态载入class
- explicit显式方式,又分两种方式:
- java.lang.Class的forName()方法
- java.lang.ClassLoader的loadClass()方法
用Class.forName加载类
Class.forName使用的是被调用者的类加载器来加载类的。
这种特性, 证明了java类加载器中的名称空间是唯一的, 不会相互干扰。
即在一般情况下, 保证同一个类中所关联的其他类都是由当前类的类加载器所加载的。
- public static Class forName(String className)
- throws ClassNotFoundException {
- return forName0(className, true , ClassLoader.getCallerClassLoader());
- }
- /** Called after security checks have been made. */
- private static native Class forName0(String name, boolean initialize,
- ClassLoader loader)
- throws ClassNotFoundException;
- public static Class forName(String className)
- throws ClassNotFoundException {
- return forName0(className, true , ClassLoader.getCallerClassLoader());
- }
- /** Called after security checks have been made. */
- private static native Class forName0(String name, boolean initialize,
- ClassLoader loader)
- throws ClassNotFoundException;
public static Class forName(String className)
throws ClassNotFoundException {
return forName0(className, true , ClassLoader.getCallerClassLoader());
}
/** Called after security checks have been made. */
private static native Class forName0(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException;
上面中 ClassLoader.getCallerClassLoader 就是得到调用当前forName方法的类的类加载器
static块在什么时候执行?
- 当调用forName(String)载入class时执行,如果调用ClassLoader.loadClass并不会执行.forName(String,false,ClassLoader)时也不会执行.
- 如果载入Class时没有执行static块则在第一次实例化时执行.比如new ,Class.newInstance()操作
- static块仅执行一次
各个java类由哪些classLoader加载?
- java类可以通过实例.getClass.getClassLoader()得知
- 接口由AppClassLoader(System ClassLoader,可以由ClassLoader.getSystemClassLoader()获得实例)载入
- ClassLoader类由bootstrap loader载入
NoClassDefFoundError和ClassNotFoundException
- NoClassDefFoundError:当java源文件已编译成.class文件,但是ClassLoader在运行期间在其搜寻路径load某个类时,没有找到.class文件则报这个错
- ClassNotFoundException:试图通过一个String变量来创建一个Class类时不成功则抛出这个异常
为什么要使用Thread context classloader
类A调用类B,B会要求调用它的类的类加载器来加载它,也就是B会要求加载A的加载器来加载B。这就会有个问题,如果他们在一起,那没关系,肯定某个classloader会把它们俩都加载好。但是如果A在/lib/ext文件夹中,而B在Classpath中呢?过程是这样的首先加载A,那么一层层上到Bootstrap Classloader,boot没找到所以ext自己找,找到了,没问题;加载B,因为A调用了B,所以也从bootstrap来找,没找到,然后A的ext classloader来找还是没找到,但是再也不会往下调用了,于是报出ClassNotFoundException。
但是现实生活中有很多应用,比如JDBC核心方法在核心库而驱动在扩展库,是必定在两个地方的,那怎么办呢?要用到Context ClassLoader我们在建立一个线程Thread的时候,可以为这个线程通过setContextClassLoader方法来指定一个合适的classloader作为这个线程的context classloader,当此线程运行的时候,我们可以通过getContextClassLoader方法来获得此context classloader,就可以用它来载入我们所需要的Class。
默认的是system classloader。利用这个特性,我们可以“打破”classloader委托机制了,父classloader可以获得当前线程的context classloader,而这个context classloader可以是它的子classloader或者其他的classloader,那么父classloader就可以从其获得所需的 Class,这就打破了只能向父classloader请求的限制了。这个机制可以满足当我们的classpath是在运行时才确定,并由定制的 classloader加载的时候,由system classloader(即在jvm classpath中)加载的class可以通过context classloader获得定制的classloader并加载入特定的class(通常是抽象类和接口,定制的classloader中是其实现),例如web应用中的servlet就是用这种机制加载的.
Thread.currentThread().getContextClassLoader()的意义:
父Classloader可以使用当前线程Thread.currentthread().getContextLoader()中指定的classloader中加载的类。
颠覆了父ClassLoader不能使用子Classloader或者是其它没有直接父子关系的Classloader中加载的类这种情况。
这个就是Context Class Loader的意义。
实战演练
(1)步骤一
上图进行了这样一个实验:首先一个名为Class(1)的类中启动MainThread(其实就是这个类里面有main函数的意思啦),注意这个类的名字后面标出了其所在的路径(即ClassPath),然后在里面进行测试,发现目前它的装载器和当前线程(MainThread)的ContextClassLoader都是AppClassLoader。然后Class(1)启动了一个新线程Class(2)。这里的Class(2)是一个Thread的子类,执行Class(2)代码的线程我称之为Thread-0。
(2)步骤二
上图可以看到Class(2)的装载器和ContextClassLoader同样都是AppClassLoader。随后我在Class(2)中创建了一个新的URLCLassLoader,并用这个ClassLoader来载入另一个和Class(1)不在同一个ClassPath下的类Class(3)。此时我们就可以看到变化:即载入Class(3)的装载器是URLClassLoader,而ContextClassLoader还仍然是AppClassLoader。
(2)步骤三
最后我们在Class(3)中启动了一个线程类Class(4),发现Class(4)也是由URLClassLoader载入的,而此时ContextClassLoader仍然是AppClassLoader。
在整个过程中,装载类的ClassLoader发生了变化,由于线程类Class(4)是由Class(3)启动的,所以装载它的类装载器就变成了URLClassLoader。与此同时,所有线程的ContextClassLoader都继承了生成该线程的ContextClassLoader--AppClassLoader。
如果我们在第二步的结尾执行了绿色框中的代码:setContextClassLoader(),则结果就会变成下面这个样子:
我们可以清楚地看到,由于Thread-0将其ContextClassLoader设置成了URLClassLoader,而Thread-1是在Thread-0里面生成的,所以就继承了其ContextClassLoader,变成了URLClassLoader。
有三个工程
ModelA
public class AActivator implements IActivator{
@Override
public void init() {
System.out.println("load A plugin~~~~~~~~~");
PluginCenter center =PluginCenter.getInstance();
center.putNotice("A", new ANotice());
Thread.currentThread().setContextClassLoader(PluginCenter.getInstance().getClassLoader("B"));
MyThread m =new MyThread();
Thread a =new Thread(m);
a.start();
}
}
public class ANotice implements INotice {
@Override
public void noticeName() {
System.out.println("A notice");
}
}
public class MyThread implements Runnable {
@Override
public void run() {
//Aurl,父classloader是app,MyThread是由AActivator产生的,AActivator由Aurl载入的,所以MyThread也是Aurl载入
System.out.println("~~~@@MyThread类~~~"+MyThread.class.getClassLoader());
//Burl,由于AActivator类里设定了上下文,如果不设定还是APP(一直继承初始的线程类装载器)
System.out.println("~~~@@当前线程~~~"+Thread.currentThread().getContextClassLoader());
try {
Class bog = Class.forName("com.b.Bgo", true, Thread.currentThread().getContextClassLoader());
// Class bog = Thread.currentThread().getContextClassLoader().loadClass("com.b.Bgo");
// Class bog = PluginCenter.getInstance().getClassLoader("B").loadClass("com.b.Bgo");
CenterGo b=(CenterGo) bog.newInstance();
System.out.println("~~**~~"+b.getGo());
//报错,Bgo由当前classloader Aurl加载,它的父classloader是APP,都找不到ModleB的类
// Bgo pp = new Bgo();
// Bgo bb=(Bgo) bog.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
modelB
public class BActivator implements IActivator{
@Override
public void init() {
Bgo bgo =new Bgo();
System.out.println("load B pugin~~~~~~~~~"+bgo.getGo());
PluginCenter center =PluginCenter.getInstance();
center.putNotice("B", new BNotice());
}
}
public class Bgo implements CenterGo{
private int b = 1;
public int getGo() {
return b;
}
}
public class BNotice implements INotice {
@Override
public void noticeName() {
System.out.println("B notice");
}
}
ModleCenter
public interface CenterGo {
public int getGo();
}
public interface IActivator {
/**
* 第一次启动插件时,调用此方法 IPluginActivator的初始化最好在这个方法中实现
* 因为使用无参构造方法实例化IPluginActivator时plugin的信息并不一定完整
*/
public void init();
}
public class Inint {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
String Apath = "D:/work_AUM/ModelA/bin/";
String Bpath = "D:/work_AUM/ModelB/bin/";
URL aurl = new URL("file", "", -1, Apath);
URL burl = new URL("file", "", -1, Bpath);
URLClassLoader Aloader =new URLClassLoader(new URL[]{aurl});
URLClassLoader Bloader =new URLClassLoader(new URL[]{burl});
PluginCenter center =PluginCenter.getInstance();
center.putClassLoader("A", Aloader);
center.putClassLoader("B", Bloader);
IActivator i1 = (IActivator)Aloader.loadClass("com.a.AActivator").newInstance();
IActivator i2 = (IActivator)Bloader.loadClass("com.b.BActivator").newInstance();;
//当前线程是appClassLoader
System.out.println("~~~~~A~~~~~"+i1.getClass().getClassLoader());//Aurl
System.out.println("~~~~~B~~~~~"+i2.getClass().getClassLoader());//Burl
i1.init();
i2.init();
center.dispathNotice("A");
}
}
public interface INotice {
public void noticeName();
}
public class PluginCenter {
private static PluginCenter p = new PluginCenter();
private PluginCenter(){
}
public static PluginCenter getInstance(){
return p;
}
private Map<String,ClassLoader> map =new HashMap<String,ClassLoader>();
private Map<String,INotice> map2 =new HashMap<String,INotice>();
public ClassLoader getClassLoader(String name){
return map.get(name);
}
public void putClassLoader(String name,ClassLoader l){
this.map.put(name, l);
}
public void putNotice(String name,INotice l){
this.map2.put(name, l);
}
public void dispathNotice(String name){
INotice i = map2.get(name);
i.noticeName();
}
public int getNo(){
return 1;
}
}