1. 使用类加载器加载类的过程

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;
        }
    }

1.1 Class<?> c = findLoadedClass(name);

1.2 parent.loadClass(name, false);

1.3 c = findClass(name);

2. 自定义类加载器

以下是自定义的类加载器

package jvmtest;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.text.MessageFormat;
import java.util.Optional;

class CustomClassLoader extends ClassLoader {


    /**
     * 类加载器名称
     */
    private String classLoaderName;

    /**
     * 类加载器根目录
     */
    private String path;

    private static final String suffixName = ".class";

    /**
     * 默认以systemClassLoader为父类加载器
     *
     * @param classLoaderName
     */
    public CustomClassLoader(String classLoaderName) {
        super();
        this.classLoaderName = classLoaderName;
    }

    /**
     * 使用传入的classlaoder作为其双亲
     *
     * @param parent
     * @param classLoaderName
     */
    public CustomClassLoader(ClassLoader parent, String classLoaderName) {
        super(parent);
        this.classLoaderName = classLoaderName;
    }


    /**
     * Finds the class with the specified <a href="#name">binary name</a>.
     * This method should be overridden by class loader implementations that
     * follow the delegation model for loading classes, and will be invoked by
     * the {@link #loadClass <tt>loadClass</tt>} method after checking the
     * parent class loader for the requested class.  The default implementation
     * throws a <tt>ClassNotFoundException</tt>.
     *
     * @param name The <a href="#name">binary name</a> of the class
     * @return The resulting <tt>Class</tt> object
     * @throws ClassNotFoundException If the class could not be found
     * @since 1.2
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        System.out.println("findClass..");
        byte[] bytes = this.loadClassData(name);
        return this.defineClass(name, bytes, 0, bytes.length);
    }


    /**
     * 加载指定className对应的文件,返回对应的数据,这里注意使用 read()读取class文件内容时,只能一个一个字节的读取,不能一次多个。
     *
     * @param className
     * @return
     */
    private byte[] loadClassData(String className) {
        className = className.replace(".", "/");
        byte[] readDatas = null;
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             FileInputStream fileInputStream = new FileInputStream(new File(Optional.ofNullable(this.path).orElse("") + className + suffixName));) {

            int result;
            while ((result = fileInputStream.read()) != -1) {
                byteArrayOutputStream.write(result);
            }

            /*byte[] datas = new byte[1];
            while (fileInputStream.read(datas) != -1) {
                byteArrayOutputStream.write(datas);
            }*/
            readDatas = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return readDatas;
    }

    @Override
    public String toString() {
        return MessageFormat.format("[{0}]", this.classLoaderName);
    }

    public void setPath(String path) {
        this.path = path;
    }
}

测试类

package jvmtest;

/**
 * @author ztkj-hzb
 * @Date 2019/12/13 14:48
 * @Description
 */
public class Test {


    public static void main(String[] args) throws ClassNotFoundException {

        CustomClassLoader classLoader = new CustomClassLoader("customClassLoader");
        Class<?> loadClass = classLoader.loadClass("jvmtest.Test1");
        System.out.println(loadClass.getClassLoader());


    }

}

执行测试类,会看到一下结果

sun.misc.Launcher$AppClassLoader@18b4aac2

从结果上看,加载该类的类加载器并不是我们写的自定义的类加载器,而是系统类加载器。因为代码里加载的类 "jvmtest.Test1" 在项目中,会被AppClassLoader所加载,那么怎么使用自己写的类加载器加载该类呢。

3. 如何让自定义类加载器加载执行路径下的指定类

以下是新的测试类

public static void main(String[] args) throws ClassNotFoundException {

        CustomClassLoader classLoader = new CustomClassLoader("customClassLoader");
        //需要指定类加载器是从什么路径下寻找指定类文件的
        classLoader.setPath("D:/");
        Class<?> loadClass = classLoader.loadClass("jvmtest.Test1");
        System.out.println(loadClass.getClassLoader());
    }

执行测试类,得出以下的结果

findClass..
[customClassLoader]

从结果上看可以得出,Test1类是被我们自定义的类加载器所加载的。分析一下,为什么?
首先,这里我们在构建 CustomClassLoader 类加载器的时候,没有指定parentClassLoader是什么类加载器,这里会调用super(name),那么ClassLoader会默认使用AppClassLoader来作为默认的类加载器。
所以,这里会先被AppClassLoader加载,由于双亲委托机制的存在,会逐渐的由扩展类,启动类等类加载器加载,而我们可知,该类最终会被AppClassLoader类尝试加载,但是由于上面,我们将Test1.class文件在执行的编译文件中删除掉了,导致运行中AppClassLoader找不到该类,则会让我们自定义的类加载器来尝试进行,这时候,查看源码可以得知,会触发了loadClass()的第三步,findClass(),由于自定义的类加载器重写了findClass()方法,就进入了我们的findClass()方法,即会打印出我们的结果第一行数据, findClass....
随后,在指定path目录下找到了指定文件,获取内容后,交由ClassLoader封装成Class对象,返回。

4. 数组不同于其他数据类型,加载数组类型的类加载器是谁呢?

举例如下:

package jvmtest;

/**
 * @author ztkj-hzb
 * @Date 2019/12/9 13:56
 * @Description 针对数组类型而言,数组类型的类加载器是有其对象类型对应的类加载器来决定的,例如 Xxx[] 的类加载器是有Xxx类的类加载器来决定的。
 *                  针对Integer[] 和 int[]也有区别,虽然结果都是null,但是Integer是有启动类加载器加载,所以返回null,而int是原生类型,原生类型没有类加载器,所以
 *                  返回null
 */
public class Test14 {


    public static void main(String[] args) {

        System.out.println(Object[].class);
        System.out.println(Object[].class.getSuperclass());


        int[] intArr = new int[5];
        System.out.println(intArr.getClass().getClassLoader());

        Integer[] integersArr = new Integer[5];
        System.out.println(integersArr.getClass().getClassLoader());

        MyTest14[] myTest14s = new MyTest14[5];
        System.out.println(myTest14s.getClass().getClassLoader());

    }


}

class MyTest14{


}

5. 关于类加载器的父子加载区别

5.1 代码原始版本

package jvmtest;

/**
 * @author ztkj-hzb
 * @Date 2019/12/12 17:09
 * @Description
 */
public class MySample {


    public MySample(){
        System.out.println("MySample类加载器:" + this.getClass().getClassLoader());
        //实例化MyCat
        new MyCat();
    }

}
package jvmtest;

/**
 * @author ztkj-hzb
 * @Date 2019/12/12 17:09
 * @Description
 */
public class MyCat {

    public MyCat(){
        System.out.println("MyCat类加载器:" + this.getClass().getClassLoader());
    }

}
package jvmtest;

/**
 * @author ztkj-hzb
 * @Date 2019/12/12 11:47
 * @Description
 */
public class Test16 {


    public static void main(String[] args) throws Exception {


        MyTest15ClassLoader classLoader = new MyTest15ClassLoader("loader1");
        classLoader.setPath("D:/");
        Class<?> loadClass = classLoader.loadClass("jvmtest.MySample");
        System.out.println("classLoader: " + loadClass.getClassLoader());

        //实例化MySample类
        Object instance = loadClass.newInstance();

    }


}

结果如下:

classLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
MySample类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
MyCat类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2

由结果分析可知,因为创建 MyTest15ClassLoader 类的时候没有指定父加载器,所以这里使用默认的父加载器(系统类加载器)。而 MySample类本来就在当前项目编译的class中存在,所以,这里使用的是系统类加载器加载,没有用到自定义的类加载器。

5.2 变动1:在当前项目的编译的文件夹中删除MySample.class文件

在当前项目的编译的文件夹中删除MySample.class文件,再次执行代码,得到如下结果:

findClass..
classLoader: [loader1]
MySample类加载器:[loader1]
MyCat类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2

由结果分析可知,由于在当前Runtime环境中,删除了MySample.class文件,所以首先通过系统类加载器尝试加载该类,找不到,则会使用到自定义的类加载器尝试加载,只要在指定目录中放入MySample.class文件,由自定义加载器找到且加载编译成Class对象,因此可以得到输出结果的前两条数据。然后因为调用了 newInstance()实例化方法,触发了 MySample类的初始化,执行构造方法,于是输出了第三条数据,在MySample类的构造方法中,触发了MyCat类的初始化,导致自定义类加载器开始加载MyCat类,同理,首先会尝试使用系统类加载器来加载MyCat.class,由于当前Runtime环境中,存在MyCat.class文件,所以,会被系统类加载器所加载该类,因此输出了最后一条数据。
这里体现出了,可以由子类加载器加载父类加载器的类。

5.3 变动2:在当前项目的编译的文件夹中删除MyCat.class文件

在当前项目的编译的文件夹中删除MyCat.class文件,再次执行代码,得到如下结果:

classLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
MySample类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
Exception in thread "main" java.lang.NoClassDefFoundError: jvmtest/MyCat
    at jvmtest.MySample.<init>(MySample.java:14)
    at java.lang.Class.newInstance(Class.java:442)
    at jvmtest.Test16.main(Test16.java:20)
Caused by: java.lang.ClassNotFoundException: jvmtest.MyCat
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 7 more

由结果分析可知,首先,在Runtime环境中,存在MySample.class文件,所以必然会被系统类加载器所加载,即出现第一行数据,执行 newInstant方法后,触发MySample类的初始化,输出第二行数据,然后在MySample类的构造方法中触发了MyCat类的初始化,即使用当前的类加载器加载MyCat类,但是在之前,已经将MyCat.class文件从Runtime环境中删除了,所以在系统类加载器对应的命名空间中,找不到MyCat类,就会导致类找不到异常。因此报错。
这里体现出了,不能由父类加载器加载子类加载器命名空间的类。这就体现出了命名空间的特性。

5.4 变动3:在当前项目的编译的文件夹中同时删除MySample.class和MyCat.class文件

在当前项目的编译的文件夹中同时删除MySimple.class和MyCat.class文件,再次执行代码,得到如下结果:

findClass..
classLoader: [loader1]
MySample类加载器:[loader1]
findClass..
MyCat类加载器:[loader1]

由结果分析可知,首先,在Runtime环境中,不存在MySample.class文件,所以会被被自定义类加载器所加载,即出现第一行,第二行数据,执行 newInstant方法后,触发MySample类的初始化,出现第三行,同理,使用自定义类加载器加载MyCat.class,根据双亲委托机制,在系统类加载器命名空间中无法找到MyCat类,因此还是由自定义类加载器加载,出现第四行和第五行数据。

12-20 02:59
查看更多