[TOC]
类加载器
类与类加载器
类的唯一性是由类本身和加载他的类确定的,不同类加载器加载的类将被置于不同的命名空间
系统提供的类加载器是从本地磁盘加载 .class文件 的。如果,需要从远程网络或数据库中下载.class字节码文件,那就需要我们来挂载自定义的类加载器。
类加载器 类介绍
java.lang.ClassLoader
类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例。除此之外,ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等。
方法 | 说明 |
---|---|
getParent() | 返回该类加载器的父类加载器。 |
loadClass(String name) | 加载名称为 name的类,返回的结果是 java.lang.Class类的实例。 |
findClass(String name) | 查找名称为 name的类,返回的结果是 java.lang.Class类的实例。 |
findLoadedClass(String name) | 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。 |
defineClass(String name, byte[] b, int off, int len) | 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。 |
resolveClass(Class<?> c) | 链接指定的 Java 类。 |
loadClass(String name)
内部是通过调用 findLoadedClass(String name)
和 findClass(String name)
实现类加载的。
类加载器种类
BootStrapClassLoader(启动类加载器)
该类 是由C++编写的,涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不能直接通过引用进行操作。
加载$JAVA_HOME/jre/lib
下面的类库(或者通过参数-Xbootclasspath
指定)
ExtClassLoader(扩展类加载器)
该类的源代码在 sun.misc.Launcher.java
中,继承自 URLClassLoader
加载$JAVA_HOME/jre/lib/ext
下的类库(或者通过参数-Djava.ext.dirs
指定)
AppClassLoader(应用程序类加载器)
也称 系统类加载器。该类的源代码同样在 sun.misc.Launcher.java
中,继承自 URLClassLoader
加载 -classpath
-cp
-Djava.class.path
所指定的路径(即CLASSPATH)下的类库。
可以通过 ClassLoader.getSystemClassLoader()来获取它。
CustomClassLoader(自定义类加载器)
通过继承 java.lang.ClassLoader类可以自己自定义 一个类加载器。继承 java.lang.ClassLoader类时。
若未指定parent,jvm会自动将 AppClassLoader作为CustomClassLoader的parent。
ContextClassLoader(线程上下文类加载器)
线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。该类加载器可以打破双亲委派机制,用于请求子加载器去完成部分类的加载。
java.lang.Thread 中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。
Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。
比如 jdbc 接口,由于 jdbc 接口 是由jdk自带的,jdbc 接口的代码就会由 BootStrapClassLoader 加载的。
然而,通常情况下jdbc的实现类都是放在classpath路径下的,BootStrapClassLoader 是无法获取 jdbc 的实现类。
这时 ContextClassLoader 就派上用场了,通过 Thread.currentThread().getContextClassLoader() 获得类加载器,而该加载器属于 AppClassLoader(能够顺利的获取 classpath下的jdbc 的实现类)。
双亲委派模型
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
双亲委派优点
避免重复加载。
比如 object 类,其存放在 $JAVA_HOME/jre/lib 中,无论哪个类加载器加载 object,都会委派给 BootStrapClassLoader 加载,使得环境中的 object 始终是同一个。
但如果不是双亲委派机制,每个子类都自己加载 object,就会造成环境中存在多个 object 类
类的加载过程
以下分析 简要分析下 loadClass() 方法。
1 | public abstract class ClassLoader { |
URLClassLoader 的 findClass()
1 | public class URLClassLoader extends SecureClassLoader implements Closeable { |
URLClassPath 的 getResource() 方法
1 | public class URLClassPath { |
题外话: 通过分析源码后发现,URLClassPath 查找资源时,是通过遍历URLClassPath.Loader来查找类的。因此,URLClassPath.Loader 的遍历顺序决定了 class文件的加载时机。
可见性/隔离性
自定义类加载器拥有三个其本类加载器加载的所有类的可见性,但是处于不同分支的自定义类加载器相互之间不具有可见性。
所谓不可见即不能直接互相访问, 也就是即使它们装载同一个类,也会拥有不同的命名空间, 会有不同的Class实例。
Class.forName
Class.forName是一个静态方法,同样可以用来加载类。该方法有两种形式:Class.forName(String name, boolean initialize, ClassLoader loader)和 Class.forName(String className)。
Class.forName(String name, boolean initialize, ClassLoader loader)
参数 name表示的是类的全名;initialize表示是否初始化类;loader表示加载时使用的类加载器。
Class.forName(String className)
相当于设置了参数 initialize的值为 true,loader的值为当前类的类加载器
tomcat
- CommonClassLoader: 加载 /common/*
- CatalinaClassLoader: /server/*
- SharedClassLoader: /shared/*
- WebAppClassLoader: /WebApp/WEB-INF/* 中的 Java 类库。
其中 WebApp 类加载器和 Jsp 类加载器通常会存在多个实例,每一个 Web 应用程序对应一个 WebApp 类加载器,每一个 JSP 文件对应一个 Jsp 类加载器。
从图中的委派关系中可以看出,CommonClassLoader 能加载的类都可以被 CatalinaClassLoader 和 SharedClassLoader 使用,而 CatalinaClassLoader 和 SharedClassLoader 自己能加载的类则与对方相互隔离。
WebAppClassLoader 可以使用 SharedClassLoader 加载到的类,但各个 WebAppClassLoader 实例之间相互隔离。
JasperLoader 的加载范围仅仅是这个 JSP 文件所编译出来的那一个 Class,它出现的目的就是为了被丢弃:当服务器检测到 JSP 文件被修改时,会替换掉目前的 JasperLoader 的实例,并通过再建立一个新的 Jsp 类加载器来实现 JSP 文件的 HotSwap 功能。
其他参考
https://my.oschina.net/huzorro/blog/96791