如何在RCP 4应用中获取当前进程的Windows句柄信息
我完全理解你的痛点——生产环境碰到SWT因句柄耗尽抛出异常,但开发环境根本复现不了,还得用纯Java方案统计Windows的GDI/User Handles以及SWT内部资源的使用情况,完全绕开命令行。下面分模块给你具体实现思路和代码:
一、获取Windows系统级GDI/User Handles总数
Java本身没法直接调用Windows底层API,这里推荐用JNA(Java Native Access),不用写JNI代码,直接在Java里调用系统函数,特别适合RCP这类桌面应用。
1. 引入JNA依赖
因为是RCP应用,你可以把JNA的OSGi bundle加入target平台,或者在MANIFEST.MF里添加依赖:
Require-Bundle: com.sun.jna;bundle-version="5.13.0", com.sun.jna.platform;bundle-version="5.13.0"
如果用Maven构建,pom.xml里加:
<dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna</artifactId> <version>5.13.0</version> </dependency> <dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna-platform</artifactId> <version>5.13.0</version> </dependency>
2. 系统句柄统计代码
通过JNA调用Windows的GetGuiResources函数,获取当前进程的GDI和User Handles数量:
import com.sun.jna.platform.win32.Kernel32; import com.sun.jna.platform.win32.WinNT; public class SystemHandleMonitor { public static long getGdiHandleCount() { WinNT.HANDLE processHandle = Kernel32.INSTANCE.GetCurrentProcess(); // 参数0代表获取GDI Handles数量 return Kernel32.INSTANCE.GetGuiResources(processHandle, 0); } public static long getUserHandleCount() { WinNT.HANDLE processHandle = Kernel32.INSTANCE.GetCurrentProcess(); // 参数1代表获取User Handles数量 return Kernel32.INSTANCE.GetGuiResources(processHandle, 1); } }
二、追踪SWT内部资源使用情况
SWT会为每个Device(比如Display)维护资源计数,我们可以通过内部API获取图像、字体、复合控件等资源的具体数量,这对定位泄漏点至关重要。
SWT资源统计代码
注:这里用到的getDeviceData()是SWT内部API,在RCP 4常用的SWT 4.x版本中稳定,但如果后续SWT版本迭代,可能需要微调:
import org.eclipse.swt.widgets.Display; import org.eclipse.swt.internal.win32.DeviceData; public class SwtResourceMonitor { public static void logSwtResourceUsage(Display display) { DeviceData deviceData = display.getDeviceData(); // 输出各类SWT资源的实时计数 System.out.println("SWT已创建图像数量: " + deviceData.images.length); System.out.println("SWT已创建字体数量: " + deviceData.fonts.length); System.out.println("SWT已创建复合控件数量: " + deviceData.composites.length); System.out.println("SWT已创建控件总数: " + deviceData.controls.length); System.out.println("SWT已创建光标数量: " + deviceData.cursors.length); } }
如果担心内部API的兼容性,也可以遍历Display的所有资源来统计,效率稍低但完全满足监控需求。
三、整合到RCP 4应用中
结合你提供的plugin.xml,你的应用基于org.eclipse.e4.ui.workbench.swt.E4Application,可以通过E4的生命周期扩展来启动监控:
1. 创建生命周期启动处理器
import org.eclipse.e4.core.di.annotations.Execute; import org.eclipse.swt.widgets.Display; public class HandleMonitorStartup { @Execute public void startMonitoring() { Display display = Display.getDefault(); // 启动后台定时任务,每隔5分钟记录一次句柄信息 new Thread(() -> { while (!display.isDisposed()) { try { Thread.sleep(5 * 60 * 1000); // 切换到SWT主线程执行资源统计,避免线程安全问题 display.syncExec(() -> { // 记录系统级句柄 long gdiCount = SystemHandleMonitor.getGdiHandleCount(); long userCount = SystemHandleMonitor.getUserHandleCount(); System.out.printf("系统GDI句柄数: %d, 系统User句柄数: %d%n", gdiCount, userCount); // 记录SWT内部资源 SwtResourceMonitor.logSwtResourceUsage(display); }); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }).start(); } }
2. 在plugin.xml中注册扩展
添加org.eclipse.e4.core.contexts.runner扩展,让应用启动时自动执行监控逻辑:
<plugin> <!-- 你原有的product扩展 --> <extension id="product" point="org.eclipse.core.runtime.products"> <product application="org.eclipse.e4.ui.workbench.swt.E4Application" name="appid"> <property name="modelResourceHandler" value="bundleclass://a.model.resource.handler.ModelResourceHandler"> </property> </product> </extension> <!-- 新增生命周期监控扩展 --> <extension point="org.eclipse.e4.core.contexts.runner"> <runner class="com.yourpackage.HandleMonitorStartup" /> </extension> </plugin>
四、额外优化建议
- 异常触发时立即记录:除了定时监控,还可以监听SWT异常(比如错误码为
OS.ERROR_NO_HANDLES的SWTError),发生时立即dump所有句柄信息,精准捕捉问题瞬间的状态。 - 日志持久化:别只打印到控制台,把这些信息写到滚动日志文件里(用SLF4J+Logback等框架),方便后续回溯分析。
- 第三方组件排查:如果监控到句柄持续增长,重点排查你引入的第三方SWT控件,大概率是第三方组件存在资源未释放的情况。
内容的提问来源于stack exchange,提问作者Michael




