再次认识ClassLoader

xiaoxiao2021-02-28  23

通读这篇文章你会知道如何回答以下问题:

Java自带的三大加载器加载的jar位置都是在哪里?三大加载器之间的关系是怎么样的? 在代码中是如何体现的?双亲委派模型是什? 代码中如何体现这种模式的应用? 这种模式的不足是什么?上下文加载器存在的作用是什么?应用的场景有哪些?

java三大加载器加载的jar位置

知道每个加载器加载什么位置的jar,这对后面分析委托机制会起到作用。Java语言自带的有三个类加载器

Bootstrap CLassloder

Extention ClassLoader

AppClassLoader以下是输出各个加载器的加载jar的位置,这些路径可以通过虚拟机参数进行修改。

public static void testClassLoader() { System.out.println("BootstrapClassLoader:"); String property = System.getProperty("sun.boot.class.path"); Arrays.stream(property.split(";")).forEach(System.out::println); System.out.println("ExtClassLoader :"); property = System.getProperty("java.ext.dirs"); Arrays.stream(property.split(";")).forEach(System.out::println); System.out.println("AppClassLoader :"); property = System.getProperty("java.class.path"); Arrays.stream(property.split(";")).forEach(System.out::println); } Bootstrap CLassloder 加载位置如下:JDK\jdk1.8\jre\lib\resources.jarJDK\jdk1.8\jre\lib\rt.jarJDK\jdk1.8\jre\lib\sunrsasign.jarJDK\jdk1.8\jre\lib\jsse.jarJDK\jdk1.8\jre\lib\jce.jarJDK\jdk1.8\jre\lib\charsets.jarJDK\jdk1.8\jre\lib\jfr.jarJDK\jdk1.8\jre\classesExtention ClassLoader加载位置如下:C:\Program Files\Java\jre1.8.0_91\lib\ext;C:\Windows\Sun\Java\lib\ext;AppClassLoader 加载位置如下:就是你项目配置的jar路径以及工程生成的classes的位置,如maven会是 target\classes,或者bin目录;这些路径的可以从sun.misc.Launcher类得知;

加载器之间的关系

如果类是在boostrapClassLoder下加载,无法获取其加载器从报错的情况,因为bootstrapclassloader有加载过rt.jar,这个从前面可以看出来,Integer类是rt.jar里面的类,所以Integer是由bootstrapclassloader进行加载的没错,那为啥是空指针呢?

1-1 来一张extclassloader的继承图,AppClassLoader也一样的继承关系 图1 这个空指针我是这么理解的:凡是有boostraploader进行加载的类,都是获取不到此类的加载器,因为Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代码中获取它的引用;

ExtClassLoadder的父加载器是null接下来从源码角度来看下,图2输出的原因(即extClassLoader的父记载器为啥null)

图2 关键从parent方法入手,可以发现在图一的继承关系中找到parent方法是在ClassLoader中如图3,从idea点击过去也可以找到,从方法来看parent是final成员变量,所以找到构造方法,就知道如何它是如何初始化了 图3 从下面的代码可以看出parent初始化有两种方式:一种是指定ClassLoader;第二种如果没有指定则通过initSystemClassLoader方法进行初始化,这个方法获取classLoader方式是从Launcher类的getClassLoader方法获取的;

protected ClassLoader(ClassLoader parent) { this(checkCreateClassLoader(), parent); } protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); } public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkClassLoaderPermission(scl, Reflection.getCallerClass()); } return scl; } private static synchronized void initSystemClassLoader() { if (!sclSet) { if (scl != null) throw new IllegalStateException("recursive invocation"); sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); if (l != null) { Throwable oops = null; scl = l.getClassLoader(); try { scl = AccessController.doPrivileged( new SystemClassLoaderAction(scl)); } catch (PrivilegedActionException pae) { oops = pae.getCause(); if (oops instanceof InvocationTargetException) { oops = oops.getCause(); } } if (oops != null) { if (oops instanceof Error) { throw (Error) oops; } else { // wrap the exception throw new Error(oops); } } } sclSet = true; } }

Launcher类的classLoader方法初始化如下:从代码可知Launcher.getClassLoader就是获取appclassLoader,意味着一个类的父加载器如果没有指定,则是默认就是AppClassLoader。

public Launcher() { Launcher.ExtClassLoader var1; try { var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } try { this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } Thread.currentThread().setContextClassLoader(this.loader); String var2 = System.getProperty("java.security.manager"); if(var2 != null) { SecurityManager var3 = null; if(!"".equals(var2) && !"default".equals(var2)) { try { var3 = (SecurityManager)this.loader.loadClass(var2).newInstance(); } catch (IllegalAccessException var5) { ; } catch (InstantiationException var6) { ; } catch (ClassNotFoundException var7) { ; } catch (ClassCastException var8) { ; } } else { var3 = new SecurityManager(); } if(var3 == null) { throw new InternalError("Could not create SecurityManager: " + var2); } System.setSecurityManager(var3); } } public ClassLoader getClassLoader() { return this.loader; }

但是这个还是无法解释extClassLoader的父加载器是null,请看Laucher类中ExtClassLoader类的构造法方方法,指定的父加载器就是null,所以从parent变量的获取追踪到extClassloader是由于指定null父加载器,所以导致extClassLoader获取父加载器是null,而AppClassLoader获取父加载器是extClassLoader,是因为指定了extClassLoader为自己的父加载器;

extclassLoader构造方法 appclassLoader构造方法

从源码角度理解双亲委托机制以及boostrapClassLoader为啥可以作为extClassLoader的父加载器双亲委托是什么,看过博主的文章就知道了,一句话概括

委托是从下向上,然后具体查找过程却是自上至下

从ClassLoader的loadClass可以明白双亲委托机制过程,同时知道如果自定义的ClassLoader是覆盖findClass,而不是loadClass,采用这种方式进行加载可以避免java核心api中定义的类型被自定义的加载器加载,从而出现多个

ClassLoadere-loadClass

自定义加载器,自己也写个,跟博主一样,然后调试了一遍,对加载过程再熟悉一遍

public class MyClassLoader extends ClassLoader{ private String filePath; MyClassLoader(String filePath) { this.filePath = filePath; } @Override public Class<?> findClass(String name) throws ClassNotFoundException { int i = name.lastIndexOf(".") +1; String s = name.substring(i) + ".class"; File file = new File(filePath, s); try { FileInputStream is = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = 0; try { while ((len = is.read()) != -1) { bos.write(len); } } catch (IOException e) { e.printStackTrace(); } byte[] data = bos.toByteArray(); is.close(); bos.close(); return defineClass(name,data,0,data.length); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return super.findClass(name); } public static void main(String[] args) throws Exception{ MyClassLoader classLoader = new MyClassLoader("D:\\"); Class<?> aClass = classLoader.loadClass("com.example.Product"); if (aClass != null) { Object o = aClass.newInstance(); Method ok = aClass.getDeclaredMethod("ok"); ok.invoke(o); } } }

打破双亲委派模型------引出上下文类加载器双亲委派模型的加载方式是从底层加载器到顶层加载器进行加载,但是如果顶层加载器要加载来自底层加载器的类时,此时就传统加载模式就无法完成了。这种情况会出现在SPI使用中,引用一段话来说明SPI加载的情况

SPI机制简介SPI的全名为Service Provider Interface,主要是应用于厂商自定义组件或插件中。在java.util.ServiceLoader的文档里有比较详细的介绍。简单的总结下java SPI机制的思想:我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块、xml解析模块、jdbc模块等方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。SPI具体约定Java SPI的具体约定为:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。

以下面这段代码来说明双亲委派模型的 逆向加载

String url = "jdbc:mysql://localhost:3306/mysql"; //通过java库获取数据库连接 Connection conn = java.sql.DriverManager.getConnection(url, "root", "123456");

首先调用静态方法必将导致调用者会进行初始化,所以DriverMananger类将初始化,如果类中有静态代码块必将执行,下面是DriverManager类的代码:

静态代码k loadInitialDrivers方法

因为这句Class.forName(DriverName, false, loader)代码所在的类在java.util.ServiceLoader类中,而ServiceLoader.class又加载在BootrapLoader中,因此传给 forName 的 loader 必然不是BootrapLoader,那会是什么加载器呢,就是上下文加载,这个可以从load方法中看出:

load

所以这边就可以看出要通过顶层加载器去加载底层加载器的类时,通过上下文加载器实现;

参考了以下文章:真正理解线程上下文类加载器(多案例分析)深入浅出ClassLoader一看你就懂,超详细java中的ClassLoader详解
转载请注明原文地址: https://www.6miu.com/read-1650047.html

最新回复(0)