[JVM]类加载器(一)

类加载器概述

在Java中类加载器是用来把类加载到Java虚拟机中的。从JDK 1.2 版本开始,类的加载过程使用双亲委托机制,这种机制能够更好的保证Java平台的安全。在此委托机制中,除了Java虚拟机自带的2类加载器外,其余加载器都有且只有一个父加载器。当Java程序员请求加载器loader1加载Sample类时,loader1会首先委托自己的父加载器去加载Sample类,若父加载器能加载,则由父加载器完成加载任务,否则才由加载器loader1本身加载Sample类。

JVM内置类加载器

Java虚拟机自带了以下几种类加载器:

  • 根(bootstrap)类加载器:该加载器没有父加载器。它负责加载虚拟机的核心类库,如java.lang.*等。例如java.lang.Object就是由根类加载器加载的。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库,例如根类加载器会加载$JAVA_HOMEjre/lib/rt.jar里所有的class。根类加载器的实现依赖于底层操作系统,由C++实现,属于虚拟机实现的一部分,它并没有继承java.lang.ClassLoader类。
  • 扩展(Extension)类加载器:它的父加载器为根类加载器。负责加载Java平台中扩展功能的一些jar包。它从java.ext.dirs系统属性所指定目录中加载类库,或者从JDK的安装目录jre\lib\ext子目录(扩展目录)下加载类库,如果把用户创建的JAR文件放在这个目录下,也会由扩展类加载器加载。扩展类加载器是纯Java类,是java.lang.ClassLoader类的子类。
  • 系统(System)类加载器:也称为应用类加载器。它的父加载器为扩展类加载器。它从classpath环境变量或java.class.path所指定的目录中加载类,它是用户自定义类加载器的默认父加载器。系统类加载器是纯Java类,是java.lang.ClassLoader类的子类。

类加载器的双亲委托机制

如下图所示:
这里写图片描述
假设我们要使用自定义的类加载器CustomClassLoader去加载一个Sample类,那么CustomClassLoader会优先将加载这个类的任务委托给其父加载器即系统类加载器AppClassLoaderAppClassLoader首先查找Sample类是否已经加载,如未加载又会优先将加载任务委托给其父加载器即扩展类加载器ExtensionClassLoader,同理ExtensionClassLoader也会首先查找Sample类是否已经加载过,若未加载过会优先将加载任务委托给其父加载器即根类加载器BootstraoClassLoader,根类架在器同样查找Sample类是否已经加载过,若还是未加载过,因为根类加载器已经没有父加载器了,此时根类加载器会尝试去加载Sample类,因为根类加载器只能从特定的目录下加载类,如果该目录下没有Sample类则根类加载无法加载,接着由扩展类加载器ExtensionClassLoader尝试去加载Sample类,同理若还是无法加载则由系统类加载器尝试去加载Sample类,以此类推从上往下查找能够加载Sample类的类加载器,直到找到为止。

定义类加载器和初始类加载器

如果一个类能够成功的加载Sample类,那么这个类加载器被称为定义类加载器,所有能成功返回Sample类的Class对象引用的类加载器(包括定义类加载器)都被称为初始类加载器。如下图所示:
这里写图片描述
假设最开始由loader2尝试去加载Sample类,但最终是系统类加载器成功加载了Sample类,那么系统类加载器就称为定义类加载器,而loader2及其父加载器loader2和系统类加载器则都是Sample类的初始类加载器,因为这三者都能够返回Sample类的Class对象的引用。

获取ClassLoader的途径

  • 获取当前类的ClassLoader:clazz.getClassLoader()
  • 获得当前线程上下文的ClassLoader:Thread.currentThread.getContextClassLoader()
  • 获得系统ClassLoader:ClassLoader.getSystemClassLoader()
  • 获得调用者ClassLoader:DriverManager.getCallerClassLoader()

关于ClassLoader类

ClassLoader类是一个抽象类,给定一个类的二进制名称,类加载器应该能够定位或者生成构成该类定义的运行时数据(我的理解就是这个类的Class对象),每一个Class对象都包含一个Class.getClassLoader()来获取定义该类的类加载器,但对于数组类型的Class对象不是由并不是由类加载器创建的,数组类型的Class对象是由JVM在运行期动态为我们生成的,但数组类型的Class对象调用getClassLoader()所返回的类加载器与数组元素的的类加载器是一样的,但如果该数组中元素属于原生类型,那么该数组就没有类加载器(注意这个对于根类加载器返回null时不一样的)。通过实现ClassLoader我们可以实现自定义类加载器,从扩展JVM加载类的方式。

ClassLoader使用了一种委托模型来查找类和资源,每一个ClassLoader类的实例(即类加载器)都会关联一个父类加载器。类加载器在加载一个类之前会首先将加载类的动作委托给其父加载器,父加载器无法加载时才由自己加载。JVM内建了一个称为boot strap class loader的类加载器,该加载器没有父加载器但可以作为其他类加载器的父加载器。

一个类加载器如果支持并行的加载类,则称之为并行类加载器,并行类类加载器要求在初始化的时候调用ClassLoader类的registerAsParallelCapable()方法来注册自身。如果该类加载的子类也需要能够并行的加载类,则子类依然需要调用registerAsParallelCapable()来注册自身,就是说不能因为父类注册过了子类就不需要注册了。

ClassLoader类的defineClass方法可以将一个字节数组转化为一个Class对象,接着就可以使用该Class对象的newInstance来创建对象了。

ClassLoader源码分析

通过ClassLoader类的loadClass方法我们可以加载一个类,loadClass方法的实现如下所示:

    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
                }
                /**
                 * 若父加载器和跟加载器都未能加载该类,则调用当前类加载器的findClass方法来加载该类,而findClass在ClassLoader类中实现就是抛出一个异常,需要在我们自定义的类加载器中实现,一般`findClass`的实现方式是首先加载类的二进制数据得到一个byte数组,然后调用ClassLoader的defineClass方法将该byte数组转换为Class对象即完成加载
                 **/
                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;
        }
    }

从代码实现中我们也可以看出,类加载器优先使用父加载器来加载类,当父加载器都无法加载时才使用当前类加载器来加载,当前类加载器调用ClassLoader类的findClass完成类的加载,而findClass方法在ClassLoader类中定义如下:

   protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

只抛出了一个异常,需要在我们自定义的类加载器中实现,在自定义类加载器需要继承ClassLoader类,findClass实现中首先加载类的二进制数据得到byte数组,然后调用defineClass方法将二进制数组转换为Class对象。defineClass的实现如下:

   protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }

通过调用native方法defineClass1byte数组转换为Class对象。

类加载器的命名空间

每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成,在同一个命名空间中,不会出现类完整名字(包括类的包名)相同的两个类,在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的的两个类。

自定义类加载器

自定义类加载器需要继承ClassLoader类,然后在构造方法中设置自定义ClassLoader类的父加载器,最后实现实现findClass方法即可,一个自定义的类加载器如下所示:

public class MyClassLoader extends ClassLoader {
    // 保存当前类加载器的名称
    private String classLoaderName;

    private final String classExt = ".class";

    private String path;

    public MyClassLoader(String classLoaderName) {
        // 调用super方法设置当前类加载器的父加载器为系统类加载器
        super();
        this.classLoaderName = classLoaderName;
    }

    public MyClassLoader(ClassLoader parent, String classLoaderName) {
        // 调用super方法设置当前类加载器的父加载器为parent
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    /**
     * 设置类加载的路径,从当前项目外加载类,否则加载当前项目中的类永远是父加载器
     * @param path
     */
    public void setPath(String path) {
        this.path = path;
    }

    @Override
    public String toString() {
        return "[" + classLoaderName + "]";
    }

    /**
     * 实现findClass方法,根据类名name加载类的二进制数据得到byte数组,并调用defineClass方法将byte数组转换为Class对象
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        return defineClass(name, classData, 0, classData.length);
    }

    /**
     * 根据类名name加载类的二进制数据
     * @param name
     * @return
     */
    protected byte[] loadClassData(String name) {
        System.out.println("MyClassLoader load class:" + name);
        byte[] data = null;
        // 将类的二进制名称转换具体类路径
        name = this.path + '/' + name.replace('.', File.separatorChar) + classExt;
        try(ByteArrayOutputStream baos = new ByteArrayOutputStream();
            InputStream fileStream = new FileInputStream(new File(name))) {
            int ch = 0;
            while (-1 != (ch = fileStream.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();
        }catch (Exception ex) {
            ex.printStackTrace();
        }
        return data;
    }

    public static void main(String[] args) throws Exception {
        /**
         * myClassLoader1首先在自己的命名空间中查找Singleton是否已经加载过,没有加载过则委托其父加载器即系统类加载器去加载Singleton
         * 类,系统类加载器同样在自己的命名空间查找Singleton类,同样没找到,委托其父加载器即启动类加载器去加载Singleton类,以此类推
         * 最后发现父加载器都无法加载Singleton类,所以myClassLoader1调用findClass方法去加载Singleton类
         */
        MyClassLoader myClassLoader1 = new MyClassLoader("MyClassLoader1");
        myClassLoader1.setPath("/Users/peter/Desktop");
        Class<?> clazz1 = myClassLoader1.loadClass("com.ctrip.flight.test.jvm.classloader.Singleton");
        System.out.println("clazz1 : " + clazz1.hashCode());
        System.out.println("clazz1 loader : " + clazz1.getClassLoader());

        System.out.println("=====================================================================================");

        /**
         * myClassLoader1是myClassLoader2的父加载器,当myClassLoader2去加载Singleton类时,会首先在自己的命名空间中查找Singleton
         * 是否已经加载过,由于myClassLoader2的命名空间已经包含了myClassLoader1的命名空间,而myClassLoader1已经加载了Singleton类,
         * 因此myClassLoader2直接在自己的命名空间中找到Singleton类的Class对象并直接返回
         */
        MyClassLoader myClassLoader2 = new MyClassLoader(myClassLoader1, "MyClassLoader2");
        myClassLoader2.setPath("/Users/peter/Desktop");
        Class<?> clazz2 = myClassLoader2.loadClass("com.ctrip.flight.test.jvm.classloader.Singleton");
        System.out.println("clazz2 : " + clazz2.hashCode());
        System.out.println("clazz2 loader : " + clazz2.getClassLoader());

        System.out.println("======================================================================================");

        /**
         * myClassLoader3加载Singleton的过程和myClassLoader1加载Singleton的过程一样,只不过myClassLoader3的命名空间和myClassLoader1
         * 的命名空间不一样
         */
        MyClassLoader myClassLoader3 = new MyClassLoader("MyClassLoader3");
        myClassLoader3.setPath("/Users/peter/Desktop");
        Class<?> clazz3 = myClassLoader3.loadClass("com.ctrip.flight.test.jvm.classloader.Singleton");
        System.out.println("clazz3 : " + clazz3.hashCode());
        System.out.println("clazz3 loader : " + clazz3.getClassLoader());
    }
}

运行结果如下:

MyClassLoader load class:com.ctrip.flight.test.jvm.classloader.Singleton
clazz1 : 1872034366
clazz1 loader : [MyClassLoader1]
=====================================================================================
clazz2 : 1872034366
clazz2 loader : [MyClassLoader1]
======================================================================================
MyClassLoader load class:com.ctrip.flight.test.jvm.classloader.Singleton
clazz3 : 1670675563
clazz3 loader : [MyClassLoader3]

类的卸载

当一个类被加载、链接和初始化后,它的生命周期就开始了。当代表该类的Class对象不再被引用,即不可触及时,该Class对象就会结束生命周期,该类在方法区内的数据也会被卸载,从而结束该类的生命周期。一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期

由java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载,java虚拟机自带的类加载器包括根加载器、扩展类加载器和系统类加载器。java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用他们所加载的Class对象,因此这些Class对象始终是可触及的。

由用户自定义的类加载器所加载的类是可以被卸载的。如下示例演示类被卸载:

public class MyClassLoader extends ClassLoader {
    // 保存当前类加载器的名称
    private String classLoaderName;

    private final String classExt = ".class";

    private String path;

    public MyClassLoader(String classLoaderName) {
        // 调用super方法设置当前类加载器的父加载器为系统类加载器
        super();
        this.classLoaderName = classLoaderName;
    }

    public MyClassLoader(ClassLoader parent, String classLoaderName) {
        // 调用super方法设置当前类加载器的父加载器为parent
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    /**
     * 设置类加载的路径,从当前项目外加载类,否则加载当前项目中的类永远是父加载器
     * @param path
     */
    public void setPath(String path) {
        this.path = path;
    }

    @Override
    public String toString() {
        return "[" + classLoaderName + "]";
    }
    /**
     * 实现findClass方法,根据类名name加载类的二进制数据得到byte数组,并调用defineClass方法将byte数组转换为Class对象
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        return defineClass(name, classData, 0, classData.length);
    }

    /**
     * 根据类名name加载类的二进制数据
     * @param name
     * @return
     */
    protected byte[] loadClassData(String name) {
        System.out.println("MyClassLoader load class:" + name);
        byte[] data = null;
        // 将类的二进制名称转换具体类路径
        name = this.path + '/' + name.replace('.', File.separatorChar) + classExt;
        try(ByteArrayOutputStream baos = new ByteArrayOutputStream();
            InputStream fileStream = new FileInputStream(new File(name))) {
            int ch = 0;
            while (-1 != (ch = fileStream.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();
        }catch (Exception ex) {
            ex.printStackTrace();
        }
        return data;
    }

    public static void main(String[] args) throws Exception {
        /**
         * 如下示例演示类被卸载
         */
        MyClassLoader myClassLoader4 = new MyClassLoader("MyClassLoader4");
        myClassLoader4.setPath("/Users/peter/Desktop");
        Class<?> clazz4 = myClassLoader4.loadClass("com.ctrip.flight.test.jvm.classloader.Singleton");
        System.out.println("clazz4 : " + clazz4.hashCode());
        System.out.println("clazz4 loader : " + clazz4.getClassLoader());

        myClassLoader4 = null;
        clazz4 = null;
        System.gc();

        myClassLoader4 = new MyClassLoader("MyClassLoader5");
        myClassLoader4.setPath("/Users/peter/Desktop");
        clazz4 = myClassLoader4.loadClass("com.ctrip.flight.test.jvm.classloader.Singleton");
        System.out.println("clazz4 : " + clazz4.hashCode());
        System.out.println("clazz4 loader : " + clazz4.getClassLoader());
    }
}

运行的时候加上-XX:+TraceClassUnloading参数,输出结果如下所示:

MyClassLoader load class:com.ctrip.flight.test.jvm.classloader.Singleton
clazz4 : 1872034366
clazz4 loader : [MyClassLoader4]
[Unloading class com.ctrip.flight.test.jvm.classloader.Singleton 0x00000007c0061028]
MyClassLoader load class:com.ctrip.flight.test.jvm.classloader.Singleton
clazz4 : 1670675563
clazz4 loader : [MyClassLoader5]

猜你喜欢

转载自blog.csdn.net/zkp_java/article/details/80042143