You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过反射获取静态字段且不触发类初始化?

解决WAR包undeploy时第三方静态线程池残留问题(避免触发类初始化)

问题背景

我们的WAR应用依赖的第三方库,在某个内部类的静态字段中创建了带运行线程的线程池,且该线程池是在类的静态初始化块中初始化的。当undeploy WAR包时,这个线程池的线程会持续运行导致资源泄漏。我们需要在undeploy阶段通过反射关闭线程池,但要避免为了关闭而触发未初始化类的静态初始化(否则会凭空创建出不需要的线程池)。

如示例代码所示,即使通过Class.forName(name, false, classLoader)获取未初始化的类,调用Field.get()时仍会触发类初始化:

示例代码

package scratch;
public class A {
    private static final String field;
    static {
        System.out.println("A initializing");
        field = "I'm initialized!";
    }
}

package scratch;
public class B {
    public static void main(String[] args) {
        try {
            System.out.println("B starting");
            var clazz = Class.forName("scratch.A", false, B.class.getClassLoader());
            System.out.println("B got class");
            var field = clazz.getDeclaredField("field");
            System.out.println("B got field");
            field.setAccessible(true);
            System.out.println("B set accessible");
            var value = field.get(null);
            System.out.println("B got value '" + value + "'");
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

运行输出

B starting
B got class
B got field
B set accessible
A initializing
B got value 'I'm initialized!'

核心解决方案思路

关键逻辑是:只对已经初始化的类执行关闭操作。未加载/未初始化的类,其静态初始化块没执行,线程池根本没创建,完全不需要处理。

1. 判断类是否已加载

通过类加载器的findLoadedClass()方法可以判断类是否已被加载:

  • 返回null表示类未被加载,直接跳过
  • 返回Class对象表示类已加载,进入下一步判断是否初始化

2. 判断类是否已初始化

Java标准API没有直接暴露类的初始化状态,但可以通过sun.misc.Unsafe的isClassInitialized()方法判断。这个方法不会触发类初始化,正好符合需求。

3. 仅对已初始化的类关闭线程池

确认类已初始化后,再通过反射获取线程池字段并调用关闭方法,此时不会触发额外的初始化。

完整实现代码

import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.ExecutorService;

public class ThreadPoolShutdownHandler {
    private static final Unsafe UNSAFE;

    static {
        try {
            Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            unsafeField.setAccessible(true);
            UNSAFE = (Unsafe) unsafeField.get(null);
        } catch (Exception e) {
            throw new RuntimeException("Failed to initialize Unsafe", e);
        }
    }

    public static void shutdownThreadPool(String targetClassName, String poolFieldName, ClassLoader appClassLoader) {
        try {
            // 检查类是否已加载
            Class<?> targetClass = appClassLoader.findLoadedClass(targetClassName);
            if (targetClass == null) {
                // 类未加载,线程池未创建,无需处理
                return;
            }

            // 检查类是否已初始化
            if (!UNSAFE.isClassInitialized(targetClass)) {
                // 类已加载但未初始化,静态块未执行,线程池不存在
                return;
            }

            // 反射获取线程池字段
            Field poolField = targetClass.getDeclaredField(poolFieldName);
            poolField.setAccessible(true);
            ExecutorService threadPool = (ExecutorService) poolField.get(null);

            // 关闭线程池
            if (threadPool != null && !threadPool.isShutdown()) {
                threadPool.shutdown();
                // 可选:等待超时后强制关闭
                // if (!threadPool.awaitTermination(10, java.util.concurrent.TimeUnit.SECONDS)) {
                //     threadPool.shutdownNow();
                // }
            }
        } catch (ClassNotFoundException e) {
            // 类未加载,无需处理
        } catch (NoSuchFieldException | IllegalAccessException e) {
            // 记录反射异常,比如日志
            e.printStackTrace();
        }
    }
}

注意事项

  • sun.misc.Unsafe是JDK内部API,不同版本可能有兼容性问题。OpenJDK 9及以上版本需要添加JVM参数--add-opens java.base/sun.misc=ALL-UNNAMED才能正常访问
  • 要在WAR包undeploy的回调中执行这段代码,比如实现ServletContextListener,在contextDestroyed()方法中调用shutdownThreadPool()
  • 如果目标是内部类,类名要使用正确的格式,例如com.example.ThirdPartyClass$InnerThreadPoolClass

内容的提问来源于stack exchange,提问作者Kenster

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 05:07:50