Java动态加载与卸载jar包深度思考与全面实战技巧

2026-06-16阅读 0热度 0
其他

实战:JAR动态加载与卸载

首先需要准备一个用于验证的 JAR 包。编写一个包含静态代码块、静态方法以及实例方法的简单类,并将其打包为 jartest.jar(假设存放路径为 /Users/tmp/jartest.jar)。

package cn.com.test;
public class JarTest {
    static {
        System.out.println("I am JarTest's static code");
    }
    public static void run(){
        System.out.println("I am JarTest's static method");
    }
    public void run1(){
        System.out.println("I am JarTest's method>>> " + this);
    }
}

接下来编写用于加载与卸载的测试工具。核心思路为:通过 URLClassLoader 加载外部 JAR,调用其内部方法,然后尝试卸载该 JAR。

package jar;
import sun.misc.ClassLoaderUtil;
import ja va.io.File;
import ja va.io.IOException;
import ja va.lang.reflect.InvocationTargetException;
import ja va.lang.reflect.Method;
import ja va.net.MalformedURLException;
import ja va.net.URL;
import ja va.net.URLClassLoader;

public class LoadJar {
    static Object jarTestInstance = null;
    static ClassLoader myClassLoader1;
    static Class jarTest;

    public static void main(String[] args) throws MalformedURLException, InterruptedException {
        System.out.println("before load jar");
        loadClassAndRun();
        Thread.sleep(1000);
        System.out.println("load jar");
        loadJar();
        Thread.sleep(1000);
        System.out.println("after load jar");
        loadClassAndRun();
        Thread.sleep(1000);
        System.out.println("start unload jar");
        unLoad();
        Thread.sleep(1000);
        System.out.println("after unload jar");
        loadClassAndRun();
        System.out.println("load jar");
        loadJar();
        Thread.sleep(1000);
        System.out.println("after load jar");
        loadClassAndRun();
    }

    private static void loadClassAndRun() {
        if(myClassLoader1 == null) {
            myClassLoader1 = LoadJar.class.getClassLoader();
        }
        try {
            jarTest = myClassLoader1.loadClass("cn.com.test.JarTest");
            jarTestInstance = jarTest.newInstance();
            for (Method method : jarTest.getMethods()) {
                try {
                    method.invoke(jarTestInstance);
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    private static void loadJar() throws MalformedURLException {
        URL url = new File("/Users/tmp/jartest.jar").toURI().toURL();
        myClassLoader1 = new URLClassLoader(new URL[] { url});
    }

    // 卸载jar包的代码如下:
    public static void unLoad() {
        if (null != myClassLoader1 && myClassLoader1 instanceof URLClassLoader) {
            System.out.println("unload URLClassLoader ");
            URLClassLoader loader = (URLClassLoader) myClassLoader1;
            try {
                // 关注下这里,这里一会儿要改
                ClassLoaderUtil.releaseLoader(loader);
                loader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

注意:上述代码调用了 ClassLoaderUtil.releaseLoader,这是一个内部 API,后续会采用更标准的方式处理。

运行结果

在这里插入图片描述在这里插入图片描述

问题剖析

一个令人困惑的现象:调用 URLClassLoader.close() 释放资源后,之前加载的 class 竟然仍然可以继续使用?官方文档给出的解释如下:

在这里插入图片描述在这里插入图片描述

*** 官方文档明确指出,关闭 URLClassLoader 并不会自动回收已加载的类 *** 那么,类究竟在何时才会被真正卸载?回顾类加载机制:只有当类不再有任何引用时,GC 才能回收它。所以问题就清晰了——仅关闭 URLClassLoader 并不等于切断了所有引用链。

因此我们对 unLoad() 方法进行改造,显式将类实例、Class 对象以及 ClassLoader 本身全部置为 null,并主动触发 GC:

// 卸载jar包的代码如下:
public static void unLoad() {
    if (null != myClassLoader1 && myClassLoader1 instanceof URLClassLoader) {
        System.out.println("unload URLClassLoader ");
        URLClassLoader loader = (URLClassLoader) myClassLoader1;
        try {
            jarTest = null;
            jarTestInstance = null;
            myClassLoader1 = null;
            System.gc();
            Thread.sleep(2000);
            ClassLoaderUtil.releaseLoader(loader);
            loader.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

再次执行,得到如下结果:

在这里插入图片描述在这里插入图片描述

需要留意的是,System.gc() 并不会立即执行,因此实际测试结果可能与上述截图略有差异。关键结论是:关闭 URLClassLoader 仅仅完成了第一步,确保所有关联对象失去引用才是真正卸载类的前提。

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

相关阅读

更多
欢迎回来 登录或注册后,可保存提示词和历史记录
登录后可同步收藏、历史记录和常用模板
注册即表示同意服务条款与隐私政策