如何通过反射获取静态字段且不触发类初始化?
解决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
相关产品推荐
相关产品推荐

