java findLoadedClass实践

本文深入探讨了自定义ClassLoader的工作原理,通过具体实验演示了类加载过程中的定义类加载器与初始类加载器的区别,以及findLoadedClass方法的使用场景与实际返回情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实验

Bar1.java

package com.external;

public class Bar1 {
    public Bar1(){
        Bar2 bar2 = new Bar2();
        bar2.test();
    }
}

Bar2.java

package com.external;

public class Bar2 {
	public void test(){
		System.out.println("bar2");
	}
}

 Bar1.java和Bar2.java这2个类是工程外的代码,由工程里的SimpleClassLoader2定义;

package com.snda.classloader;

import com.snda.util.FileUtils;
import sun.misc.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class SimpleClassLoader2 extends ClassLoader {
    public SimpleClassLoader2() {
        super(getSystemClassLoader());
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        System.out.println("SimpleClassLoader2 loadClass: " + name);
        return super.loadClass(name, resolve);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String rootdir = System.getProperty("user.dir");
        try{
            byte[] bytes = FileUtils.InputStream2ByteArray(rootdir + File.separator + "external" +
                    File.separator + name.replace(".", File.separator) + ".class");
            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e){
            e.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args){
        try{
            SimpleClassLoader2 classLoader = new SimpleClassLoader2();
//            ClassLoader classLoader = new ClassLoader(classLoader2){};

            Class<?> barTestClass =  classLoader.loadClass("com.external.Bar1");
            System.err.println(invoke(classLoader, "com.external.Bar1"));
            Object barTest = barTestClass.newInstance();
            barTest.toString();

            Thread.sleep(3000);
            System.out.println("findLoadedClass:" + invoke(classLoader, "java.lang.Object"));

        } catch (Exception e){
            e.printStackTrace();
        }

    }

    private static Object invoke(Object object, String name){
        try {
            Method method = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
            method.setAccessible(true);
            return method.invoke(object, name);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        return null;
    }

}

 输出:

SimpleClassLoader2 loadClass: com.external.Bar1 
class com.external.Bar1(Bar1是由SimpleClassLoader2定义的,findLoadedClass一定会返回)
SimpleClassLoader2 loadClass: java.lang.Object
SimpleClassLoader2 loadClass: com.external.Bar2
SimpleClassLoader2 loadClass: java.lang.System
SimpleClassLoader2 loadClass: java.io.PrintStream
bar2(Bar2类的输出)
findLoadedClass:class java.lang.Object(Object为被动加载,SimpleClassLoader2会被标记为该类的初始类加载器,因而findLoadedClass一定会返回)

结论

findLoadedClass官方文档的解释:

Returns the class with the given binary name if this loader has been recorded by the Java virtual machine as an initiating loader of a class with that binary name.

经过实践,结论如下:

1.Class的getClassLoader方法返回的是该类的定义类加载器,不是初始类加载器;

2.某个Class引用的其他类也会由该Class的getClassLoader方法返回的类加载器加载,因而也是由该Class的定义类加载器发起加载;

2.主动调用ClassLoader的loadClass加载一个类,但该ClassLoader并不是被加载类的定义类加载器,那么后面该ClassLoader的findLoadedClass方法总是回返回null;如果该ClassLoader是被加载类的定义类加载器,则findLoadedClass会返回对应的Class;

3.如果是JVM自己根据类加载机制加载的Class,那么被加载类的初始类加载器(initiating loader)和被加载类的定义类加载器(defining loader),它们的findLoadedClass均会返回被加载的Class;

思考:

1.根据官方文档的说明,如果是初始类加载器,按说findLoadedClass都会返回对应的Class,但根据结论1其并未返回,所以我猜测在主动调用加载Class情况下,并没有标记对应的初始类加载器;

2.调用loadClass的加载器和定义类加载器之间,如果还有别的类加载器,它们都不会被标记为被加载类的初始类加载器,它们的findLoadedClass都会返回null;

 

具体参考https://stackoverflow.com/questions/21962631/findloadedclass-returns-null

<think>我们讨论的是Java动态加载类。根据引用内容,动态加载类可以增加程序的灵活性,避免一次性加载所有类,节省内存。动态加载的核心是ClassLoader,它负责将类文件加载到JVM中。引用[1]提到动态加载可以按需加载,引用[2]介绍了URLClassLoader,引用[3]说明了ClassLoader加载类的流程(先检查缓存,再委托父加载器,最后自己加载),引用[4]解释了动态加载的必要性(节省内存)。 现在,我将详细说明Java动态加载类的方法和实现步骤: ### 1. 使用ClassLoader.loadClass()方法 这是最基础的方式,通过ClassLoader的loadClass方法按类名加载类,但不进行初始化(静态块和静态变量不会执行)。适合只需要加载类而不立即使用的情况。 ```java // 获取当前类的类加载器 ClassLoader classLoader = MyClass.class.getClassLoader(); try { // 加载指定类(完整类名) Class<?> loadedClass = classLoader.loadClass("com.example.DynamicClass"); // 此时类已加载但未初始化 System.out.println("类加载成功: " + loadedClass.getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } ``` ### 2. 使用Class.forName()方法 此方法允许控制类初始化(第二个参数决定是否初始化)。通常用于需要立即初始化类的场景(如数据库驱动加载)。 ```java try { // 加载并初始化类 Class<?> dynamicClass = Class.forName("com.example.DynamicClass", true, classLoader); System.out.println("类已初始化: " + dynamicClass.getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } ``` ### 3. 使用URLClassLoader加载外部类 当需要从非classpath路径(如网络或文件系统)加载类时,可使用URLClassLoader。引用[2]提到它是动态加载类的重要工具。 ```java // 创建URLClassLoader,指定类文件所在目录或JAR URL[] urls = {new URL("file:///path/to/classes/")}; try (URLClassLoader urlClassLoader = new URLClassLoader(urls)) { // 加载指定类 Class<?> externalClass = urlClassLoader.loadClass("com.example.ExternalClass"); // 创建实例 Object instance = externalClass.newInstance(); // 调用方法 Method method = externalClass.getMethod("execute"); method.invoke(instance); } catch (Exception e) { e.printStackTrace(); } ``` ### 4. 自定义ClassLoader实现高级加载逻辑 通过重写findClass方法,可以自定义类加载规则(如解密字节码)。引用[3]提到,当父加载器无法加载时,会调用findClass方法。 ```java public class MyClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // 1. 从自定义位置读取类字节码 byte[] classBytes = loadClassBytes(name); if (classBytes == null) { throw new ClassNotFoundException(name); } // 2. 定义类(将字节数组转换为Class对象) return defineClass(name, classBytes, 0, classBytes.length); } private byte[] loadClassBytes(String className) { // 实现从文件/网络等加载字节码的逻辑 // 示例:从文件读取 String path = className.replace('.', '/') + ".class"; try (InputStream ins = new FileInputStream(path)) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bytesRead; byte[] buffer = new byte[4096]; while ((bytesRead = ins.read(buffer)) != -1) { baos.write(buffer, 0, bytesRead); } return baos.toByteArray(); } catch (IOException e) { return null; } } } // 使用自定义ClassLoader MyClassLoader myLoader = new MyClassLoader(); Class<?> customLoadedClass = myLoader.loadClass("com.example.CustomClass"); ``` ### 5. 动态加载类的典型应用场景 - **插件系统**:如IDE插件,运行时加载新功能 - **模块化开发**:按需加载模块减少启动内存 - **热部署**:在应用运行期间替换类(需结合Java Agent,引用[5]提到动态加载Agent) ### 6. 注意事项 1. **类隔离**:不同ClassLoader加载的相同类被视为不同类,避免冲突需设计好类加载结构 2. **内存泄漏**:动态加载的类可能因未及时卸载导致PermGen/OOM(Java 8+ Metaspace) 3. **安全限制**:自定义ClassLoader需考虑安全管理器(SecurityManager)策略 ### 7. 类卸载条件 动态加载的类在满足以下条件时可被GC回收: - 所有实例已被回收 - 该类的ClassLoader实例已被回收 - 该类对应的Class对象无强引用 ### 示例:动态加载并执行 ```java // 从指定JAR动态加载类并执行 URL jarUrl = new URL("file:/plugins/math-utils.jar"); URLClassLoader pluginLoader = new URLClassLoader(new URL[]{jarUrl}); Class<?> mathClass = pluginLoader.loadClass("com.plugins.MathUtil"); Object mathUtil = mathClass.newInstance(); Method addMethod = mathClass.getMethod("add", int.class, int.class); int result = (int) addMethod.invoke(mathUtil, 5, 3); System.out.println("5+3=" + result); // 输出:5+3=8 pluginLoader.close(); // 关闭加载器(允许类卸载) ``` §§相关问题§§ 1. 如何解决不同ClassLoader加载的类之间的类型转换问题? 2. 如何实现Java类的热替换(HotSwap)? 3. Java 9模块化系统如何影响动态类加载? 4. 如何监控和诊断动态加载类的内存泄漏?</think>### Java 动态加载类的方法与实现 在 Java 中,动态加载类允许程序在运行时按需加载类文件,而不是在启动时一次性加载所有类。这种机制提高了程序的灵活性和内存效率,尤其适用于插件系统、模块化应用等场景[^1][^4]。 #### 一、核心方法 1. **ClassLoader.loadClass()** 基础加载方式,加载类但不初始化(静态块不会执行): ```java ClassLoader loader = MyClass.class.getClassLoader(); Class<?> dynamicClass = loader.loadClass("com.example.DynamicClass"); ``` 2. **Class.forName()** 加载并初始化类(会执行静态块): ```java Class<?> dynamicClass = Class.forName("com.example.DynamicClass", true, loader); ``` 3. **URLClassLoader** 从外部路径或网络加载类(引用[2]): ```java URL[] urls = {new URL("file:///path/to/jar/")}; try (URLClassLoader urlLoader = new URLClassLoader(urls)) { Class<?> pluginClass = urlLoader.loadClass("com.plugin.CustomModule"); } ``` #### 二、完整实现流程 ```mermaid graph LR A[创建ClassLoader] --> B[定位类文件] B --> C[加载字节码] C --> D[定义类对象] D --> E[实例化类] E --> F[调用方法] ``` ```java public class DynamicLoader { public static void main(String[] args) throws Exception { // 1. 创建自定义类加载器 ClassLoader parentLoader = DynamicLoader.class.getClassLoader(); URLClassLoader customLoader = new URLClassLoader( new URL[]{new File("plugins/").toURI().toURL()}, parentLoader ); // 2. 动态加载类 Class<?> pluginClass = customLoader.loadClass("com.plugin.DataProcessor"); // 3. 实例化并调用方法 Object processor = pluginClass.getDeclaredConstructor().newInstance(); Method processMethod = pluginClass.getMethod("process", String.class); String result = (String) processMethod.invoke(processor, "input_data"); System.out.println("处理结果: " + result); } } ``` #### 三、关键机制解析 1. **双亲委派模型**(引用[3]): ```java public Class<?> loadClass(String name) throws ClassNotFoundException { // 1. 检查是否已加载 Class<?> c = findLoadedClass(name); if (c == null) { // 2. 委托父加载器 try { c = parent.loadClass(name); } catch (ClassNotFoundException e) { // 3. 自行查找 c = findClass(name); } } return c; } ``` 2. **类查找过程**: - 缓存检查 → 父加载器 → `findClass()` 方法 - 自定义加载器需重写 `findClass()`(引用[4]) #### 四、进阶应用:热加载类 ```java // 自定义热部署ClassLoader public class HotSwapLoader extends URLClassLoader { public HotSwapLoader(URL[] urls) { super(urls, null); // 打破双亲委派 } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { // 自定义类优先重新加载 if (name.startsWith("com.dynamic.")) { return findClass(name); } return super.loadClass(name); } } // 使用示例 HotSwapLoader loader = new HotSwapLoader(new URL[]{new File("hotdeploy/").toURL()}); Class<?> reloadedClass = loader.loadClass("com.dynamic.UpdatedModule"); ``` #### 五、注意事项 1. **内存泄漏风险** 长期存活的ClassLoader会导致加载的类无法卸载 ```java // 正确释放 URLClassLoader loader = ...; loader.close(); // JDK 1.7+ ``` 2. **类冲突解决** 使用不同ClassLoader实现类隔离: ```java ClassLoader loaderA = new URLClassLoader(urlsA); ClassLoader loaderB = new URLClassLoader(urlsB); // loaderA和loaderB加载的同名类互不影响 ``` 3. **安全管理** 启用SecurityManager控制加载权限: ```java System.setSecurityManager(new SecurityManager()); Policy.setPolicy(new MyPluginPolicy()); ``` #### 六、典型应用场景 1. 插件系统(如IDE插件) 2. 模块热部署(引用[5]) 3. 类版本隔离(不同版本库共存) 4. 远程代码加载(如Applet替代方案) > **最佳实践**:对于生产环境,建议使用OSGi(如Apache Felix)或JPMS等成熟框架实现动态模块化[^5]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值