在 '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.sql.DriverAction.deregister()
进行清理。
不适合,因为在正常的应用程序退出时不会注销驱动程序。
使用java.sql.DriverAction.deregister()
删除关闭挂钩并自行执行关闭逻辑。
DriverAction
的使用有点问题,因为驱动程序仍然支持 Java 7,而这个 class 是在 JDBC 4.2 中引入的(Java 8 ).这在技术上并不总是正确使用操作(JDBC 驱动程序也可以在现有连接保持有效和使用时注销),并且有可能使用该驱动程序(通过 javax.sql.DataSource
)而 JDBC java.sql.Driver
实现未注册。
包括一个用 @WebListener
注释的 javax.servlet.ServletContextListener
实现以及将删除关闭挂钩并自行执行关闭逻辑的驱动程序。
如果将驱动程序作为一个整体部署到服务器而不是特定的 Web 应用程序(尽管可以解决这些复杂问题),则此选项会很复杂。
Java 中是否有适合我需要的关机机制?
我试图解决这个问题,因为这似乎是一个有趣的案例。我在这里发布我的发现,尽管我觉得我可能仍然误解了某些东西,或者也做了一些 far-fetched 简化。其实也有可能是我完全误解了你的情况,这个回答毫无用处(如果是的话,我深表歉意)。
我在这里汇总的是基于两个概念:
- 应用程序服务器全局状态(我使用
System.props
,但它可能不是最佳选择 - 也许一些临时文件会更好)
- container-specific 全局状态(这意味着所有 类 由 container-specific
ClassLoader
加载)
我提出一个 EmbeddedEngineHandler.loadEmbeddedEngineIfNeeded
方法,将被调用:
- 在您 driver 注册期间
- 在你的
javax.sql.DataSource
实现静态初始化器中(如果整个 DataSource
相关的东西都是这样工作的——我对此知之甚少)
如果我做对了,你根本不需要打电话给 Runtime.removeShutdownHook
。
这里我不确定的主要事情是 - 如果 driver 是全局部署的,它会在任何 servlet 初始化之前注册吗?如果没有,那我就错了,这行不通。但是也许检查 EmbeddedEngineHandler
的 ClassLoader
会有帮助吗?
这是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();
}
}
}
我维护一个 JDBC 驱动程序,该驱动程序还具有通过本机库(通过 JNA 访问)提供的嵌入式数据库服务器模式。由于卸载其依赖项的顺序,作为本机库本身卸载的一部分完成的关闭在 Windows 上会遇到问题。为避免访问冲突或其他问题,我需要在卸载此库之前明确关闭嵌入式引擎。
鉴于其使用的性质,很难确定调用关闭的合适时机,我现在看到的正常 Java 应用程序的唯一正确方法是使用 Runtime.getRuntime().addShutdownHook
与实现关闭逻辑的 Thread
的子 class。
这对于普通的 Java 应用程序来说工作正常,但是对于将我的库作为应用程序的一部分包含的 Web 应用程序(在 WAR 的 WEB-INF/lib
中),这将在取消部署时导致内存泄漏,因为关闭挂钩将保持对我的关闭实现和 Web 应用程序的 classloader 的强引用。
解决这个问题的适当方法是什么?我现在正在研究的选项是:
使用
java.sql.DriverAction.deregister()
进行清理。不适合,因为在正常的应用程序退出时不会注销驱动程序。
使用
java.sql.DriverAction.deregister()
删除关闭挂钩并自行执行关闭逻辑。DriverAction
的使用有点问题,因为驱动程序仍然支持 Java 7,而这个 class 是在 JDBC 4.2 中引入的(Java 8 ).这在技术上并不总是正确使用操作(JDBC 驱动程序也可以在现有连接保持有效和使用时注销),并且有可能使用该驱动程序(通过javax.sql.DataSource
)而 JDBCjava.sql.Driver
实现未注册。包括一个用
@WebListener
注释的javax.servlet.ServletContextListener
实现以及将删除关闭挂钩并自行执行关闭逻辑的驱动程序。如果将驱动程序作为一个整体部署到服务器而不是特定的 Web 应用程序(尽管可以解决这些复杂问题),则此选项会很复杂。
Java 中是否有适合我需要的关机机制?
我试图解决这个问题,因为这似乎是一个有趣的案例。我在这里发布我的发现,尽管我觉得我可能仍然误解了某些东西,或者也做了一些 far-fetched 简化。其实也有可能是我完全误解了你的情况,这个回答毫无用处(如果是的话,我深表歉意)。
我在这里汇总的是基于两个概念:
- 应用程序服务器全局状态(我使用
System.props
,但它可能不是最佳选择 - 也许一些临时文件会更好) - container-specific 全局状态(这意味着所有 类 由 container-specific
ClassLoader
加载)
我提出一个 EmbeddedEngineHandler.loadEmbeddedEngineIfNeeded
方法,将被调用:
- 在您 driver 注册期间
- 在你的
javax.sql.DataSource
实现静态初始化器中(如果整个DataSource
相关的东西都是这样工作的——我对此知之甚少)
如果我做对了,你根本不需要打电话给 Runtime.removeShutdownHook
。
这里我不确定的主要事情是 - 如果 driver 是全局部署的,它会在任何 servlet 初始化之前注册吗?如果没有,那我就错了,这行不通。但是也许检查 EmbeddedEngineHandler
的 ClassLoader
会有帮助吗?
这是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();
}
}
}