Java类加载机制

Artio 于 2022-04-24 发布

Java程序是由class文件组成的一个完整的应用程序。在程序运行时,并不会一次性加载所有class文件进入内存,而是通过Java的类加载机制进行动态加载,从而转换成java.lang.Class类的一个实例。

ClassLoader类

ClassLoader是一个抽象类,主要的功能是通过指定的类的名称,找到或生成对应的字节码,返回一个java.lang.Class类的实例。开发者可以继承ClassLoader类来实现自定义的类加载器。

ClassLoader类中和加载器相关的方法:

方法 说明
getParent() 返回该类加载器的父类加载器
loadClass(String name) 加载名称为name的类,返回的结果是java.lang.Class类的实例
findClass(String name) 查找名称为name的类,返回的结果是java.lang.Class类的实例
findLoadedClass(String name) 查找名称为name的已经被加载过的类,返回的结果是java.lang.Class类的实例
defineClass(String name, byte[] b, int off, int len) 把字节数组b中的内容转换为Java类,返回的结果是java.lang.Class类的实例,该方法被声明为final
resolveClass(Class<?> c) 链接指定的Java类

loadClass()方法的流程

loadClass方法可以加载类并返回一个java.lang.Class类对象。通过下边的代码可以看出:当loadClass()方法被调用时,会首先使用findLoadedClass()方法判断该类是否已经被加载,如果未被加载,则优先使用加载器的父类加载器进行加载。当不存在父类加载器时,无法对该类进行加载,则会调用自身的findClass()方法,因此可以重写findClass()方法来完成一些类加载的特殊要求。

上边的流程被称为:双亲委托机制

通俗点来说就是如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的类加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

双亲委派机制的实现:

  1. 首先检查请求的类是否已经被加载
  2. 未加载,则请求父类加载器去加载对应路径下的类
  3. 如果加载不到,才由下边的字类去加载

自定义的类加载器

根据loadClass()方法的流程,可以发现通过重写findClass()方法,利用defineClass()方法来将字节码转换成java.lang.class类对象,就可以实现自定义的类加载器。

loadClass()方法与Class.forName()的区别

loadClass()方法只会对类进行加载,不会对类进行初始化。Class.forName()会默认对类进行初始化,当对类进行初始化时,静态的代码块就会得到执行,而代码块和构造函数则需要适合的类实例化才能得到执行。

public class loadClass_forName {
    static {
        System.out.println("静态代码执行");
    }
    {
        System.out.println("代码块执行");
    }
    public loadClass_forName() {
        System.out.println("构造方法执行");
    }
}
public class ClassLoaderTest{
    public static void main(String[] args) throws ClassNotFoundException{
        Class.forName("loadClass_forName");
        ClassLoader.getSystemClassLoader().loadClass("loadClass_forName");
    }
}

结果输出:静态代码执行

URLClassLoader

URLClassLoader类是ClassLoader的一个实现,拥有从远程服务器上加载类的能力。通过URLClassLoader可以实现对一些Webshell的远程加载、对某个漏洞的深入利用。