Classloader 一 基本概念

一 前言

java程序执行流程,以下demo为例:

package java;

public class JavaClassDemo {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String str = new String("demo");
    }

}

首先通过javac命令将.java文件编译生成class文件

javac JavaClassDemo.java,生成JavaClassDemo.class文件

java JavaClassDemo,通过java命令执行主程序入口所在的class文件

程序运行过程,首先需要将JavaClassDemo类装入内存、验证、准备、解析、初始化。在初始化过程发现调用了String类,会继续请求将String类执行装入内存、验证、准备、解析、初始化。在这个程序执行过程分别需要完成JavaClassDemo、String两个类的加载,在java程序中有一个模块专门负责完成这个功能,就是类加载器。

二 类加载器分类

站在Jvm的角度,类加载有两种类型:
1. Bootstrap Classloader 由c++语言实现,属于jvm的一部分
2. 其他类加载器 由java语言实现,继承自java.lang.ClassLoader

站在java应用程序角度,类加载器主要有四种:
1. Bootstrap Classloader,负责加载jre/lib目录下的核心类库
2. Extension Classloader,负责加载jre/lib/ext目录下的核心类库
3. App Classloader 负责加载app classpath目录下的类库
4. 用户自定义 Classloader

三 双亲委托模型

从jdk1.2开始,类加载主要使用的模型是双亲委托模型。

加载过程

1.一个类加载器收到类加载请求,首先交给父类加载器来处理,依次类推

2.如果父类加载亲完成了类加载请求,则结束

3.如果父类加载器没有完成类加载请求,则交给子类加载器来尝试加载

好处

使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着
它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在
rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加
载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有
使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为
java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object
类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。如果读者
有兴趣的话,可以尝试去编写一个与rt.jar类库中已有类重名的Java类,将会发现可以正常编
译,但永远无法被加载运行[2]。
双亲委派模型对于保证Java程序的稳定运作很重要,但它的实现却非常简单,实现双亲
委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中,如代码清单7-10所示,
逻辑清晰易懂:先检查是否已经被加载过,若没有加载则调用父加载器的loadClass()方
法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出
ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

  //jdk 1.8
  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.
                    long t1 = System.nanoTime();
                    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;
        }
    }

loadClass方法执行流程如下:

1.检查需要加载的类是否已经被加载过,如果已经加载了,直接返回,否则执行2

2.将加载工作交给父类加载器来完成

3.如果父类加载器完成了类加载工作,结束,否则执行4

4.当前类加载器尝试完成类加载工作,加载完成,结束。

四 非双亲委托模型

1.loadClass 和 findClass

在上节看到loadClass执行逻辑遵循双亲委托原则,如果自定义类加载器重写loadClass方法,会破坏双亲委托原则。因此,从jdk1.2以后,提供了一个新的protected方法findClass,自定义类加载器应该重写这个方法,如果父类加载器通过loadClass加载类失败,会调用当前类的findClass继续加载。

2. OSGI

OSGI是当下java模块化的标准,实现动态热部署的关键是自定义的类加载器。在安卓模块化开源项目中,阿里推出的ATLAS就是基于OSGI标准。OSGI模式下,类加载机制不再是双亲委托机制,而是复杂网状结构。

五 总结

本文主要介绍了类加载器的基本概念,包括类加载工作流程、类加载器分类、双亲委托模型、非双亲委托模型。后面会继续学习每一个子知识点,逐一展开。

猜你喜欢

转载自blog.csdn.net/rambomatrix/article/details/78489852