Java类加载器、双亲委派机制及作用、加载不到类所报异常ClassNotFoundException

一、类加载器(Class Loader)

1、类加载器的分类

启动类加载器(根加载器)(BootstrapClassLoader); ② 扩展类加载器(ExtClassLoader);
应用程序类加载器(系统类加载器)(AppClassLoader);④ 自定义类加载器(Custom).

在这里插入图片描述

2、类加载器的作用:
(1)加载字节码:将由.Java文件通过编译器编译而成的.class加载到JVM中,以此在元空间(JDK8)中形成该类的类模板信息;
(2)创建Class对象:在JVM中有了字节码后,会在堆空间中通过字节码来创建对应的Class对象,只有有了Class对象后,才可以通过new关键字、反射机制、工厂Factory、静态内部类等方式创建实例对象,以上创建对象的方式本质都是通过无参构造器来创建对象的。(JVM调用< init > 方法

3、双亲委派机制图解
在这里插入图片描述
4、其实这四个类加载器也可以分为两大类:一类是启动类加载器,另一类是自定义加载器(这里的自定义指的是java语言自定义)
(1)启动类加载器(C++层面):这个是最顶级的类加载器,JVM启动就首先启动该类加载器,然后启动类加载器会去启动扩展类加载器和系统类加载器等。它是C++实现,所以我们在Java层面是获取不到的。
在这里插入图片描述

public static void main(String[] args) {
        // 1、获取系统类加载器---可以获取到
        //  (1) 方式一 : 使用当前线程获取
        System.out.println(Thread.currentThread().getContextClassLoader());
        //  (2) 方式二 : 使用Classloader的静态方法获取
        System.out.println(ClassLoader.getSystemClassLoader());
        //  (3) 方式三 : 使用当前类对象的Class实例获取(因为当前的类的Class实例就是使用系统类加载器加载并生成的)
        System.out.println(CustomClassLoaderTest.class.getClassLoader());
        // 2、获取扩展类加载器-这里第一点的其中一种方式获取---可以获取到
        System.out.println(ClassLoader.getSystemClassLoader().getParent());
        // 3、获取启动类加载器---获取不到---类似于调用native方法
         //  (1) 方式一 : 使用系统类加载器获取
        System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
         //  (2) 方式二 : 使用反射拿到类对象获取
        Class<?> Clazz = Class.forName("java.lang.String");
        System.out.println(Clazz.getClassLoader());
        // 注意:基本数据类型是没有类加载器的,基本数据类型不需要类加载器,如:
        int arr[] = new int[10];  // 此时的返回也为 null,但是这个 null 指的是没有类加载器,而不是获取达到
        System.out.println("-----" + arr.getClass().getClassLoader());
    }

(2)自定义加载器(Java层面,都是直接或间接地继承自ClassLoader类),包括扩展类加载器、系统类加载器和自定义加载器(Java层面实现ClassLoder或URLClassLoader接口)

  • Java层面自定义类加载器的继承关系图如下。所以ExtClassLoader和AppClassLoader间接继承自ClassLoader,但是ExtClassLoader、AppClassLoader和BootstrapClassLoader并没有什么继承关系,这些加载器之间是一种包含关系,包含着上层加载器的引用
    在这里插入图片描述
  • Java源码位置:sun.misc.Launcher$ AppClassLoader和sun.misc.Launcher $ExtClassLoader
    在这里插入图片描述

二、双亲委派机制的意义

1、防止类的重复加载

防止内存中出现多份同样的字节码。试想,如果没有双亲委派机制模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,多个类加载器都会去加载这个类到内存中,系统中将会出现多个不同的Object类,那么类之间的比较结果及类的唯一性将无法保证。

2、保证程序安全,防止核心API被篡改

由于所有的用户类都会先通过BootStrap ClassLoder(启动类加载器)查看里面有没有该类资源,有则直接安装或者加载,从而保证了底层的类一定是预先加载的,这样可以对虚拟机的安全得到了很好的保证。

三、如何破坏双亲委派

首先要知道双亲委派代码在 loadClass 方法中,只需要绕开 loadClass 方法即可。

1、SPI 机制绕开 findClass方法,当前线程设定关联类加载器。

SPI 机制:简单地说就是自动加载文件里所定义的类,SPI 机制为很多框架扩展提供了可能,比如说像Dubbo、JDBC中都使用到了 SPI 机制。

2、自定义类加载器,重写 findClass 方法(推荐);重写 loadClass 方法(jdk1.2的方式,自定义加载器时不推荐重写这个方法)
在这里插入图片描述

四、一个异常、一个错误

1、ClassNotFoundException(在JVM运行期间动态地通过类全限定名去加载类路径下的某些类时,在该类路径下没有找到这些类,就会抛出该异常,例如:使用像Class.forName方法去动态的加载某些类时,找不到这些类的字节码)-- 可以由程序员调用;

NoClassDefFoundError(编译时和运行时环境不一致导致。某些类在编译期间被编译器正常的编译,但是在JVM运行起来去加载这些类进内存时却找不到这些类的定义字节码,也就是说,这些类在字节码层面上有符号引用,但是在转化为直接引用时,去加载这个类进内存过程中找不到这个类)-- 由JVM调用

小结
这一个异常,一个错误的相同点:都是找不到类抛出异常或者直接报错中止程序运行;

本质区别:侧重点不太相同。
ClassNotFoundException 侧重的是在类加载器加载阶段将字节码加载进内存过程中找不到类指定的类信息;
NoClassDefFoundError 侧重在运行使用阶段将符号引用转化为直接引用时,找不到要使用new关键字实例化依赖的类,这个依赖类可能过程编译后就不存在了,也可能是编译后还存在但是在初始化时失败了。

2、产生原因

ClassNotFoundException:

(1)可能是您用了不该用的类名,比如说自定义的类,您使用了Java类库中本来就有的类名了,这样类加载器是不会加载您自定义的类的;也就是当一个类已经某个类加载器加载到内存中了,此时另一个类加载器又尝试着动态地从同一个包中加载这个类就会报ClassNotFoundException这个异常。

(2)假如您是在框架中报这个异常的话,那么可能是加载的路径和注解不对,或者是您没有将类放在它应该所在的位置(有的类只有在对的位置,才会被JVM加载到)

(3)像使用 Class.forName、xxxClassLoader中的loadClass方法动态的去加载某一个类时,没有找到该类也会报这异常。

在这里插入图片描述
在这里插入图片描述
NoClassDefFoundError:

举个例子就是:假如有A、B两个类,B类中要创建并使用A 类,先编译这两个类,生成了A、B两个类的字节码,再删除A.class,后启动JVM运行B类,此时JVM会将B类中对A的符号引用转化为直接引用时是找不到已经被删除的这个A类的定义的,那么就会报NoClassDefFoundError这个错误,该错误是JVM引起的,故不应该尝试捕捉这个错误。
在这里插入图片描述
为什么一个是异常,一个错误呢?

我觉得是因为JVM认为都已经被编译器成功编译了,符号引用转为直接引用肯定是没有问题的,所有B类中再去创建A肯定也是可以,但是这在个过程中却没有找到A,那么这个问题就很严重了,直接由JVM报 NoClassDefFoundError 错误,而不会简简单单抛出异常了。有异常程序可能会恢复运行,但是有错误程序就不应该再继续运行下去了。

有用点个关注,手留余香! ? ? ?


版权声明:本文为qq_43012792原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。