手写一个JVM自定义类加载器

1. 自定义类加载器的意义

  • 隔离加载类 :在某些框架内进行中间件与应用的模块隔离,把类加载到不同的环境。比如:阿里内某容器框架通过自定义类加载器确保应用中依赖的jar包不会影响到中间件运行时使用的jar包。再比如:Tomcat这类Web应用服务器,内部自定义了好几种类加载器,用于隔离同一个Web应用服务器上的不同应用程序。 (类的仲裁-->类冲突)
  • 修改类加载的方式 :类的加载模型并非强制,除Bootstrap外,其他的加载并非一定要引入,或者根据实际情况在某个时间点进行按需进行动态加载
  • 扩展加载源:比如从数据库、网络、甚至是电视机机顶盒进行加载。
  • 防止源码泄漏 :Java代码容易被编译和篡改,可以进行编译加密。那么类加载也需要自定义,还原加密的字节码。

2. 手写一个自定义类加载器

java 复制代码
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author polaris
 * @version 1.0
 * ClassName UserClassLoader
 * Package main.java.classloader
 * Description
 * @create 2024-07-21 20:06
 */
public class CustomerClassLoader extends ClassLoader{
    private String rootPath;
    public CustomerClassLoader (String rootPath){
        this.rootPath = rootPath;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String filePath = rootPath + "\\" + name.replace(".", "\\") + ".class";
        //获取指定路径的class文件对应的二进制流数据
        byte[] data = getBytesFromPath(filePath);
        return defineClass(name,data,0,data.length);
    }
    public byte[] getBytesFromPath(String path){
        FileInputStream fis = null;
        ByteArrayOutputStream baos = null;
        try {
            fis = new FileInputStream(path);
            baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while((len=fis.read(buffer))!=-1){
                baos.write(buffer,0,len);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                assert fis != null;
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                assert baos != null;
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return new byte[0];
    }
    public static void main (String[] args){
        try {
            String rootDir = "G:\\JavaLearning2023\\20_JVMJava虚拟机\\尚硅谷宋红康JVM精讲与GC调优\\JVMdachang\\chapter02_classload\\src";

            //######################自定义类加载器1
            CustomerClassLoader loader1 = new CustomerClassLoader(rootDir);
            Class<?> clazz1 = loader1.loadClass("com.atguigu.java3.User");
            System.out.println("----------自定义类加载器1----------");
            System.out.println("获取到的Class1实例:"+clazz1);
            System.out.println("Class1的类加载器:"+clazz1.getClassLoader());
            System.out.println("类加载器的父类加载器:"+clazz1.getClassLoader().getParent());
            Class<?> clazz11 = loader1.findClass("com.atguigu.java3.User");
            System.out.println("获取到的Class11实例:"+clazz11);
            System.out.println("Class11的类加载器:"+clazz11.getClassLoader());
            System.out.println("类加载器的父类加载器:"+clazz11.getClassLoader().getParent());

            //######################自定义类加载器2
            CustomerClassLoader loader2 = new CustomerClassLoader(rootDir);
            Class clazz2 = loader2.findClass("com.atguigu.java3.User");
            System.out.println("----------自定义类加载器2----------");
            System.out.println("获取到的Class2实例:"+clazz2);
            System.out.println("Class2的类加载器:"+clazz2.getClassLoader());
            System.out.println("类加载器的父类加载器:"+clazz2.getClassLoader().getParent());
            System.out.println("clazz1==clazz2 :" + (clazz1 == clazz2));//clazz1与clazz2对应了不同的类模板结构。
            System.out.println("clazz11==clazz2 :" + (clazz11 == clazz2));//clazz11与clazz2对应了不同的类模板结构。
            Class clazz22 = loader2.loadClass("com.atguigu.java3.User");
            System.out.println("获取到的Class22实例:"+clazz22);
            System.out.println("Class22的类加载器:"+clazz22.getClassLoader());
            System.out.println("类加载器的父类加载器:"+clazz22.getClassLoader().getParent());
            System.out.println("clazz22==clazz2 :" + (clazz22 == clazz2));//clazz22与clazz2对应了相同的类模板结构。
            //######################自定义类加载器3
            CustomerClassLoader loader3 = new CustomerClassLoader(rootDir);
            Class<?> clazz3 = loader3.loadClass("com.atguigu.java3.User");
            System.out.println("----------自定义类加载器3----------");
            System.out.println("获取到的Class3实例:"+clazz3);
            System.out.println("Class3的类加载器:"+clazz3.getClassLoader());
            System.out.println("类加载器的父类加载器:"+clazz3.getClassLoader().getParent());
            System.out.println("clazz1==clazz3 :" + (clazz1 == clazz3));//clazz1与clazz3对应了相同的类模板结构。
            //######################系统类加载器
            System.out.println("----------系统类加载器----------");
            Class clazz4 = ClassLoader.getSystemClassLoader().loadClass("com.atguigu.java3.User");
            System.out.println("获取到的Class4实例:"+clazz4);
            System.out.println("Class4的类加载器:"+clazz4.getClassLoader());
            System.out.println("类加载器的父类加载器:"+clazz4.getClassLoader().getParent());
            System.out.println("clazz1==clazz4 :" + (clazz1 == clazz4));//clazz1与clazz4对应了相同的类模板结构。
            //######################new对象的加载器
            System.out.println("----------new对象的加载器----------");
            com.atguigu.java3.User user = new com.atguigu.java3.User();
            Class<? extends com.atguigu.java3.User> clazz5 = user.getClass();
            System.out.println("获取到的Class5实例:"+clazz5);
            System.out.println("Class5的类加载器:"+clazz5.getClassLoader());
            System.out.println("类加载器的父类加载器:"+clazz5.getClassLoader().getParent());
            System.out.println("clazz1==clazz5 :" + (clazz1 == clazz5));//clazz1与clazz5对应了相同的类模板结构。
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}
输出
----------自定义类加载器1----------
获取到的Class1实例:class com.atguigu.java3.User
Class1的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
类加载器的父类加载器:sun.misc.Launcher$ExtClassLoader@4b67cf4d
获取到的Class11实例:class com.atguigu.java3.User
Class11的类加载器:com.atguigu.java3.CustomerClassLoader@7ea987ac
类加载器的父类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
----------自定义类加载器2----------
获取到的Class2实例:class com.atguigu.java3.User
Class2的类加载器:com.atguigu.java3.CustomerClassLoader@29453f44
类加载器的父类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
clazz1==clazz2 :false
clazz11==clazz2 :false
获取到的Class22实例:class com.atguigu.java3.User
Class22的类加载器:com.atguigu.java3.CustomerClassLoader@29453f44
类加载器的父类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
clazz22==clazz2 :true
----------自定义类加载器3----------
获取到的Class3实例:class com.atguigu.java3.User
Class3的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
类加载器的父类加载器:sun.misc.Launcher$ExtClassLoader@4b67cf4d
clazz1==clazz3 :true
----------系统类加载器----------
获取到的Class4实例:class com.atguigu.java3.User
Class4的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
类加载器的父类加载器:sun.misc.Launcher$ExtClassLoader@4b67cf4d
clazz1==clazz4 :true
----------new对象的加载器----------
获取到的Class5实例:class com.atguigu.java3.User
Class5的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
类加载器的父类加载器:sun.misc.Launcher$ExtClassLoader@4b67cf4d
clazz1==clazz5 :true

说明:

在自定义类加载器1中

用**loadClass()**方法加载,虽然是自定义类加载器,但是还是用的是系统类加载器AppClassLoader,其父类加载器是扩展类ExtClassLoader,这是因为基于双亲委派机制,会先向上层父类加载器请求加载,这里AppClassLoader正好管理了classpath下的所有类,就加载了User类,就没有loader1什么事了。

用**findClass()**方法加载,跳过了双亲委派机制,直接用当前类加载器加载,所以是CustomerClassLoader,其父类加载器是AppClassLoader。

而在自定义类加载器2中

用**findClass()**方法加载跳过了双亲委派机制,直接用当前类加载器加载,所以是CustomerClassLoader,其父类加载器是AppClassLoader。虽然都是自定义类加载器,但是是不同的类加载器实例,所以获取到Class实例clazz11和clazz2不同,clazz1和clazz就更不等了。

用**loadClass()**方法加载,会先检查是否已经被加载过了,发现已经用CustomerClassLoader加载过了,就会直接返回clazz2。

在自定义类加载器3中

还是用**loadClass()**方法加载,会先检查是否已经被加载过了,还没有加载过,根据双亲委派机制用AppClassLoader加载,但系统类加载器共用了clazz1的,已经加载过直接返回实例,所以clazz3和clazz1相同。

在系统类加载器中

用loadclass()方法加载,会先检查是否已经被加载过了,系统类加载器共用了clazz1的,已经加载过直接返回实例,所以clazz4和clazz1相同。

new的对象的加载器

由于new的时classpath下的类,所以默认用系统类加载器,系统类加载器共用了clazz1的,已经加载过直接返回实例,所以clazz5和clazz1相同。

注意:不同的类加载器实例的缓存(是否已经加载过)是不共通的,所以clazz4并不会等于clazz2,而系统类加载器和扩展类加载器会公用一个,加载过就不会再次加载,直接返回(所以说类很难卸载,系统类加载器会有很多引用)。

相关推荐
2401_826097621 分钟前
JavaEE-Mybatis初阶
java·java-ee·mybatis
KIDAKN4 分钟前
JavaEE->多线程2
java·算法·java-ee
wu_android9 分钟前
Java匿名内部类访问全局变量和局部变量的注意事项
java·开发语言
飞鸟malred10 分钟前
rust单体web项目模板搭建
前端·jvm·rust
喵手10 分钟前
领导让我同事封装一个自定义工具类?结果她说要三小时...
java·后端·java ee
cubicjin14 分钟前
JVM对象创建与内存分配机制深度剖析
jvm
牛马baby15 分钟前
synchronized 做了哪些优化?
java·高并发·并发编程·synchronized·锁升级·面试资料·程序员涨薪跳槽
钟琛......20 分钟前
Java事务失效(面试题)的常见场景
java·数据库·spring
带刺的坐椅1 小时前
Solon Expression Language (SnEL):轻量高效的Java表达式引擎
java·solon·snel·表达式语言
老马啸西风1 小时前
从零开始手写redis(18)缓存淘汰算法 FIFO 优化
java