原文链接:https://blog.csdn.net/qq_32679835/article/details/92848424
类加载器
一、预定义类型类加载器
1、启动(Bootstrap)类加载器,负责将 <Java_Runtime_Home>/lib 下面的类库加载到内存中。
2、扩展(Extension)类加载器ExtClassLoader:负责将 < Java_Runtime_Home >/lib/ext 或者由系统变量 java.ext.dir 指定位置中的类库加载到内存中
3、系统(System)类加载器AppClassLoader:负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。
4、线程上下文类加载器ThreadContextClassLoader(TCCL):用于解决双亲委托模型的缺陷,可以实现核心库接口加载系统类(这一条先忽略)
二、类加载器结构
1、jvm加载的顺序:BoopStrap ClassLoder–>ExtClassLoader–>AppClassLoder 2、类加载器之间的关系:AppClassLoader的父加载器为ExtClassLoader,ExtClassLoader的父加载器为BoopStrap ClassLoader,BoopStrap ClassLoader为顶级加载器。
考虑一下类加载器的结构为什么会选择首先通过父类加载器去加载,只有父类无法加载了才会使用子类加载器去加载?(类加载器代理模式)
首先需要去明确一点,如果是相同的Java类,当使用不同的类加载器加载,并通过加载器获得的java.lang.Class实例化的对象,这两个实例化对象实不相等的。有了这个前提之后,在java中会具有一些核心类,如果去使用自身类加载器,实例化后就会造成这些类之间不兼容。因此会选择代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。
根据类加载器流程图,当需要查找一个class对象时候,由于类加载机制只要加载过该类,就不需要去重新加载,只需要查找缓存
1、缓存路:查找自身加载器是否有缓存,没有则委托父类AppClassLoader加载器---->查找AppClassLoader加载器是否有缓存,没有则委托父类ExtClassLoader---->查找ExtClassLoader加载器是否有缓存,没有则委托BoopStrap加载器–>查找BoopStrap加载器是否有缓存,没有则开始加载(在任何一个加载器中该类已经加载,则直接返回)
2、加载路:BoopStrap在核心库中加载,如果未加载成果---->ExtClassLoader在lib/ext中加载,如果未加载成果----->AppClassLoader在当前classpath中加载,如果未加载成果---->自定义加载器加载,如果未加载成果---->抛出异常ClassNotFoundException
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先,检查是否已经加载过 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { //父加载器不为空,调用父加载器的loadClass c = parent.loadClass(name, false); } else { //父加载器为空则,调用Bootstrap Classloader 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(); //父加载器没有找到,则调用findclass 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() resolveClass(c); } return c; } }
四、双亲委托模型的破坏
1、第一次破坏
第一次模型的破坏就是指在jdk1.2之前,因为此时还没有引入双亲委托模型,用户在自定义类加载器的时候需要去重写loadclass(),因为虚拟在进行类加载的时候会调用加载器的私有方法loadClassInternal(),而这个方法的唯一逻辑就是去调用自己的loadClass()。jdk1.2之后就不提倡用户去重写loadclass(),类加载逻辑写到findclass()中,保证新写出的类符合双亲委托模型。
2、第二次破坏
第二次的破坏是由于双清委托模型自身的缺陷导致,Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。
这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器(Bootstrap Classloader)来加载的;SPI的实现类是由系统类加载器(System ClassLoader)来加载的。引导类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader无法委派AppClassLoader来加载类。
引用
【1】双亲委派模型流程图:https://www.cnblogs.com/gdpuzxs/p/7044963.html
【2】ExtClassLoader和AppClassLoader继承关系图:http://www.360doc.com/content/16/0614/10/7510008_567634519.shtml
【3】ExtClassLoader和AppClassLoader实现差异性:
loaderClass与findClass区别:https://blog.csdn.net/caomiao2006/article/details/47735245
【4】比较好:http://www.blogjava.net/zhuxing/archive/2008/08/08/220841.html
【5】第二次破坏,线程上下文类加载器:https://blog.csdn.net/yangcheng33/article/details/52631940
原文:https://www.cnblogs.com/manmanchanglu/p/12437383.html