游乐游手机版
首页/AI教程/文章详情

Java动态加载卸载JAR包的关键技术深度思考分析

时间:2026-06-16 15:54
在使用URLClassLoader动态加载JAR文件后,仅关闭加载器无法卸载类,因为类实例、Class对象及类加载器本身仍被引用,导致无法回收。必须显式将相关变量置为null,并手动触发垃圾回收(GC),才能彻底卸载类,释放内存,避免内存泄漏。

实现

首先需要准备一个用于测试的 JAR 包。编写一个简单的 Java 类,包含静态代码块、静态方法和实例方法,并将其打包成 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() 释放资源后,之前加载的类竟然仍然可以被使用?官方文档对此解释如下:

在这里插入图片描述官方文档说明截图如下:

*** 从官方解释来看,确实如此 *** 那么,类究竟在何时才会被真正回收?回顾类加载机制可知:只有当类没有任何引用时,才会被垃圾回收器(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 只是第一步,让所有相关对象失去引用才是实现卸载的前提。

来源:https://cloud.tencent.com.cn/developer/article/2689654
上一篇Docker Harbor私有仓库搭建完整指南 下一篇Phoenix映射HBase时间戳的实现方案
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
企业组织级AI赋能具体实施方法
AI教程 · 2026-06-30

企业组织级AI赋能具体实施方法

前段时间收到一位读者的留言,希望聊聊企业级、组织级的AI赋能究竟该怎么落地。巧的是,前几天刚看到一份咨询调研机构的数据:对近一两年所有企业级AI赋能项目的统计显示,超过90%的甲方企业认为,AI赋能在核心业务价值链上没有发挥任何实质性作用。除了AI辅助办公、企业智能知识库这类边缘应用起到了一些辅助效

Scrapy与Redis分布式架构的日本电商多平台数据聚合系统
AI教程 · 2026-06-30

Scrapy与Redis分布式架构的日本电商多平台数据聚合系统

从事日本电商数据聚合工作时,最大的难点在于要同时应对雅虎拍卖、煤炉(Mercari)、乐天和亚马逊日本站等截然不同的平台。以往使用单机爬虫,经常出现运行中崩溃的情况——单点故障、带宽利用率不足、数据存储混乱,这三大痛点令人困扰。 本文分享一套基于Scrapy + Redis的分布式爬虫方案,专门解决

详细PuTTY 0.81安装教程 SSH远程连接与自定义路径设置
AI教程 · 2026-06-30

详细PuTTY 0.81安装教程 SSH远程连接与自定义路径设置

​ PuTTY(简称PT)是一款轻量级开源SSH Telnet客户端,凭借简洁高效的特性,多年来始终是系统管理员与开发者进行远程连接的首选利器。本教程将详细介绍PuTTY 0 81版本的完整安装过程,并指导您自定义安装路径,以便更灵活地管理SSH远程连接工具。 安装准备 首先需要说明的是,整个安装流

在线教育系统必备功能:直播课堂与题库考试架构
AI教程 · 2026-06-30

在线教育系统必备功能:直播课堂与题库考试架构

很多人一想到做在线教育系统,第一反应往往是先把直播间和课程播放器搭起来,觉得“能看课”就万事大吉了。真到落地那天才发现,系统能不能顺滑跑起来,关键全藏在那些细节里——课程怎么组织、学习进度怎么记、考试怎么处理、后台怎么管得住。前端看起来就几个页面,后端其实是一整条业务链路。不管你是要做在线教育APP

ZStack源码级AI诊断套件让故障排查秒出答案
AI教程 · 2026-06-30

ZStack源码级AI诊断套件让故障排查秒出答案

一次故障排查,到底要花多少时间? 运维人员处理私有云、虚拟化平台的问题,流程大致都是这样:先翻日志看现象,再去文档里找对应机制,然后搜社区有没有类似案例,最后综合判断给出答复。简单问题半小时,复杂问题可能要跨天——而这些时间里,大部分精力耗在了“找信息”而不是“做决策”上。 类似的问题,也许每天都在