背景:
項目中發(fā)現(xiàn)文件服務(wù)的臨時文件越來越多導(dǎo)致磁盤吃滿梯找。根據(jù)文件名生成的規(guī)則找到代碼相關(guān)模塊诊胞,乍一看這個代碼貌似沒問題。(可能把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);
}
}
點(diǎn)進(jìn)來源碼一看睦授,我丟···DeleteOnExitHook.add(path); 原來是系統(tǒng)退出時執(zhí)行刪除鉤子两芳,那為什么我們重啟項目沒有將這些臨時文件刪除掉呢?疑問
/**
* 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);
}
繼續(xù)點(diǎn)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()
//在這里初始化這個類的時候就注冊一個線程任務(wù)執(zhí)行鉤子
.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);
}
//執(zhí)行鉤子去枷, 那什么時候執(zhí)行鉤子呢怖辆?
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();
}
}
}
繼續(xù)看下這個方法 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) {
// 注冊關(guān)機(jī)鉤子
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();
}
});
}
關(guān)機(jī)對象
/*
* 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");
}
// 注冊鉤子線程任務(wù)
hooks[slot] = hook;
}
}
/* Run all registered shutdown hooks
* 當(dāng)系統(tǒng)關(guān)機(jī)時遍歷運(yùn)行所有鉤子線程任務(wù),删顶,被調(diào)用方都在同類方法里 竖螃,這里就重復(fù)粘貼
*/
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();
}
}
}
那么問題來了,為什么重啟應(yīng)用時為什么不執(zhí)行鉤子逗余?
原因:
我們項目重啟的腳本先執(zhí)行kill 進(jìn)程的命令 再啟動進(jìn)程特咆,我們都知道kill -9 是強(qiáng)制殺掉進(jìn)程,導(dǎo)致進(jìn)程還沒執(zhí)行關(guān)機(jī)鉤子就被殺掉了進(jìn)程猎荠。屬于暴力殺程序坚弱。所以同事為了方便腳本中都是使用 -9 。但是一般建議是使用kill -15 通知進(jìn)程關(guān)機(jī)命令关摇,讓進(jìn)程執(zhí)行完存在的業(yè)務(wù)邏輯以及執(zhí)行關(guān)機(jī)鉤子相關(guān)業(yè)務(wù)再殺掉進(jìn)程荒叶,這樣不影響自身的業(yè)務(wù)流。
kill相關(guān)命令有大神總結(jié)的更好就引用了一下
linux kill命令參數(shù)及用法詳解 - etwits - 博客園 (cnblogs.com)
驗證:
再springboot 啟動類中添加如下代碼
/**
* 服務(wù)啟動類(腳手架)
*
* @author open-gatway服務(wù)
* @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输虱、執(zhí)行kill -15 看是否復(fù)核預(yù)期些楣,嗯···正常輸出胡漢三又回來的日志;
kill-15回調(diào)鉤子.png
2、再驗證一下kill -9,,麻也沒有愁茁,進(jìn)程直接被干掉了蚕钦;
kill-9強(qiáng)制.png
總結(jié)
要么使用File.delete(); 直接刪除文件。要么使用File.deleteOnExit() 重啟命令用kill -15 ;