java.lang.ClassLoader
。
该类负责将存放在[JAVA_HOME]/lib目录中的或者被-Xbootclasspath参数所指定的路径中,并且是虚拟机识别的,意味着及时将你自己的jar放到该目录下也不一定被加载,因为在JVM内已经按照文件名识别,例如rt.jar。
该加载器负责加载[JAVA_HOME]/lib/ext目录中的或者被java.ext.dirs系统变量指定的路径中的所有类库,该加载器由sun.misc.Launcher$ExtClassLoader实现。
该类加载器负责加载用户类路径(classpath)中指定的类库,开发者可以直接使用这个类加载器,该加载器由sun.misc.Launcher$AppClassLoader实现。
该类加载器由开发者自己实现,可以进行某些定制功能。
Java类加载机制遵循的是双亲委派模型,这个模型设计师在JDK1.2期间引入的。该模型的工作过程:如果一个类加载器接收到类加载的请求,它先不会加载,而是将该加载请求交个它的父类加载器去完成,每一层的加载器都如此,知道最顶层的加载器即Bootstrap ClassLoader,只有父类加载都没法完成加载的任务,那么才应该由子加载器去尝试加载。
双亲委派模型的示意图如下所示:
下面是ClassLoader的类加载器的loadClass实现,即类加载的代码:
//java.lang.ClassLoader#loadClass(java.lang.String, boolean)
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//<1> 加锁
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//<2> 查找是否已经被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//<3>.判断是否有父类加载器
if (parent != null) {
//<3.1> 有父类加载器,先使用父类加载器进行加载
c = parent.loadClass(name, false);
} else {
//<3.2> 如果没父类加载器,则表明类加载器已经是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();
//<4> 如果该类还没有加载,则会调用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(c);
}
return c;
}
}
Bootstrap ClassLoader
findClass
,尝试用字类加载器加载. 接下来我们来查看一下ClassLoader#findClass(String name)
,会发现该方法是空实现,直接抛出了异常,如下所示的代码,这是什么原因了;其实在签名提到了,所有类加载器,独立于虚拟机外部,并且完全继承自抽象类java.lang.ClassLoader
,所以该findClass(String name)
方法由具体的的实现类来实现,也就是如果我们自己定义类加载器,在不打破双亲委派机制的前提下,则只需要重写这个findClass(String name)
方法即可。
//java.lang.ClassLoader#findClass
protected Class<?> findClass(String name) throws ClassNotFoundException {
//直接抛出了ClassNotFoundException异常,显然父类没有找到则会抛出该异常,看回上面的loadClass就可以知道,子类是由try-catch该异常,然后再有子类加载的findClass尝试加载
throw new ClassNotFoundException(name);
}
下面我们看一下ClassLoader的子类sun.misc.Launcher.ExtClassLoader
对于findClass(String name)
的实现,ExtClassLoader的实现代码在URLClassLoader中,因为ExtClassLoader
继承自URLClassLoader
。
//java.net.URLClassLoader#findClass
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
//将带包名的全限定的类名,转为文件路径,例如将com.learn.Student转为/com/learn/Student.class
String path = name.replace('.', '/').concat(".class");
//加载class资源
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
//开始解析class
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
//如果加载失败,则会抛出ClassNotFoundException
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
首先编写一个Student
和测试的类ClassLoaderDemo
,如下所示:
//Student类
public class Student {
private String name = "Coco";
private int age = 20;
}
//ClassLoaderDemo类
public class ClassLoaderDemo {
public static void main(String[] args) {
Student student = new Student();
//打印该类的加载器
System.out.println("class loader:"+student.getClass().getClassLoader());
}
}
先直接在IDE工具中直接运行ClassLoaderDemo
后,得到下列的结果,类加载器为AppClassLoader
:
根据父类委派机制AppClassLoader
的父类加载器有ExtClassLoader
和BootstrapClassLoader
,因此可以知道ExtClassLoader和BootstrapClassLoader类加载器肯定没有Student类的class文件,按照父类委派模型,如果将Student的class文件放到[JAVA_HOME]/lib/ext目录下,则类加载器应该变为ExtClassLoader
。将Student类打包成JAR文件,将它放到对应的ext目录下,下面是打包后的Student类:
在IDE工具中再次运行ClassLoaderDemo
后,得到下列的结果,类加载器为ExtClassLoader
,可见先加载的是ext
目录下的Student
类,而不是classpath
下的:
双亲委派模型并不是一个强制性的约束,而仅仅只是Java设计者推荐开发者的类加载器实现方式。故如果有需要,则可以破坏双亲委派模型,实现自己的类加载器。双亲委派模型,解决了哥哥类加载器的基础类统一的问题,但是如果基础类又要回调用户的代码,也就是基础类的实现类,那要怎么办。为了解决这个问题,Java设计团队,只好引入:线程上下文类加载器(Thread Context ClassLoader)。
线程上下文类加载器(Context Class Loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread
中的方法 getContextClassLoader()
和 setContextClassLoader(ClassLoader cl)
用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)
方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。这样基础类,例如ExtClassLoader加载的类中,可以通过Thead.getContextClassLoader()获得比它低层次的类加载器,例如获取到AppClassLoader,则可以将ExtClassLoader加载的接口定义的实现加载进JVM中。典型的实现由JNDI,JDBC,JCE,Slf4j日志框架等。
如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器,也可以指定线程上下文中的类加载器。
/**
* Returns the context ClassLoader for this Thread. The context
* ClassLoader is provided by the creator of the thread for use
* by code running in this thread when loading classes and resources.
* If not {@linkplain #setContextClassLoader set}, the default is the
* ClassLoader context of the parent Thread. The context ClassLoader of the
* primordial thread is typically set to the class loader used to load the
* application.
*
* If a security manager is present, and the invoker's class loader is not
* {@code null} and is not the same as or an ancestor of the context class
* loader, then this method invokes the security manager's {@link
* SecurityManager#checkPermission(java.security.Permission) checkPermission}
* method with a {@link RuntimePermission RuntimePermission}{@code
* ("getClassLoader")} permission to verify that retrieval of the context
* class loader is permitted.
* 如果为null,则默认返回的是System ClassLoader
* @return the context ClassLoader for this Thread, or {@code null}
* indicating the system class loader (or, failing that, the
* bootstrap class loader)
*
* @throws SecurityException
* if the current thread cannot get the context ClassLoader
*
* @since 1.2
*/
@CallerSensitive
public ClassLoader getContextClassLoader() {
if (contextClassLoader == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(contextClassLoader,
Reflection.getCallerClass());
}
return contextClassLoader;
}
类加载器是 Java 语言的一项创新,完成了Java的Class文件加载的过程,通过分层,委托的机制,来确保基础类的Class不被恶意覆盖和修改,解决了哥哥类加载器的基础类的统一问题。