深入理解Java虚拟机之类加载机制篇 (2)

​ 从 Java 虚拟机角度来说,类加载器主要分两种:一种为启动类加载器(Bootstrap ClassLoader),是使用C++语言进行编写的,属于虚拟机中的一部分;另一种就是所有其他类加载器,这些类加载器都由 Java 语言实现,是独立于虚拟机外部的,都继承自抽象类 java.lang.ClassLoader。接下来讲讲,Java 中用的最多的三种类加载器。

启动类加载器(Bootstrap ClassLoader):这个类加载器负责将存放在 <JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 re.jar ,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,那直接使用null即可。

扩展类加载器(Extension ClassLoader):这个加载器由 sun.misc.Launcher$ExtClassLoader 实现,它负责加载 <JAVA_HOME>\lib\ext 目录中的或被 java.ext.dirs 系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器

应用程序类加载器(Application ClassLoader):这个类加载器由 sun.misc.Launcher$AppClassLoader 实现。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,所以也称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般就会指定该类加载器为程序中默认的类加载器。

除了这3个类加载器之外,我们还可以自定义自己的类加载器。类加载器之间的关系如下图:

深入理解Java虚拟机之类加载机制篇

​ 以上展示的类加载器之间的这种层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有本身的父级类加载器。这里的类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。

​ 双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

​ 使用双亲委派模型来组织类加载器之间的关系,Java 类随着它的类加载器一起具备了一种带有优先级的层级关系。例如类 Java.lang.Object,它存放在 rt.jar 之中,无论哪一个类加载器要加载这个类,最终都是要委派给处于模型最顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都属于同一个类。如果没有采用双亲委派模型,让各个类加载器自行去加载的话,假设用户自定义了一个 java.lang.Object 的类,并同时放在程序的 ClassPath 中,那么系统将存在多个不同实现的Object类,应用程序就会变得一片混乱。

​ 从上图中我们也可以看到,存在着两个自定义类加载器;实现自定义类加载器的步骤:继承 ClassLoader,重写 findClass() 方法,调用 defineClass() 方法。

自定义类加载器的应用场景:

隔离加载类。在某些框架内进行中间件与应用的模块分离,把类加载到不同的环境。

修改类加载方式。类的加载模型并非强制,除Bootstrap外,其他的加载并非一定要引入,或者根据实际情况在某个时间点进行按需进行动态加载。

扩展加载源。比如从数据库、网络中进行加载。

防止源码泄露。Java 代码容易被编译和篡改,可以进行编译加密。

​ 双亲委派模型在 Java 程序中发挥着很大的作用,并且实现起来相对简单,双亲委派的实现代码都编写在 java.lang.ClassLoader 的 loadClass() 方法里面。实现逻辑是:首先会先去检查类是否被加载过,倘若没有加载就会调用父加载器的 loadClass() 方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载器加载失败,就会抛出 ClassNotFoundException 异常,最后就会调用自己的 findClass() 方法进行加载。下面看下 loadClass() 源码方法的简要实现:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先,检查类是否已经加载 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { // 类没有加载就会调用父加载器的loadClass()方法 if (parent != null) { c = parent.loadClass(name, false); } else { // 若父加载器为空则默认使用启动类加载器作为父加载器 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // 如果父加载器抛出 ClassNotFoundException // 说明父类加载器无法完成加载请求 } if (c == null) { // 在父类加载器无法加载的时候 // 再调用本身的findClass方法来进行 long t1 = System.nanoTime(); c = findClass(name); // 这是自定义类加载器;主要是记录统计数据 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzxwwx.html