在 'normal' Java 应用程序和 Web 应用程序中运行良好的库关闭例程

Library shutdown routine that works well in a 'normal' Java application and in a web application

我维护一个 JDBC 驱动程序,该驱动程序还具有通过本机库(通过 JNA 访问)提供的嵌入式数据库服务器模式。由于卸载其依赖项的顺序,作为本机库本身卸载的一部分完成的关闭在 Windows 上会遇到问题。为避免访问冲突或其他问题,我需要在卸载此库之前明确关闭嵌入式引擎。

鉴于其使用的性质,很难确定调用关闭的合适时机,我现在看到的正常 Java 应用程序的唯一正确方法是使用 Runtime.getRuntime().addShutdownHook 与实现关闭逻辑的 Thread 的子 class。

这对于普通的 Java 应用程序来说工作正常,但是对于将我的库作为应用程序的一部分包含的 Web 应用程序(在 WAR 的 WEB-INF/lib 中),这将在取消部署时导致内存泄漏,因为关闭挂钩将保持对我的关闭实现和 Web 应用程序的 classloader 的强引用。

解决这个问题的适当方法是什么?我现在正在研究的选项是:

Java 中是否有适合我需要的关机机制?

我试图解决这个问题,因为这似乎是一个有趣的案例。我在这里发布我的发现,尽管我觉得我可能仍然误解了某些东西,或者也做了一些 far-fetched 简化。其实也有可能是我完全误解了你的情况,这个回答毫无用处(如果是的话,我深表歉意)。

我在这里汇总的是基于两个概念:

  • 应用程序服务器全局状态(我使用 System.props,但它可能不是最佳选择 - 也许一些临时文件会更好)
  • container-specific 全局状态(这意味着所有 类 由 container-specific ClassLoader 加载)

我提出一个 EmbeddedEngineHandler.loadEmbeddedEngineIfNeeded 方法,将被调用:

  • 在您 driver 注册期间
  • 在你的 javax.sql.DataSource 实现静态初始化器中(如果整个 DataSource 相关的东西都是这样工作的——我对此知之甚少)

如果我做对了,你根本不需要打电话给 Runtime.removeShutdownHook

这里我不确定的主要事情是 - 如果 driver 是全局部署的,它会在任何 servlet 初始化之前注册吗?如果没有,那我就错了,这行不通。但是也许检查 EmbeddedEngineHandlerClassLoader 会有帮助吗?


这是EmbeddedEngineHandler:

final class EmbeddedEngineHandler {

    private static final String PREFIX = ""; // some ID for your library here
    private static final String IS_SERVLET_CONTEXT = PREFIX + "-is-servlet-context";
    private static final String GLOBAL_ENGINE_LOADED = PREFIX + "-global-engine-loaded";

    private static final String TRUE = "true";

    private static volatile boolean localEngineLoaded = false;

    // LOADING
    static void loadEmbeddedEngineIfNeeded() {
        if (isServletContext()) {
            // handles only engine per container case
            loadEmbeddedEngineInLocalContextIfNeeded();
        } else {
            // handles both normal Java application & global driver cases
            loadEmbeddedEngineInGlobalContextIfNeeded();
        }

    }

    private static void loadEmbeddedEngineInLocalContextIfNeeded() {
        if (!isGlobalEngineLoaded() && !isLocalEngineLoaded()) { // will not load if we have a global driver
            loadEmbeddedEngine();
            markLocalEngineAsLoaded();
        }
    }

    private static void loadEmbeddedEngineInGlobalContextIfNeeded() {
        if (!isGlobalEngineLoaded()) {
            loadEmbeddedEngine();
            markGlobalEngineAsLoaded();
            Runtime.getRuntime().addShutdownHook(new Thread(EmbeddedEngineHandler::unloadEmbeddedEngine));
        }
    }

    private static void loadEmbeddedEngine() {
    }

    static void unloadEmbeddedEngine() {
    }

    // SERVLET CONTEXT (state shared between containers)
    private static boolean isServletContext() {
        return TRUE.equals(System.getProperty(IS_SERVLET_CONTEXT));
    }

    static void markAsServletContext() {
        System.setProperty(IS_SERVLET_CONTEXT, TRUE);
    }

    // GLOBAL ENGINE (state shared between containers)
    private static boolean isGlobalEngineLoaded() {
        return TRUE.equals(System.getProperty(GLOBAL_ENGINE_LOADED));
    }

    private static void markGlobalEngineAsLoaded() {
        System.setProperty(GLOBAL_ENGINE_LOADED, TRUE);
    }

    // LOCAL ENGINE (container-specific state)
    static boolean isLocalEngineLoaded() {
        return localEngineLoaded;
    }

    private static void markLocalEngineAsLoaded() {
        localEngineLoaded = true;
    }
}

这是 ServletContextListener:

@WebListener
final class YourServletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        EmbeddedEngineHandler.markAsServletContext();
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        if (EmbeddedEngineHandler.isLocalEngineLoaded()) {
            EmbeddedEngineHandler.unloadEmbeddedEngine();
        }
    }
}