【面试题精讲】JVM-打破双亲委派机制-自定义类加载器

简介: 【面试题精讲】JVM-打破双亲委派机制-自定义类加载器

1. 什么是 Java 类加载器?

Java 类加载器就是将 Java 字节码文件转换成 Java 类的一种机制。Java 虚拟机会在需要使用某个类时通过类加载器将该类加载进内存并转换成对应的 Java 类。

Java 类加载器主要有三类:Bootstrap ClassLoader、Extension ClassLoader 和 Application ClassLoader,其中 Bootstrap ClassLoader 是由 C++ 语言实现的,其他两个是由 Java 语言实现的。

2. 为什么需要自定义类加载器?

Java 类加载器的作用在于加载 Java 类,但是有时候默认的类加载器无法满足我们的需求。例如:

  • 在程序的运行时,可能需要从网络上下载一些 Java 类库并加载到 JVM 中,这时就需要自定义类加载器。
  • 为了实现不同的功能,可能需要区分不同的 Java 包,而默认类加载器无法实现这个功能,需要使用自定义类加载器进行区分。

3. 自定义类加载器的实现原理

自定义类加载器打破的是 Java 的双亲委派机制。Java 类加载器的加载顺序是从下往上走的,即:先由自定义类加载器加载类,如果找不到,再由父加载器加载,如果父加载器也找不到,再由祖先加载器加载,一直到 Bootstrap ClassLoader。这个机制可以保证同一个应用程序中的同一个类只被加载一次。

根据上述机制,我们实现自定义类加载器需要实现以下两个方法:

  • findClass() 方法: 如果默认加载器找不到类,则调用此方法来查找并加载类。
  • defineClass() 方法:将字节码转换成 Java 类并返回。

具体实现方式可见以下代码:

public class MyClassLoader extends ClassLoader {
    private String classPath;
    public MyClassLoader(String classPath, ClassLoader parent) {
        super(parent);
        this.classPath=classPath;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = getClassData(name);
        if (classData == null){
            throw new ClassNotFoundException();
        }
        else{
            return defineClass(name,classData,0,classData.length);
        }
    }
    private byte[] getClassData(String className){
        String path = classNameToPath(className);
        try{
            InputStream inputStream = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int num = 0;
            while((num=inputStream.read(buffer))!=-1){
                baos.write(buffer,0,num);
            }
            inputStream.close();
            return baos.toByteArray();
        }
        catch(Exception e) {
            return null;
        }
    }
    private String classNameToPath(String className){
        return classPath + className.replace(".", "/") + ".class";
    }
}

该代码模拟了一个自定义类加载器。在这个例子中,我们在 findClass() 方法中使用 getClassData() 方法来加载类,并在 defineClass() 方法中将字节码转换成 Java 类并返回。

上述示例是一个简单的自定义类加载器示例,其中还有一些细节和注意事项需要引起我们的注意,具体会在后面的章节中提到。

4. 自定义类加载器的使用示例

在项目实际中,我们可能需要使用多个自定义类加载器来加载不同的类,这需要我们自己来实现 ClassLoader。下面是一个示例:

public class Test {
    public static void main(String[] args) {
        String classPath = "F:/work/TestDemo/bin/";
        MyClassLoader classLoader1 = new MyClassLoader(classPath, ClassLoader.getSystemClassLoader().getParent());
        MyClassLoader classLoader2 = new MyClassLoader(classPath, ClassLoader.getSystemClassLoader().getParent());
        try {
            Class<?> class1 = classLoader1.loadClass("demo.bean.User");
            Class<?> class2 = classLoader2.loadClass("demo.bean.User");
            System.out.println(class1 == class2); // false,因为类加载器不一样
            Object obj1= class1.newInstance();
            Object obj2= class2.newInstance();
            Method method=class1.getMethod("setUname", String.class);
            method.invoke(obj1, "张三");
            method.invoke(obj2,"李四");
            Method method1=class1.getMethod("getUname");
            Method method2=class2.getMethod("getUname");
            System.out.println(method1.invoke(obj1).equals(method2.invoke(obj2)));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,我们使用了两个自定义类加载器(classLoader1 和 classLoader2),用它们分别加载了同样的一个类(demo.bean.User)。由于是使用不同的类加载器类加载同样的一个类,所以两个 class 对象是不相等的。我们在 setUname() 方法中设置了 obj1 中的 uName 为“张三”,在 obj2 中设置为“李四”,并在 getUname() 方法中返回了 uName 属性的值,在最终的输出中我们可以看到它们的返回值是不相等的。

5. 自定义类加载器的优点

  • 解决了类的相互依赖关系。由于自定义类加载器不受双亲委派机制的限制,所以我们可以通过自定义类加载器来避免由于类互相依赖而引起的加载问题。
  • 可以实现类隔离。自定义类加载器可以使用不同的命名空间,从而实现类隔离。
  • 扩展了 Java 类加载机制的实现。自定义类加载器可以通过继承 JDK 中已有的类来扩展类加载机制的实现,从而满足各种特定的需求。

6. 自定义类加载器的缺点

  • 安全问题。由于自定义类加载器打破了 Java 的双亲委派机制,所以可能导致外部加载非法类、内存泄漏等问题。
  • 代码复杂度高。相比于默认加载器的方式,使用自定义类加载器需要编写更多的代码,且需要考虑多种异常情况,如类的加载失败、类库的损坏等。

7. 自定义类加载器的使用注意事项

  • 由于自定义类加载器需要重写父类的 loadClass() 方法,所以我们需要注意自定义类加载器的作用范围,避免影响到系统的正确使用。
  • 自定义类加载器所加载的类的命名一定不能与系统默认的类库相同,避免加载类库出错。可以通过使用命名空间来实现。
  • 在加载类时,应该保证加载同一个类的不同实例使用的是同一个 Class 对象,否则会出现 Class Cast 异常。这可以使用 findLoadedClass() 方法来实现。

8. 总结

Java 类加载器是将 Java 字节码文件转换成 Java 类的机制。Java 类加载器分为三种:Bootstrap ClassLoader、Extension ClassLoader 和 Application ClassLoader。

自定义类加载器可以解决一些默认类加载器无法解决的问题,如实现类隔离、解决类的相互依赖关系等。自定义类加载器打破 Java 的双亲委派机制,需要重写 findClass() 方法和 defineClass() 方法。

自定义类加载器有一些缺点,如安全问题、代码复杂度高等,使用时需要注意一些细节和注意事项。

本文由 mdnice 多平台发布


相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
76 2
|
2天前
|
Java 数据库连接 Maven
最新版 | SpringBoot3如何自定义starter(面试常考)
在Spring Boot中,starter是一种特殊的依赖,帮助开发人员快速引入和配置特定功能模块。自定义starter可以封装一组特定功能的依赖和配置,简化项目中的功能引入。其主要优点包括模块化、简化配置、提高代码复用性和实现特定功能。常见的应用场景有短信发送模块、AOP日志切面、分布式ID生成等。通过创建autoconfigure和starter两个Maven工程,并编写自动配置类及必要的配置文件,可以实现一个自定义starter。最后在测试项目中验证其有效性。这种方式使开发者能够更便捷地管理和维护代码,提升开发效率。
最新版 | SpringBoot3如何自定义starter(面试常考)
|
1月前
|
SQL 缓存 监控
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
本文详细解析了数据库、缓存、异步处理和Web性能优化四大策略,系统性能优化必知必备,大厂面试高频。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
|
3月前
|
安全 Java 应用服务中间件
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
什么是类加载器,类加载器有哪些;什么是双亲委派模型,JVM为什么采用双亲委派机制,打破双亲委派机制;类装载的执行过程
107 35
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
|
2月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
61 1
|
2月前
|
监控 架构师 Java
从蚂蚁金服面试题窥探STW机制
在Java虚拟机(JVM)中,垃圾回收(GC)是一个至关重要的机制,它负责自动管理内存的分配和释放。然而,垃圾回收过程并非没有代价,其中最为显著的一个影响就是STW(Stop-The-World)机制。STW机制是指在垃圾回收过程中,JVM会暂停所有应用线程的执行,以确保垃圾回收器能够正确地遍历和回收对象。这一机制虽然保证了垃圾回收的安全性和准确性,但也可能对应用程序的性能产生显著影响。
42 2
|
2月前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?