JAVA类加载

发布时间 2023-11-10 21:59:21作者: Ho1d_F0rward

JAVA类加载

类加载器

java中有三种类加载器

-Bootstrap ClassLoader (引导类加载器) 该类加载器实现于JVM层,采用C++编写
- Extension ClassLoader (扩展类加载器)
- App ClassLoader (系统类加载器) 默认的类加载器

BootstrapClassLoader只加载包名为java、javax、sun等开头的类)。

扩展类加载器(ExtensionsClassLoader),由sun.misc.Launcher$ExtClassLoader类实现,用来在/jre/lib/ext或者java.ext.dirs中指明的目录加载java的扩展库.

App类加载器/系统类加载器(AppClassLoader),由sun.misc.Launcher$AppClassLoader实现,一般通过通过(java.class.path或者Classpath环境变量)来加载Java类,也就是我们常说的classpath路径。

自定义类加载器

除开上文提到的三种加载器,我们还可以自定义加载器.

本地类加载

public class ClassLoaderStudy extends ClassLoader {
    private static final String testClassName = "top.longlone.Hello";
   
    private static final byte[] testClassBytes = Base64.getDecoder().decode("yv66vgAAADQAHAoACAARBwASCgACABEIABMKAAIAFAoAAgAVBwAWBwAXAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABWhlbGxvAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwACQAKAQAXamF2YS9sYW5nL1N0cmluZ0J1aWxkZXIBAAZIZWxsbyAMABgAGQwAGgAbAQASdG9wL2xvbmdsb25lL0hlbGxvAQAQamF2YS9sYW5nL09iamVjdAEABmFwcGVuZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEACHRvU3RyaW5nAQAUKClMamF2YS9sYW5nL1N0cmluZzsAIQAHAAgAAAAAAAIAAQAJAAoAAQALAAAAHQABAAEAAAAFKrcAAbEAAAABAAwAAAAGAAEAAAADAAEADQAOAAEACwAAACwAAgACAAAAFLsAAlm3AAMSBLYABSu2AAW2AAawAAAAAQAMAAAABgABAAAABQABAA8AAAACABA=");

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        if (name.equals(testClassName)) {
            return defineClass(testClassName, testClassBytes, 0, testClassBytes.length);
        }
        return super.findClass(name);
    }

    public static void main(String[] args) throws Exception {
        ClassLoaderStudy loader = new ClassLoaderStudy();
        Class testClass = loader.loadClass(testClassName);
        Object o = testClass.newInstance();
        Method sayHello = o.getClass().getMethod("hello", String.class);
        String longlone = (String) sayHello.invoke(o, "Longlone");
        System.out.println(longlone);

    }
}

我们要继承ClassLoader类和覆盖findClass()方法.其中最核心的逻辑是defineClass方法

protected final Class<?> defineClass(byte[] b, int off, int len)
        throws ClassFormatError
    {
        return defineClass(null, b, off, len, null);
    }
  1. String name 这是类的全限定名。在类加载器的作用下,该类将会被加载到Java虚拟机中。
  2. byte[] b 这是一个字节数组,包含了一个类的字节码。这个字节数组通常是通过读取一个类文件(以 .class 结尾的文件)获得的。字节码数组应该包含有效的Java类文件的内容。
  3. int off 这是字节数组 b 中的起始偏移量,表示开始转换的位置。
  4. int len 这是要转换的字节数,表示从偏移量 off 处开始的连续字节的长度。

远程类加载

import java.net.URL;
import java.net.URLClassLoader;

public class ClassLoaderDemo {
    public static void main(String[] args) throws Exception {
        URL url = new URL("http://localhost:8080/examples/");
        URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
        Class clazz = classLoader.loadClass("com.test.Test");
        clazz.newInstance();
    }
}

双亲委派机制

简单来说就是类加载器接到加载类的请求时,首先会先将任务委托给父类加载器,接着请求父类加载这个类,当父类加载器无法加载时(其目录搜素范围没有找到所需要的类时),子类加载器才会进行加载使用。这样可以避免有些类被重复加载。

image-20231110214109445

我们可以看到我们自定义的类是在最下面的,所以我们是无法更改java内部的三个加载器所负责的类.