背景:
项目中发现文件服务的临时文件越来越多导致磁盘吃满。根据文件名生成的规则找到代码相关模块,乍一看这个代码貌似没问题。(可能把Exit看错exist)
/**
* 上传后清除临时压缩包
*
* @param zipFilePath
*/
public void deleteTempFile(String zipFilePath) {
try {
File file = new File(zipFilePath);
if (file.exists()) {
file.deleteOnExit();
}
} catch (Exception e) {
bizLog.warn("上海银行临时文件清理出错, zipFilePath={}", zipFilePath);
}
}
点进来源码一看,我丢···DeleteOnExitHook.add(path); 原来是系统退出时执行删除钩子,那为什么我们重启项目没有将这些临时文件删除掉呢?疑问
/**
* Requests that the file or directory denoted by this abstract
* pathname be deleted when the virtual machine terminates.
* Files (or directories) are deleted in the reverse order that
* they are registered. Invoking this method to delete a file or
* directory that is already registered for deletion has no effect.
* Deletion will be attempted only for normal termination of the
* virtual machine, as defined by the Java Language Specification.
*
* <p> Once deletion has been requested, it is not possible to cancel the
* request. This method should therefore be used with care.
*
* <P>
* Note: this method should <i>not</i> be used for file-locking, as
* the resulting protocol cannot be made to work reliably. The
* {@link java.nio.channels.FileLock FileLock}
* facility should be used instead.
*
* @throws SecurityException
* If a security manager exists and its <code>{@link
* java.lang.SecurityManager#checkDelete}</code> method denies
* delete access to the file
*
* @see #delete
*
* @since 1.2
*/
public void deleteOnExit() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkDelete(path);
}
if (isInvalid()) {
return;
}
DeleteOnExitHook.add(path);
}
继续点add方法里面看
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package java.io;
import java.util.*;
import java.io.File;
/**
* This class holds a set of filenames to be deleted on VM exit through a shutdown hook.
* A set is used both to prevent double-insertion of the same file as well as offer
* quick removal.
*/
class DeleteOnExitHook {
private static LinkedHashSet<String> files = new LinkedHashSet<>();
static {
// DeleteOnExitHook must be the last shutdown hook to be invoked.
// Application shutdown hooks may add the first file to the
// delete on exit list and cause the DeleteOnExitHook to be
// registered during shutdown in progress. So set the
// registerShutdownInProgress parameter to true.
sun.misc.SharedSecrets.getJavaLangAccess()
//在这里初始化这个类的时候就注册一个线程任务执行钩子
.registerShutdownHook(2 /* Shutdown hook invocation order */,
true /* register even if shutdown in progress */,
new Runnable() {
public void run() {
runHooks();
}
}
);
}
private DeleteOnExitHook() {}
static synchronized void add(String file) {
if(files == null) {
// DeleteOnExitHook is running. Too late to add a file
throw new IllegalStateException("Shutdown in progress");
}
// 收集要被删除的文件地址
files.add(file);
}
//执行钩子, 那什么时候执行钩子呢?
static void runHooks() {
LinkedHashSet<String> theFiles;
synchronized (DeleteOnExitHook.class) {
theFiles = files;
files = null;
}
ArrayList<String> toBeDeleted = new ArrayList<>(theFiles);
// reverse the list to maintain previous jdk deletion order.
// Last in first deleted.
Collections.reverse(toBeDeleted);
for (String filename : toBeDeleted) {
//遍历所有文件将其删除
(new File(filename)).delete();
}
}
}
继续看下这个方法 registerShutdownHook
private static void setJavaLangAccess() {
// Allow privileged classes outside of java.lang
sun.misc.SharedSecrets.setJavaLangAccess(new sun.misc.JavaLangAccess(){
public sun.reflect.ConstantPool getConstantPool(Class<?> klass) {
return klass.getConstantPool();
}
public boolean casAnnotationType(Class<?> klass, AnnotationType oldType, AnnotationType newType) {
return klass.casAnnotationType(oldType, newType);
}
public AnnotationType getAnnotationType(Class<?> klass) {
return klass.getAnnotationType();
}
public Map<Class<? extends Annotation>, Annotation> getDeclaredAnnotationMap(Class<?> klass) {
return klass.getDeclaredAnnotationMap();
}
public byte[] getRawClassAnnotations(Class<?> klass) {
return klass.getRawAnnotations();
}
public byte[] getRawClassTypeAnnotations(Class<?> klass) {
return klass.getRawTypeAnnotations();
}
public byte[] getRawExecutableTypeAnnotations(Executable executable) {
return Class.getExecutableTypeAnnotationBytes(executable);
}
public <E extends Enum<E>>
E[] getEnumConstantsShared(Class<E> klass) {
return klass.getEnumConstantsShared();
}
public void blockedOn(Thread t, Interruptible b) {
t.blockedOn(b);
}
public void registerShutdownHook(int slot, boolean registerShutdownInProgress, Runnable hook) {
// 注册关机钩子
Shutdown.add(slot, registerShutdownInProgress, hook);
}
public int getStackTraceDepth(Throwable t) {
return t.getStackTraceDepth();
}
public StackTraceElement getStackTraceElement(Throwable t, int i) {
return t.getStackTraceElement(i);
}
public String newStringUnsafe(char[] chars) {
return new String(chars, true);
}
public Thread newThreadWithAcc(Runnable target, AccessControlContext acc) {
return new Thread(target, acc);
}
public void invokeFinalize(Object o) throws Throwable {
o.finalize();
}
});
}
关机对象
/*
* Copyright (c) 1999, 2005, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package java.lang;
/**
* Package-private utility class containing data structures and logic
* governing the virtual-machine shutdown sequence.
*
* @author Mark Reinhold
* @since 1.3
*/
class Shutdown {
/* Shutdown state */
private static final int RUNNING = 0;
private static final int HOOKS = 1;
private static final int FINALIZERS = 2;
private static int state = RUNNING;
/* Should we run all finalizers upon exit? */
private static boolean runFinalizersOnExit = false;
// The system shutdown hooks are registered with a predefined slot.
// The list of shutdown hooks is as follows:
// (0) Console restore hook
// (1) Application hooks
// (2) DeleteOnExit hook
private static final int MAX_SYSTEM_HOOKS = 10;
private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];
// the index of the currently running shutdown hook to the hooks array
private static int currentRunningHook = 0;
/* The preceding static fields are protected by this lock */
private static class Lock { };
private static Object lock = new Lock();
/* Lock object for the native halt method */
private static Object haltLock = new Lock();
/* Invoked by Runtime.runFinalizersOnExit */
static void setRunFinalizersOnExit(boolean run) {
synchronized (lock) {
runFinalizersOnExit = run;
}
}
/**
* Add a new shutdown hook. Checks the shutdown state and the hook itself,
* but does not do any security checks.
*
* The registerShutdownInProgress parameter should be false except
* registering the DeleteOnExitHook since the first file may
* be added to the delete on exit list by the application shutdown
* hooks.
*
* @params slot the slot in the shutdown hook array, whose element
* will be invoked in order during shutdown
* @params registerShutdownInProgress true to allow the hook
* to be registered even if the shutdown is in progress.
* @params hook the hook to be registered
*
* @throw IllegalStateException
* if registerShutdownInProgress is false and shutdown is in progress; or
* if registerShutdownInProgress is true and the shutdown process
* already passes the given slot
*/
static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
synchronized (lock) {
if (hooks[slot] != null)
throw new InternalError("Shutdown hook at slot " + slot + " already registered");
if (!registerShutdownInProgress) {
if (state > RUNNING)
throw new IllegalStateException("Shutdown in progress");
} else {
if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
throw new IllegalStateException("Shutdown in progress");
}
// 注册钩子线程任务
hooks[slot] = hook;
}
}
/* Run all registered shutdown hooks
* 当系统关机时遍历运行所有钩子线程任务,,被调用方都在同类方法里 ,这里就重复粘贴
*/
private static void runHooks() {
for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
try {
Runnable hook;
synchronized (lock) {
// acquire the lock to make sure the hook registered during
// shutdown is visible here.
currentRunningHook = i;
hook = hooks[i];
}
if (hook != null) hook.run();
} catch(Throwable t) {
if (t instanceof ThreadDeath) {
ThreadDeath td = (ThreadDeath)t;
throw td;
}
}
}
}
/* The halt method is synchronized on the halt lock
* to avoid corruption of the delete-on-shutdown file list.
* It invokes the true native halt method.
*/
static void halt(int status) {
synchronized (haltLock) {
halt0(status);
}
}
static native void halt0(int status);
/* Wormhole for invoking java.lang.ref.Finalizer.runAllFinalizers */
private static native void runAllFinalizers();
/* The actual shutdown sequence is defined here.
*
* If it weren't for runFinalizersOnExit, this would be simple -- we'd just
* run the hooks and then halt. Instead we need to keep track of whether
* we're running hooks or finalizers. In the latter case a finalizer could
* invoke exit(1) to cause immediate termination, while in the former case
* any further invocations of exit(n), for any n, simply stall. Note that
* if on-exit finalizers are enabled they're run iff the shutdown is
* initiated by an exit(0); they're never run on exit(n) for n != 0 or in
* response to SIGINT, SIGTERM, etc.
*/
private static void sequence() {
synchronized (lock) {
/* Guard against the possibility of a daemon thread invoking exit
* after DestroyJavaVM initiates the shutdown sequence
*/
if (state != HOOKS) return;
}
runHooks();
boolean rfoe;
synchronized (lock) {
state = FINALIZERS;
rfoe = runFinalizersOnExit;
}
if (rfoe) runAllFinalizers();
}
/* Invoked by Runtime.exit, which does all the security checks.
* Also invoked by handlers for system-provided termination events,
* which should pass a nonzero status code.
*/
static void exit(int status) {
boolean runMoreFinalizers = false;
synchronized (lock) {
if (status != 0) runFinalizersOnExit = false;
switch (state) {
case RUNNING: /* Initiate shutdown */
state = HOOKS;
break;
case HOOKS: /* Stall and halt */
break;
case FINALIZERS:
if (status != 0) {
/* Halt immediately on nonzero status */
halt(status);
} else {
/* Compatibility with old behavior:
* Run more finalizers and then halt
*/
runMoreFinalizers = runFinalizersOnExit;
}
break;
}
}
if (runMoreFinalizers) {
runAllFinalizers();
halt(status);
}
synchronized (Shutdown.class) {
/* Synchronize on the class object, causing any other thread
* that attempts to initiate shutdown to stall indefinitely
*/
sequence();
halt(status);
}
}
/* Invoked by the JNI DestroyJavaVM procedure when the last non-daemon
* thread has finished. Unlike the exit method, this method does not
* actually halt the VM.
*/
static void shutdown() {
synchronized (lock) {
switch (state) {
case RUNNING: /* Initiate shutdown */
state = HOOKS;
break;
case HOOKS: /* Stall and then return */
case FINALIZERS:
break;
}
}
synchronized (Shutdown.class) {
sequence();
}
}
}
那么问题来了,为什么重启应用时为什么不执行钩子?
原因:
我们项目重启的脚本先执行kill 进程的命令 再启动进程,我们都知道kill -9 是强制杀掉进程,导致进程还没执行关机钩子就被杀掉了进程。属于暴力杀程序。所以同事为了方便脚本中都是使用 -9 。但是一般建议是使用kill -15 通知进程关机命令,让进程执行完存在的业务逻辑以及执行关机钩子相关业务再杀掉进程,这样不影响自身的业务流。
kill相关命令有大神总结的更好就引用了一下
linux kill命令参数及用法详解 - etwits - 博客园 (cnblogs.com)
验证:
再springboot 启动类中添加如下代码
/**
* 服务启动类(脚手架)
*
* @author open-gatway服务
* @date 2021-09-13 15:17:25
**/
@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableFeignClients(basePackages = "com.jdh")
public class OpenGatewayApplication {
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
//添加个日志输出的钩子
System.out.println("我胡汉三又回来了");
}
});
SpringApplication.run(OpenGatewayApplication.class, args);
}
}
Linux上面把jar 跑起来
1、执行kill -15 看是否复核预期,嗯···正常输出胡汉三又回来的日志;
kill-15回调钩子.png
2、再验证一下kill -9,,麻也没有,进程直接被干掉了;
kill-9强制.png
总结
要么使用File.delete(); 直接删除文件。要么使用File.deleteOnExit() 重启命令用kill -15 ;