- 版本:jdk8u25
对classloader的分析
ClassLoader
这一段是非常基础的东西,是在学反序列化之前就要了解的,拿出来巩固一下,如果你已经完全了解了,直接跳过这一段就行了
众所周知,java的类想要运行,就必须被JVM加载,而classloader的作用就是把class文件加载进内存
Java虚拟机本身就定义了三种最重要的类加载器,分别为:
Bootstrap ClassLoader(引导类加载器)
- 加载JDK的核心类库,比如:java.lang.、java.util. 等
- JVM内部用C/C++实现的,不是Java类
- 这个ClassLoader没有父类加载器,也不能实例化
Extension ClassLoader(扩展类加载器)
- 加载JDK的扩展类库,比如JAVA_HOME/lib/ext目录下的jar包
- 父加载器是Bootstrap ClassLoader。
- 实现类sun.misc.Launcher$ExtClassLoader
App ClassLoader(系统类加载器)
- 加载classpath路径下的类,也就是我们写的程序(例如src/target里的类)
- 它是默认的类加载器:如果我们没有指定类加载器,Java就使用这个
- 通过ClassLoader.getSystemClassLoader()获取到的就是它。
- 实现类:sun.misc.Launcher$AppClassLoader
这三种类加载器是有层级结构:
1 | AppClassLoader |
这个结构就体现了”双亲委派模型”,当一个类加载器需要加载某个类时,它会先委托自己的慈父加载器去加载。如果慈父加载器找不到,它才自己尝试去加载
很多人可能会很一头雾水,对于这个模型,为啥要先委托慈父加载呢
主要是为了避免重复加载和保证核心类不会被用户自定义类覆盖
举个例子,假如没有这个机制,用户自定义了一个java.lang.String并用自定义ClassLoader加载,如果直接被加载进去了,Java改用原本的String还是你写的String呢
那么稍微认识了类加载器,我们再熟悉类加载过程,早在我解析TemplateImp链子的时候,我就稍微提起了类加载的过程,而现在我细致的讲一讲
而加载的第一步便是调用ClasssLoader.loadClass函数
loadClass
1 | protected Class<?> loadClass(String name, boolean resolve) |
先调用findLoadedClass(name);查看是否类已经加载,如果是则判断resolve是否为True,是则调用resolveClass解析该类,默认这个参数是false
解析类的意思就是把类转化为类引用,也就是内存地址,方便运行时直接调用
当然,如果为False时就不会立即解析,但在第一次运行调用对应类时还是会解析
如果此类没被加载过,就查看当前加载器有没有慈父,有就让慈父帮忙,没有就调用BootstrapClassLoader加载
如果BootstrapClassLoader还没能帮上忙,就调用当前类的findClass,如果当前类没重写findClass,就直接报错
当然如果通过自定义的findClass找到了字节码,调用defineClass就能把类给注册了
UrlClassLoader
这个类实现了远程加载资源的能力,被滥用的话往往会造成漏洞,我们做个小测试
exp.java
1 | package test; |


这里得注意一下,如果像加载远程的UrlEvil类,当前项目中不能有同名类,不然他会优先加载本地的
UrlEvil.java
1 | import java.io.IOException; |