jvm 类加载

发布时间 2023-04-14 10:31:28作者: HANGYCHN

将字节码文件加载到 jvm,并创建对应的字节码对象,然后对其进行验证、初始化等操作
共 5 个阶段:加载、验证、准备、解析、初始化,这里只记录【加载】里的类加载器和双亲委派

加载的是字节码文件

通过类加载器 ClassLoader 把字节码文件在堆中生成代表这个 class 文件的 java.lang.Class 对象

ClassLoader 分类

  • 站在虚拟机角度分为两种:启动类加载器(c++实现,虚拟机的一部分)、其他类加载器(java实现,java.lang.ClassLoader 的字类)
  • 站在开发角度可以分为 4 种启动类加载器、扩展类加载器、应用程序类加载器、自定义类加载器

各种 ClassLoader

  • 启动类加载器(Bootstrap ClassLoader)
    • 加载 java 核心类库,JAVA_HOME/lib 目录下的类库或 -Xbootclasspath 参数指定路径的类库
    • 处于安全考虑只加载 java、javax、sun 开头的类
    • 没有父加载器
  • 扩展类加载器(Extension ClassLoader)
    • 加载 JAVA_HOME/lib/ext 目录中的类库
    • 父加载器是 Boostrap ClassLoader
  • 应用程序类加载器(Application ClassLoader)
    • 加载我们自己编写的类
    • 父加载器是 Extention ClassLoader
  • 自定义类加载器(Customer ClassLoader)
    • 继承 java.net.URLClassLoader 或 java.lang.ClassLoader类,重写 findClass,如果要打破双亲委派机制需要重写 loadlass 方法
    • 加载自己指定路径的class文件
    • 父加载器是 Application ClassLoader

双亲委派

  • 目的是保证类的唯一性和安全性
  • 比如我们自定义一个 java.lang 包,然后创建 String 类,jdk 本身也存在 java.lang.String 类,显然是有问题的
  • 具体是:加载器收到一个类的加载请求,不管自己能不能加载都让父类加载器加载,直到引导类加载器
  • 如果引导类加载器加载不了,扩展类加载器加载;如果还是加载不了,应用类加载器尝试加载;如果还加载不了,说明没这个类抛出 ClassNotFound 异常或让自定义类加载器加载

类加载方式

  • 隐式加载
    • 遇到 new,或者获取静态变量等时
    • 对类进行反射调用时
    • 虚拟机启动时,加载包含 main 方法的主类
  • 显式加载
    • Class.forName(): 将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块
    • ClassLoader.loadClass(): 只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块
    • Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象

自定义类加载器

除了BootstrapClassLoader是由C/C++实现的,其他的类加载器都是ClassLoader的子类。所以如果我们想实现自定义的类加载器,首先要继承ClassLoader

  • 如果不想打破双亲委派机制,那么只需要重写findClass方法
  • 如果想要打破双亲委派机制,那么就需要重写整个loadClass方法

自定义类加载器

public class MyClassLoader extends ClassLoader{
    //默认ApplicationClassLoader为父类加载器
    public MyClassLoader(){
        super();
    }

    //加载类的路径
    private String path = "";

    //重写findClass,调用defineClass,将代表类的字节码数组转换为Class对象
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] dataByte = new byte[0];
        try {
            dataByte = ClassDataByByte(name);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return this.defineClass(name, dataByte, 0, dataByte.length);
    }

    //读取Class文件作为二进制流放入byte数组, findClass内部需要加载字节码文件的byte数组
    private byte[] ClassDataByByte(String name) throws IOException {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
        name = name.replace(".", "/"); // 为了定位class文件的位置,将包名的.替换为/
        is = new FileInputStream(new File(path + name + ".class"));
        int c = 0;
        while (-1 != (c = is.read())) { //读取class文件,并写入byte数组输出流
            arrayOutputStream.write(c);
        }
        data = arrayOutputStream.toByteArray(); //将输出流中的字节码转换为byte数组
        is.close();
        arrayOutputStream.close();
        return data;
    }
}

使用自定义类加载器加载指定的类

public static void main(String[] args) {
    MyClassLoader myClassLoader = new MyClassLoader();
    Class<?> clazz = myClassLoader.loadClass("com.fengjian.www.MyClassLoader");
    clazz.newInstance();
}