标签:
Java
中的所有类,必须被装载到jvm
中才能运行,这个装载工作是由jvm
中的类装载器完成的。JVM
在加载类的时候,都是通过ClassLoader
的loadClass()
方法来加载class的。
public class TestClassLoader {
public static void main(String[] args) {
System.out.println(TestClassLoader.class.getClassLoader());
}
}
运行结果:
sun.misc.Launcher$AppClassLoader@4e0e2f2a
但是ClassLoader是怎样加载class的呢?
一、java ClassLoader体系
Java默认是有三个ClassLoader,按层次关系从上到下依次是:
是最顶层的ClassLoader,比较特殊,是用C++编写集成在JVM中的,是JVM启动的时候用来加载一些核心类的,比如:rt.jar
,resources.jar
,charsets.jar
,jce.jar
等
import java.net.URL;
public class TestClassLoader {
public static void main(String[] args) {
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i].toExternalForm());
}
}
}
运行结果为:
file:/C:/Program%20Files/Java/jre1.8.0_73/lib/resources.jar
file:/C:/Program%20Files/Java/jre1.8.0_73/lib/rt.jar
file:/C:/Program%20Files/Java/jre1.8.0_73/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jre1.8.0_73/lib/jsse.jar
file:/C:/Program%20Files/Java/jre1.8.0_73/lib/jce.jar
file:/C:/Program%20Files/Java/jre1.8.0_73/lib/charsets.jar
file:/C:/Program%20Files/Java/jre1.8.0_73/lib/jfr.jar
file:/C:/Program%20Files/Java/jre1.8.0_73/classes
Bootstrap ClassLoader加载的类全都是java自有的核心类。
ExtClassLoader、AppClassLoader都是继承自Bootstrap ClassLoader。
ExtClassLoader是用来加载扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目录下的所有的jar
AppClassLoader叫做系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件,包括我们平时运行jar包指定cp参数下的jar包
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果父加载器不为空,使用父加载器加载class
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//如果父加载器也为空,使用bootstarpClassLoader加载
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();
//如果还为空,调用自定义的加载方法
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(c);
}
return c;
}
}
这时问题是,parent是什么?
public abstract class ClassLoader {
// The parent class loader for delegation
private final ClassLoader parent;
...
}
原来ClassLoader内部使用了父加载器,这就是双亲委托模式。
如图所示:
public class TestClassLoader {
public static void main(String[] args) {
ClassLoader loader = MyAppClassLoader.class.getClassLoader();
while(loader != null) {
System.out.println(loader);
loader = loader.getParent();
}
System.out.println(loader);
}
}
运行结果为:
sun.misc.Launcher$AppClassLoader@4e0e2f2a
sun.misc.Launcher$ExtClassLoader@2a139a55
null
java.lang.ClassCaseException
public class MyAppClassLoader extends ClassLoader{
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
//从特殊路径下读取class,或者从网络中读取class,不管咋样,实现自己的方法,返回加载class
//...
}
}
四、ContextLoader当前classLoader
首先关于类的加载补充一点就是如果类A是被一个加载器加载的,那么类A中引用的B也是由这个加载器加载的(如果B还没有被加载的话),通常情况下就是类B必须在类A的classpath下。
但是考虑多线程环境下不同的对象可能是由不同的ClassLoader加载的,那么当一个由ClassLoaderC加载的对象A从一个线程被传到另一个线程ThreadB中,而ThreadB是由ClassLoaderD加载的,这时候如果A想获取除了自己的classpath以外的资源的话,它就可以通过Thread.currentThread().getContextClassLoader()
来获取线程上下文的ClassLoader了,一般就是ClassLoaderD了,可以通过Thread.currentThread().setContextClassLoader(ClassLoader)
来显示的设置
之所以有Context ClassLoader是因为Java的这种“双亲委托”机制是有局限性的:
比如: JNDI为例,JNDI的类是由bootstrap ClassLoader从rt.jar中间载入的,但是JNDI具体的核心驱动是由正式的实现提供的,并且通常会处于-cp参数之下(注:也就是默认的System ClassLoader管理),这就要求bootstartp ClassLoader去载入只有SystemClassLoader可见的类,正常的逻辑就没办法处理。怎么办呢?parent可以通过获得当前调用Thread的方法获得调用线程的>Context ClassLoder 来载入类。
TODO ,contextLoader会继续补充,待完善。
标签:
原文地址:http://www.cnblogs.com/conge/p/5458901.html