Java中双亲委派相关知识梳理
classloader介绍
在java中每当需要加载一个类的时候,就需要用到类加载器(classloader),它存在的意义在于把class文件中字节码变成可以被java使用的class对象。java中原生自带3个classloader分别是Bootstrap ClassLoader、Ext ClassLoader、App ClassLoader另外用户也可以自定义Custom ClassLoader。那实际加载类的时候是用哪一个加载器?java为了安全着想规定了classloader之间有一套等级机制。具体来说就是:当从子加载器出发,但子加载器加载class的时候会先委派给由父加载器先加载,如果父加载器无法加载再由子加载器加载,而这套先后顺序就是平时所说的双亲委派。
BootstrapClassLoader,启动类加载器/根加载器,负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,我们常用内置库 java.. 都在里面。这个 ClassLoader 比较特殊,它其实不是一个ClassLoader实例对象,而是由C代码实现。用户在实现自定义类加载器时,如果需要把加载请求委派给启动类加载器,那可以直接传入null作为 BootstrapClassLoader。
ExtClassLoader,扩展类加载器,负责加载 JVM 扩展类,扩展 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,库名通常以 javax 开头。
AppClassLoader,应用类加载器/系统类加载器,直接提供给用户使用的ClassLoader,它会加载 ClASSPATH 环境变量或者 java.class.path 属性里定义的路径中的 jar 包和目录,负责加载包括开发者代码中、第三方库中的类。
容易误会的一点是Classloader之间并不是继承关系而是组合。
AppClassLoader和ExtClassLoader都继承自URLClasLoader。
URLClassLoader继承自ClassLoader。
而一般自定义加载器是直接继承ClassLoader并根据需求重写findClass或者loadClass方法(之后会提到)。
在java代码我们可以调用getParent的方法获取该加载器的父加载器。
1 | public class JavaClassLoader { |
ps: 因为BootstrapClassLoader是内嵌到jvm中c代码,开发者无法直接获取,因此ExtensionClassLoader.getParent()会返回null。
_
demo代码
1 | import java.io.*; |
在new 的位置强制进入,可以看到是从确实是从子加载器AppClassLoader开始的。
步入两步后在第一个红框可以看到加载双亲委派的逻辑。
如果父类无法加载,那么使用自己的findClass实现来找。这里Test是我自定义的因此不在JAVA_HOME/lib/rt.jar
也不在 JAVA_HOME/lib/ext/*.jar
中,所以最后会使用AppClassLoader的findClass实现,而AppClassLoader没有实现自己的findClass,所以最后是由URLClassLoader findClass寻找类。最后通过deineClass把字节码转换为类对象。
重写ClassLoader
通过上面分析我们可以看到在第一次加载类时是在loadClass进行loader调度(实现双亲委派逻辑),最后由findClass拿到路径Resource,由defineClass加载类。因此,如果我们把自己的class放在不常规的位置,就需要自定义ClassLoader,重写findClass方法。如果我们想突破双亲委派模型(覆盖系统类)就需要重写loadClass。
而实际应用中处于bypass的需要我们经常使用defineClass一个多态实现来从内存中或者文件中执行命令。看下面代码。
从内存中
1 | import java.lang.reflect.Method; |
从文件中
1 | import java.io.ByteArrayOutputStream; |
参考
Java中双亲委派相关知识梳理