JVM学习笔记(七)-类加载器

回顾

  上一篇博客复习了JVM的类加载过程,对加载、验证、准备、解析、初始化等各个阶段进行了详细内容的复习。但是,关于类加载,还有一块重要的知识——类加载器,它跟加载阶段、解析阶段等有着紧密的联系。
  本篇博客将会对类加载器的分类、双亲委派模型以及打破双亲委派等方面进行复习。

类加载器的分类

  我们常把类加载器分为四类:

  1. 启动类加载器(Bootstrap ClassLoader)
  2. 扩展类加载器(Extension ClassLoader)
  3. 系统类加载器(Application ClassLoader)
  4. 自定义类加载器

加载范围

  启动类加载器:加载<JAVA_HOME>/lib/*.jar,也就是jre文件夹下lib中的jar包,如rt.jar。此外,它还可以加载参数-XBootstrapPath指定的路径的jar包。

  扩展类加载器:加载<JAVA_HOME>/lib/ext/*.jar。
  系统类加载器:加载classpath下的jar包,也就是我们平常写java程序时,build path的jar包。一般情况下,我们自定义的类和第三方包都是由该加载器加载的。

双亲委派模型

类加载器的关系

类加载器之间的关系

  启动类加载器是JVM内置的,使用C++程序编写,并不是扩展类加载器的父类。扩展类加载器是应用类加载器的父类。

/*
测试类加载器的关系
*/
package classloadtest;

public class ClassLoaderRelationTest {

	public static void main(String[] args) {
		//获取应用类加载器
		ClassLoader c1 = ClassLoaderRelationTest.class.getClassLoader();
		//查看应用类加载器的父类
		ClassLoader c2 = c1.getParent();
		//查看应用类加载器的父类是否有父类
		ClassLoader c3 = c2.getParent();
		
		System.out.println(c1);
		System.out.println(c2);
		System.out.println(c3);
	}

}

输出结果如下:

运行结果

  可以看到自定义的类ClassLoaderRelationTest是由Application ClassLoader加载的,它的父类加载器为Extension ClassLoader,而再往上就为null了,说明Bootstrap ClassLoader并不是它的父类加载器。

双亲委派模型

  在类加载器加载类的过程中,首先会将加载任务委托给该加载器的父类加载器,每一层都这样,直到交给Extension ClassLoader,这时它的父类加载器为null,然后交给Bootstrap ClassLoader。如果上层的类加载器无法加载这个类,就逐级往下又将加载任务交给子类加载器来完成(在代码中即调用classLoader的findClass方法)。
过程很简单,上ClassLoader的源码,一看就明白了!

    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);  //父类为null就给启动类加载器
                    }
                } 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);   //调用自己的findClass方法,自己来加载

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

打破双亲委派模型

第一次打破

  第一次打破双亲委派模型在JDK1.2之前,那个时候,人们继承ClassLoader的唯一目的就是重写loadClass方法,也就是我们上面贴的源码(当然相比1.2有了很多改动,特别是JDK1.2之前并没有findClass方法)。这里有两点需要注意:

人们为什么要重写loadClass方法呢?

  因为JVM内部在加载类时,会调用类加载器的私有方法loadClassInternal,这个方法的唯一逻辑就是去调用自己的loadClass方法。

为什么不说这样就打破了双亲委托机制呢?

  在我们上面的源码中可以看到,双亲委托机制就是靠loadClass方法的内部逻辑来实现的,如果你重写loadClass方法,还有这个逻辑吗?

官方解决办法

  在JDK1.2以后,官方引进了findClass方法,推荐用户不要重写loadClass方法,而去重写findClass方法。(但是如果你执意要重写loadClass方法,仍然可以重写)。个人认为,这有点像借鉴了设计模式中的模板方法模式,即,我将算法框架固定住了,你只用实现里面的某个具体逻辑就行了。

第二次打破

  第二次打破双亲委派机制的是SPI(Service Provider Interface),服务提供接口,JavaSPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。我们常见的JDBC就是典型。

SPI为什么要打破双亲委托机制呢?

  以JDBC为例,MySQL的驱动包实现了SPI的Driver接口,DriverManager需要加载Driver接口的实现类,然而DriverManager是有启动类加载器加载的,上篇博客中写到的类和接口的解析过程,在C类中有一个D类的符号引用需要解析,C类会首先判断D类是否被加载,若没有,则先使用C类的加载器去加载D类。所以这里会使用启动类加载器去加载MySQL的驱动包,这个包根本不在bootstrap classloader的加载路径下!!所以无法加载成功。这就是《深入理解java虚拟机》中说的基础类中回调了用户代码

官方如何解决?

  官方引入了一个线程上下文加载器(contextClassLoader),这个可由java.lang.Thread类使用getClassLoader获取和setClassLoader设置,如果没有设置,那么就是从父线程中继承一个。如果程序的全局范围内都没有设置,则默认为application classLoader。这样,在DriverManager中就可以调用getContextClassLoader获取线程上下文类加载器来加载Driver的实现类了!

第三次打破

  在所谓的“热部署”、“热替换”中,人们想实现插件式的代码,比如OSGI中每一个模块(bundle)都有自己的一个类加载器。

总结

  到此就将类加载的相关基础知识复习完了!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值