我的Java程序在Linux上运行,需要在关闭之前关闭一些文件句柄(实际上是设备),所以我在JVM上添加了一个关闭钩子。但是,我注意到
java.util.logging.LogManager
也有一个关闭钩子,并且它往往会在我关闭之前关闭,这意味着我无法记录有关关闭过程的任何信息。
有没有办法阻止 LogManager 安装其关闭挂钩,以便我可以在关闭时执行日志记录,然后告诉它在我准备好时进行清理?
使 LogManager 清理线程加入您的关闭挂钩或通过安装自定义处理程序直接执行文件清理。清理线程将按顺序枚举记录器并尝试关闭附加的处理程序。只要您的处理程序是第一个尝试关闭的处理程序,您就可以控制清理程序何时执行。
public class ShutdownHandler extends Handler {
public static void main(String[] args) {
install();
}
private static void install() {
LogManager lm = LogManager.getLogManager();
Logger first = null;
ShutdownHandler sh = new ShutdownHandler();
for (;;) {
try {
Enumeration<String> e = lm.getLoggerNames();
while (e.hasMoreElements()) {
first = lm.getLogger(e.nextElement());
if (first != null) {
break;
}
}
break;
} catch (ConcurrentModificationException olderJvm) {
}
}
Logger.getLogger("").addHandler(sh);
if (first != null) {
first.addHandler(sh);
}
}
public ShutdownHandler() {
super.setLevel(Level.ALL);
}
@Override
public void publish(LogRecord record) {
}
@Override
public void flush() {
}
@Override
public void close() throws SecurityException {
if (!Level.OFF.equals(super.getLevel())) {
super.setLevel(Level.OFF);
shutDown();
}
}
private void shutDown() {
System.out.println(this + " shutdown by "
+ Thread.currentThread().getClass().getName());
//Close your files or join with your other shutdown hook.
}
}
嗯,您可以使用您创建的类的名称来定义系统属性“java.util.logging.manager”。这将允许您使用您自己设计的日志管理器。
https://docs.oracle.com/javase/8/docs/api/java/util/logging/LogManager.html
我以前从未尝试过这个,所以我不知道它的效果如何,也不知道需要做多少工作。
这更多的是一种解决方法...但是...由于我在 Linux 上运行(这是一个专用于特定系统的程序),我最终使用了使用
sun.misc.Signal
的信号处理程序。信号处理程序在 JVM 运行关闭挂钩之前运行(大概它也有自己的信号处理程序来启动该进程)。
所以我现在这样做而不是Runtime.getRuntime().addShutdownHook
:
private void installSignalHandlers() {
SignalHandler signalHandler = signal -> shutDown();
Signal.handle(new Signal("INT"), signalHandler);
Signal.handle(new Signal("TERM"), signalHandler);
}
看起来效果很好;当日志记录仍在运行时,我可以在
shutDown
中关闭所有打开的句柄等。
/**
* This code shows how you can fix your JUL Logging inside Shutdown Hooks
* to workaround Bug JDK-8161253.
*/
public class FixedShutdownHookLoggingDemoApp {
private static final Logger log;
static{
//Unfortunately, in the installed application, setting the system-property here might already be too late.
//It needs to be set that OUTSIDE on application start! But do it here for development nevertheless
System.setProperty("java.util.logging.manager", LogManagerFixed.class.getName());
log = Logger.getLogger(FixedShutdownHookLoggingDemoApp.class.getName());
}
public static void main(String[] args) {
FixedShutdownHookLoggingDemoApp myApplication = new FixedShutdownHookLoggingDemoApp();
myApplication.start(args);
}
private final MyShutdownHook shutdownHook = new MyShutdownHook();
public FixedShutdownHookLoggingDemoApp(){
Runtime.getRuntime().addShutdownHook(shutdownHook);
checkFixedLogConfig();
}
/**
* Checks that the LogManager has correctly been set to {@link LogManagerFixed}.
* If not, issues a warning to the log
*/
private void checkFixedLogConfig() {
if (!(LogManager.getLogManager() instanceof LogManagerFixed)) {
log.warning("LogManager has not been correctly initialized. Shutdowns initiated from outside - aka service stops and restarts - will not be able to log progress.");
log.warning("Please set SystemProperty -Djava.util.logging.manager="+LogManagerFixed.class.getName());
}else{
log.info("LogManager was successfully initialized to "+LogManagerFixed.class);
}
}
private volatile boolean doWork = true;
private void start(String[] arg0) {
//startup your Application here...
//just some demo code
while(doWork){
try {Thread.sleep(500);} catch (InterruptedException e) {}
log.info("Doing stupid work");
}
}
private void shutdown(){
//shutdown your Application here...
//just some demo code
log.info("shutting down the application");
for (int i=0;i<5;i++){
try {Thread.sleep(200);} catch (InterruptedException e) {}
log.info("working on shutdown... "+i);
}
doWork = false;
}
/**
* The shutdown hook is required when using for example the Install4J Service Launcher.
* It needs to be used together with LogManagerFixed such that the log messages get logged during the shutdown phase.
*/
private class MyShutdownHook extends Thread{
MyShutdownHook() {
super("My Shutdown-Hook");
}
@Override public void run(){
log.log(Level.INFO, "Shutdown initiated.");
shutdown();
//at the very end, cleanup the logging API
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof LogManagerFixed){
((LogManagerFixed) logManager).resetFinally();
//no more java util.logging possible after this point!
}
}
};
/**
* Java util Logging has a Bug - known since 2005 - which prevents logging inside other shutdown hooks.
* See https://bugs.openjdk.org/browse/JDK-8161253 and all of its duplicates...
* and https://stackoverflow.com/questions/36388969/can-i-prevent-the-java-util-logging-logmanager-from-shutting-down-too-quickly
* This is a workaround against it, inspired by https://coderanch.com/t/654750/java/resoliving-shutdown-hook-logger-LogManager
*/
public static final class LogManagerFixed extends LogManager {
public LogManagerFixed(){
super();
}
@Override public void reset() {
try {
Field f = LogManager.class.getDeclaredField("deathImminent");
f.setAccessible(true);
boolean deathImminent = f.getBoolean(this);
if (deathImminent){
//is inside the shutdown sequence, don't reset yet.
log.log(Level.FINER, "LogManagerFixed: attempt to shutdown prohibited");
return;
}
} catch (NoSuchFieldException|IllegalAccessException e) {
throw new RuntimeException(e);
}
//if getting to here, this is likely a valid reset call (this is required during initialisation of the Logging System)
super.reset();
}
public void resetFinally() {
log.log(Level.INFO, "LogManagerFixed: shutting down now");
super.reset();
}
}
}