项目终于上线,可以有空闲时间继续写博客了。时隔一年,从新看 java 的一些基础知识,发现对于类加载和双亲委派这方面的理解,更明白了一些(具体原因是以前源代码看不下去…),还是整理一下好了。
ps(本文主要分析一下双亲委派机制)
首先大家都理解,运行java程序,底层是由 C++ 实现的,那么 java 执行命令运行代码壶进行以下几个步骤:
要想了解 双亲委派机制 还是要谈谈上面提到的类加载器。
java 有以下几种类加载器:
类加载器的初始化过程也是这次我才去看源代码,才发现,哦~是这个样子,不然之前书上纯理论还是有点太片面,跟一下代码还是有必要的。
首先 C++ 会调用到 sun.misc.Launcher.getLauncher() 进行初始化
很明显可以看到,这本质上是一个单例模式,那么接下来我们观察一下它的构造方法。
首先我们先关注一下第一个 getExtClassLoader() 方法,这个方法是加载扩展类加载器
createExtClassLoader() 用于加载扩展类加载器以及校验,我们跟进去看。
new Launcher.ExtClassLoader 这就是真正加载的地方。
第一个参数是文件路径,在 getExtClassLoader() 中,我们找到 getExtDirs() 方法进行查看,看看到底传了什么路径。
由此我们可以知晓 扩展类加载器加载了 JRE的lib目录下的ext扩展目录中的JAR类包。
第二个参数是用于双亲委派机制的父节点,这点留着稍后讲解,先将代码附上。
最终会调用到父类 ClassLoader
至于为什么在进行加载扩展加载器的时候会转null呢,因为这边的父类是 引导类加载器,而引导类加载器又是由C++生成的,所以这边传null。
第三个参数是当创建Url的时候使用,初始化在Launcher实现。
接下来我们来看第二个方法:
Launcher.AppClassLoader.getAppClassLoader()
但是这边我们注意,它将实例好的扩展类加载器传入了本身,我们来看看它做了什么
首先这边需要进行以下描述,这边的 super调用的方法和 类加扩展加载器 调用的super一致,同样是调用
此方法。
我们继续分析一下三个传参。
第一个传参依旧是路径,此路径加载了我们平常代码自定义的类,看看,多熟悉的 java.class.path 啊
第二个传参将上一步生成的 类扩展加载器 传入,作为此加载器的父节点,用于接下来的双亲委派机制的实现
第三个传参与上雷同。
先上网友的图 ps(图画的真好)
抛开自定义加载器不谈,我们先来一波理论知识讲一下双亲委派机制。
首先我们自己写了一个A类,此类在进行加载的时候,会先进入到应用程序加载类查看加载了没有,如果没有,会把这个类向上委托,看看扩展类加载器加载了没有,要是还没有,就继续向上委托,最终看看引导类加载器加载了没有,如果引导类加载器也没有加载,那么就在自己的加载类路径下看看有没有这个类,有就进行加载,没有就让下一个节点扩展类加载器进行加载,最终会让应用程序类加载器进行加载(如果没有自定义加载器的话),要是还是找不到,就会抛出ClassNotFound异常。
其实可以用一句话总结:先找父节点加载,不行再由子节点加载。
注意点:这边说的都是父节点子节点,并不是类,不是继承关系!!!!
接下来看看源码。
当加载的时候,最后都会运行到ClassLoader的loadClass方法
此方法是实现双亲委派机制的核心代码,主要逻辑有三点。
第一点:findLoadedClass()
查看此类是否被当前加载器加载,若果是,则返回,如果不是,就找寻父节点进行判断是否被加载过。
ps(此处逻辑简单清晰,不推荐深究下面的源代码,下面由C++实现,深究有风险(崩溃…))
第二点:当此加载器并没有加载这个类的时候,找寻父节点进行判断。
首先,你得有父节点,所以此处的父节点就是前面所讲 类加载扩展器和应用程序类加载器的 第二个传参的对象。只有一种情况父节点会是 null 那就是此处的加载器是 类加载扩展器,这时候就会调用 findBootstrapClassOrNull() 进行寻找。
第三点:如果所有的类加载器都没有加载过,那么就需要判断当前加载类的加载路径中有没有这个类。
由于第二点判断存在与否,若不存在会寻找父节点进行判断,所以若最终进入到此步骤,就只能说明所有的类加载器都没有加载过,此时会从 顶 节点加载器开始判断本加载器路径是否存在此类。我们跟寻findClass进行查看
若最终查询不到,将会抛出异常,ClassNotFoundException。
ps(此处逻辑代码也非常清晰,不推荐继续往下抛,下面就是加载的详细过程)
双亲委派机制其实逻辑很简单,但是书上的理论和实际刨代码的理解毕竟还是有点不同。
为什么要设计双亲委派机制?
为什么有人打破双亲委派?
比如 Tomcat 就是打破了双亲委派,因为它是一个web容器,需要部署多个运用程序,为了避免不同程序依赖版本不一样的第三方库导致的问题,所以需要打破它。