文章目录
一、类加载器简介
类加载器负责在运行时将 Java 类动态加载到 JVM (Java 虚拟机)。它们也是 JRE(Java 运行时环境)的一部分。因此,借助类加载器,JVM 无需了解底层文件或文件系统即可运行 Java 程序。
此外,这些 Java 类不会一次全部加载到内存中,而是在应用程序需要它们时加载。这就是类加载器发挥作用的地方。他们负责将类加载到内存中。
二、内置类加载器的类型
public void printClassLoaders() throws ClassNotFoundException {
System.out.println("Classloader of this class:"
+ PrintClassLoader.class.getClassLoader());
System.out.println("Classloader of Logging:"
+ Logging.class.getClassLoader());
System.out.println("Classloader of ArrayList:"
+ ArrayList.class.getClassLoader());
}
执行时,上述方法打印:
Class loader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2
Class loader of Logging:sun.misc.Launcher$ExtClassLoader@3caeaf62
Class loader of ArrayList:null
正如我们所见,这里有三种不同的类加载器:应用程序、扩展程序和引导程序(显示为null)。
应用程序类加载器加载包含示例方法的类。应用程序或系统类加载器在类路径中加载我们自己的文件。
接下来,扩展类加载器加载Logging类。扩展类加载器加载作为标准核心 Java 类的扩展的类。
最后,引导类加载器加载ArrayList类。引导类加载器或原始类加载器是所有其他类加载器的父级。
但是,我们可以看到,对于ArrayList,它在输出中显示为null 。这是因为引导类加载器是用本机代码而不是 Java 编写的,因此它不会显示为 Java 类。因此,引导类加载器的行为在不同的 JVM 中会有所不同。
引导类加载器(Bootstrap Class Loader)
Java 类由java.lang.ClassLoader的实例加载。但是,类加载器本身就是类。所以问题是,谁加载java.lang.ClassLoader本身?
这就是引导程序或原始类加载器发挥作用的地方。
它主要负责加载 JDK 内部类,通常是rt.jar和其他位于$JAVA_HOME/jre/lib目录下的核心库。此外,Bootstrap 类加载器充当所有其他ClassLoader实例的父级。
这个引导类加载器是核心 JVM 的一部分,并且是用native code编写的。不同的平台可能有这个特定类加载器的不同实现。
扩展类加载器(Extension Class Loader)
扩展类加载器是引导类加载器的子类,负责加载标准核心 Java 类的扩展,以便平台上运行的所有应用程序都可以使用它们。
扩展类加载器从 JDK 扩展目录加载,通常是$JAVA_HOME/lib/ext目录,或java.ext.dirs系统属性中提到的任何其他目录。
系统类加载器(System Class Loader)
系统或应用程序类加载器负责将所有应用程序级别的类加载到 JVM 中.它加载从classpath environment variable, -classpath, or -cp command line option中找到的文件.它同时也是扩展类加载器的子类。
三、类加载器如何工作
类加载器是 Java 运行时环境的一部分。当 JVM 请求一个类时,类加载器会尝试定位该类并使用完全限定的类名将类定义加载到运行时中。
java.lang.ClassLoader.loadClass()方法负责将类定义加载到运行时。它尝试根据完全限定名称加载类。
如果该类尚未加载,它将请求委托给父类加载器。这个过程递归地发生。
最终,如果父类加载器没有找到该类,那么子类将调用java.net.URLClassLoader.findClass()方法在文件系统本身中查找类。
如果最后一个子类加载器也无法加载该类,它会抛出java.lang.NoClassDefFoundError或java.lang.ClassNotFoundException。
java.lang.ClassNotFoundException: com.classloader.SampleClassLoader
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
下面是三个类加载器的特性
3.1 委托模型
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
3.2 类的唯一性
作为委托模型的结果,很容易确保类的唯一性,因为我们总是尝试向上委托。
如果父类加载器无法找到该类,那么当前实例才会尝试自己这样做。
3.3 可见性
子类加载器对其父类加载器加载的类是可见的。
例如,system class loader加载的类可以看到extension 和 bootstrap class loaders,反之则不行。
为了说明这一点,如果 A 类由应用程序类加载器加载,而 B 类由扩展类加载器加载,那么就应用程序类加载器加载的其他类而言,A 类和 B 类都是可见的。
但是,B 类是扩展类加载器加载的其他类唯一可见的类。
四.了解java.lang.ClassLoader
4.1 loadClass( )方法
此方法负责加载给定名称参数的类。name 参数是指完全限定的类名。
Java 虚拟机调用loadClass()方法来解析类引用,将 resolve 设置为true。但是,并不总是需要解析一个类。
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 {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
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.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
该方法的默认实现按以下顺序搜索类:
调用findLoadedClass(String)方法以查看该类是否已加载。
在父类加载器上调用loadClass(String)方法。
调用findClass(String)方法来查找类。
4.2 defineClass ()
众所周知,java编译器会将.java文件编译成jvm可以识别的机器代码保存在.class文件当中。正常情况下,java会先调用classLoader去加载.class文件,然后调用loadClass函数去加载对应的类名,返回一个Class对象。而defineClass提供了另外一种方法,从官方定义中可以看出,defineClass可以从byte[]还原出一个Class对象,如果数据不包含有效的类,则会引发ClassFormatError。
此外,我们不能覆盖这个方法,因为它被标记为 final。
protected final Class<?> defineClass(
String name, byte[] b, int off, int len) throws ClassFormatError
4.3 findClass( )方法
此方法查找具有完全限定名称的类作为参数。我们需要在遵循委托模型的自定义类加载器实现中重写此方法以加载类。
此外,如果父类加载器找不到请求的类, loadClass()会调用此方法。
如果类加载器的父级没有找到该类,默认实现会抛出ClassNotFoundException 。
protected Class<?> findClass(
String name) throws ClassNotFoundException
4.4 getParent( )方法
此方法返回用于委托的父类加载器。
一些实现中,如之前在第 2 节中看到的实现,使用null来表示bootstrapClassLoader。
public final ClassLoader getParent()
4.5 getResource( )方法
此方法尝试查找具有给定名称的资源。
它将首先委托给资源的父类加载器。如果 parent 为null,则搜索虚拟机内置的类加载器的路径。
public URL getResource(String name)
如果失败,该方法将调用findResource(String)来查找资源。
它返回一个用于读取资源的 URL 对象,如果找不到资源或调用者没有足够的权限返回资源,则返回 null。
资源加载在Java中被认为是与位置独立的。
五.总结
类加载器对于执行 Java 程序是必不可少的。总结了不同类型的类加载器,即 Bootstrap、Extensions 和 System 类加载器。Bootstrap 作为所有它们的父级,负责加载 JDK 内部类。另一方面,Extension和System分别从 Java 扩展目录和ClassPath加载类。