JVM类加载器

类加载器的作用不仅仅是实现类的加载,它还与类的的“相等”判定有关,关系着Java“相等”判定方法的返回结果,只有在满足如下三个类“相等”判定条件,才能判定两个类相等。

1、两个类来自同一个Class文件

2、两个类是由同一个虚拟机加载

3、两个类是由同一个类加载器加载

JVM类加载器

启动类加载器(bootstrap classLoader):启动类加载器,负责加载java的核心类库,加载如(%JAVA_HOME%/lib)下的rt.jar(包含System,String等核心类)这样的核心类库。根类加载器不是classLoader的子类,它是JVM自身内部由C/C++实现的,并不是Java实现的。

扩展类加载器(Extension classLoader):扩展类加载器,负责加载扩展目录(%JAVA_HOME%/jre/lib/ext)下的jar包,用户可把自己开发的类的jar放入ext目录下,即可扩展除核心类以为的新功能。

系统类加载器(Application classLoader):系统类加载器或称为应用程序类加载器,是加载CLASSPATH环境变量所指定的jar包与类路径。一般来说,用户自定义的类就是由APP ClassLoader加载的。

类加载器的双亲委派模型机制

当一个类收到了类加载的请求,他首先不会自己尝试加载这个类,而是将这个请求委派给父类加载器来完成,父类加载器收到请求后,也会找到找到其父类加载器。所以类加载的请求都会传到bootstrap classLoader,只有当父类加载器无法加载时(在其类加载路径中找不到所需加载的class),子类加载器才会尝试自己去加载。

这个过程如下图标号过程所示:(借图,哈哈)

双亲委派的源码实现,先检查是否有被加载过,如果没有,则调用父类的类加载方法,若父类加载器为空,则尝试通过bootstrap classLoader来加载,如果还是未加载成功,则调用自身的加载方法,

    protected synchronized Class loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        // 首先检查该name指定的class是否有被加载
        Class c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    // 如果parent不为null,则调用parent的loadClass进行加载
                    c = parent.loadClass(name, false);
                } else {
                    // parent为null,则调用BootstrapClassLoader进行加载
                    c = findBootstrapClass0(name);
                }
            } catch (ClassNotFoundException e) {
                // 如果仍然无法加载成功,则调用自身的findClass进行加载
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }

  双亲委派模型验证

public class ClassLoaderTest {

    public static void main(String[] args){
//输出ClassLoaderTest类加载的名称
        System.out.println("ClassLoaderTest class loader is "+ClassLoaderTest.class.getClassLoader().getClass().getName());
//输出System类的类加载器
        System.out.println("System class loader is "+System.class.getClassLoader());
//输出List类的类加载器
        System.out.println("List class loader is "+List.class.getClassLoader());

        ClassLoader c1=ClassLoaderTest.class.getClassLoader();
        while(c1!=null){//递归获取类加载器的父类加载器

            System.out.print(c1.getClass().getName()+"->");
            c1=c1.getParent();
        }

        System.out.println(c1);

    }
}

  输出结果为:

ClassLoaderTest class loader is sun.misc.Launcher$AppClassLoader
System class loader is null
List class loader is null
sun.misc.Launcher$AppClassLoader->sun.misc.Launcher$ExtClassLoader->null

解释:

ClassLoaderTest的类加载器为AppClassLoader,即ClassLoaderTest类是用户定义的类,位于CLASSPATH下,由系统/应用程序类加载器加载

System与List为核心类,有boostrap classLoader加载,而启动类加载器是在JVM内部通过C/C++实现的,并不是通过Java,自然也就不能继承classLoader类,所有不能输出其名称。

箭头代表的为类加载器的委托过程,委托到ExtClassLoader,再委托到启动类加载器,最后通过AppClassLoader加载成功。

做个假设,如果将ClassLoaderTest类打包好放入到lib/ext目录下,输出结果会变成什么样?

第一个输出应该就为扩展类加载器二不是应用程序类加载器

最后一个输出应该为 sun.misc.Launcher$ExtClassLoader->null

解释:

jar包放入到ext目录下后,通过双亲委派模型,启动类加载器加载失败后,会有扩展类加载器加载,扩展类加载器加载成功后就不需要再继续加载了。

自定义加载类

若要实现自定义类加载器,只需要继承java.lang.ClassLoader 类,并且重写其findClass()方法即可。java.lang.ClassLoader 类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class 类的一个实例。除此之外,ClassLoader 还负责加载 Java 应用所需的资源,如图像文件和配置文件等,ClassLoader 中与加载类相关的方法如下:

 
方法                                 说明
getParent()  返回该类加载器的父类加载器。

loadClass(String name) 加载名称为 二进制名称为name 的类,返回的结果是 java.lang.Class 类的实例。

findClass(String name) 查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。

findLoadedClass(String name) 查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class 类的实例。

resolveClass(Class<?> c) 链接指定的 Java 类。

能不能自己写个类叫java.lang.System?

答案:通常不可以。 
解释:类加载器的双亲委派模型,即使实现System类,但是加载的时候还是会有启动类加载器先加载,而启动类加载器会加载自带的System类,所以自己写的类就不会被加载。

但是,我们可以自己定义一个类加载器来达到这个目的,为了避免双亲委托机制,这个类加载器也必须是特殊的。由于系统自带的三个类加载器都加载特定目录下的类,如果我们自己的类加载器放在一个特殊的目录,那么系统的加载器就无法加载,也就是最终还是由我们自己的加载器加载。

原文地址:https://www.cnblogs.com/dpains/p/7201554.html