什么是ClassLoader?
这个简单,直接看ClassLoader类源码:
/**
* A class loader is an object that is responsible for loading classes. The
* class <tt>ClassLoader</tt> is an abstract class. Given the <a
* href="#name">binary name</a> of a class, a class loader should attempt to
* locate or generate data that constitutes a definition for the class. A
* typical strategy is to transform the name into a file name and then read a
* "class file" of that name from a file system.
* * <p> Every {@link Class <tt>Class</tt>} object contains a {@link
* Class#getClassLoader() reference} to the <tt>ClassLoader</tt> that defined
* it.
* * <p> <tt>Class</tt> objects for array classes are not created by class
* loaders, but are created automatically as required by the Java runtime.
* The class loader for an array class, as returned by {@link
* Class#getClassLoader()} is the same as the class loader for its element
* type; if the element type is a primitive type, then the array class has no
* class loader.
* * <p> Applications implement subclasses of <tt>ClassLoader</tt> in order to
* extend the manner in which the Java virtual machine dynamically loads
* classes.
* * <p> Class loaders may typically be used by security managers to indicate
* security domains.
* * <p> The <tt>ClassLoader</tt> class uses a delegation model to search for
* classes and resources. Each instance of <tt>ClassLoader</tt> has an
* associated parent class loader. When requested to find a class or
* resource, a <tt>ClassLoader</tt> instance will delegate the search for the
* class or resource to its parent class loader before attempting to find the
* class or resource itself. The virtual machine's built-in class loader,
* called the "bootstrap class loader", does not itself have a parent but may
* serve as the parent of a <tt>ClassLoader</tt> instance.
* * <p> Class loaders that support concurrent loading of classes are known as
* <em>parallel capable</em> class loaders and are required to register
* themselves at their class initialization time by invoking the
* {@link
* #registerAsParallelCapable <tt>ClassLoader.registerAsParallelCapable</tt>}
* method. Note that the <tt>ClassLoader</tt> class is registered as parallel
* capable by default. However, its subclasses still need to register themselves
* if they are parallel capable. <br>
* In environments in which the delegation model is not strictly
* hierarchical, class loaders need to be parallel capable, otherwise class
* loading can lead to deadlocks because the loader lock is held for the
* duration of the class loading process (see {@link #loadClass
* <tt>loadClass</tt>} methods).
* * <p> Normally, the Java virtual machine loads classes from the local file
* system in a platform-dependent manner. For example, on UNIX systems, the
* virtual machine loads classes from the directory defined by the
* <tt>CLASSPATH</tt> environment variable.
* * <p> However, some classes may not originate from a file; they may originate
* from other sources, such as the network, or they could be constructed by an
* application. The method {@link #defineClass(String, byte[], int, int)
* <tt>defineClass</tt>} converts an array of bytes into an instance of class
* <tt>Class</tt>. Instances of this newly defined class can be created using
* {@link Class#newInstance <tt>Class.newInstance</tt>}.
* * <p> The methods and constructors of objects created by a class loader may
* reference other classes. To determine the class(es) referred to, the Java
* virtual machine invokes the {@link #loadClass <tt>loadClass</tt>} method of
* the class loader that originally created the class
*/
public abstract class ClassLoader {
private final ClassLoader parent;
//省略若干代码
}
很明显,这是一个抽象类,内部还有一个parent指向ClassLoader类型的对象,其他就是有很多注释,这里整理下注释中的内容:
- ClassLoader的职责是加载一个类的原始数据到内存,生成运行时的类描述对象,可以理解将虚拟机外部字节码加载到虚拟机内部,最终在内存中生成类Class对象。而ClassLoader类的实现者主要就是扩展JVM加载Class的能力,因为ClassLoader并没有限制加载来源,所以可以从本地、网络、或者其他任意你想的地方加载Class
- 每个生成的Class对象都包含一个引用指向加载该Class的ClassLoader
- 是不是每一个Java中的Class都是ClassLoader生成的呢?其实不是。注释了解释了,数组对应的Class不是由ClassLoader生成的,而是由JVM直接生成的,但是数组中的元素(原始类型除外)相关的Class是由ClassLoader生成
- ClassLoader委托模型(就是“双亲委托机制”)来实现,观察到ClassLoader类中parent成员了吗,其就是用来实现向上委托的,熟悉设计模式的小伙伴应该也感受到了,怎么有点像责任链模式,哈哈哈哈😂。
那怎么实用呢?来个官方示例:
//从网络中加载一个Class
class NetworkClassLoader extends ClassLoader {
String host;
int port;
//重写findClass,根据class name 返回一个Class
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);//调用native 方法 defineClass将byte[]转化为Class
}
private byte[] loadClassData(String name) {
// 从网络中加载类的字节数组
}
}
ClassLoader工作原理
public abstract class ClassLoader {
private final ClassLoader parent;
//...
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//synchronized关键字,解决多线程情况下同步问题,因为一个类只需要被加载一次
synchronized (getClassLoadingLock(name)) {
// 首先,检查该类是否已经被加载了,返回null表示暂时没有被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果parent不为null,先委托给pareny去加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//parent为null时,则委托findBootstrapClassOrNull方法返回的类加载器去加载,
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 如果仍然没有加载成功,则调用findClass方法去加载类,就是之前我们实例中重写的findClass方法
long t1 = System.nanoTime();
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
}
上述源码中,我们还不知道findBootstrapClass方法返回的是什么加载器,这里介绍下Jvm中的加载器:
JVM 运行实例中会存在多个 ClassLoader,不同的 ClassLoader 会从不同的地方加载字节码文件。它可以从不同的文件目录加载,也可以从不同的 jar 文件中加载,也可以从网络上不同的服务地址来加载。JVM 中内置了三个重要的 ClassLoader,分别是 BootstrapClassLoader、ExtensionClassLoader 和 AppClassLoader。
BootstrapClassLoader 负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,我们常用内置库 java.xxx.* 都在里面,比如 java.util.、java.io.、java.nio.、java.lang. 等等。这个 ClassLoader 比较特殊,它是由 C 代码实现的,我们将它称之为「根加载器」。
ExtensionClassLoader 负责加载 JVM 扩展类,比如 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名通常以 javax 开头,它们的 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,有很多 jar 包。
AppClassLoader 才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。
那些位于网络上静态文件服务器提供的 jar 包和 class文件,jdk 内置了一个 URLClassLoader,用户只需要传递规范的网络路径给构造器,就可以使用 URLClassLoader 来加载远程类库了。URLClassLoader 不但可以加载远程类库,还可以加载本地路径的类库,取决于构造器中不同的地址形式。ExtensionClassLoader 和 AppClassLoader 都是 URLClassLoader 的子类,它们都是从本地文件系统里加载类库。
AppClassLoader 可以由 ClassLoader 类提供的静态方法 getSystemClassLoader() 得到,它就是我们所说的「系统类加载器」,我们用户平时编写的类代码通常都是由它加载的。当我们的 main 方法执行的时候,这第一个用户类的加载器就是 AppClassLoader。
这里findBootstrapClass方法返回的就是BootstrapClassLoader。那么整理下,其findClass的大致加载流程如下
ClassLoader有那些性质?
延迟加载
ClassLoader不是一次加载所有的类到内存,而是按需加载,只有需要触发初始化的类或者直接引用到的类才会进行加载。
触发类初始化的有:
- 使用new和反射创建对象
- 直接访问静态方法、静态变量
- 子类初始化时,必定先初始化父类,接口除外
传递性
传递性主要体现在两个方向上:
- 由于一个类A引用到还未加载的类B时,类B的加载由类A的加载完成
- 如果没有通过Thread的setContextClassLoader方法设置ClassLoader,那么子线程会和创建ta的线程使用同一个ClassLoader
安全性
双亲委托机制使得具有层次和域,公共类始终由根加载器加载,越基础的类,其访问域越大,而会被更安全的加载器加载,从而达到保护Jvm的目的,大家可以想一下,自己写一个java.lang.Object类,试试看会不会正常运行?会出现什么情况。
ClassLoader注意问题
类版本冲突问题
https://zhuanlan.zhihu.com/p/141527120
https://developer.ibm.com/zh/articles/j-lo-classloader/