一、使用
Java中可以通过Runtime.getRuntime().addShutdownHook添加一个钩子,JVM在退出的时候会调用我们注册的钩子,借助它我们可以实现资源清理等操作。比如:
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("jvm shutdown");
}
}));
}
程序正常退出、eixt()方法退出、kill、ctrl+c等都会触发钩子的调用,其本质上是一个Thread。
二、源码分析
先看看addShutdownHook的实现:
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
经过安全检查后调用ApplicationShutdownHooks的add方法:
class ApplicationShutdownHooks {
/* The set of registered hooks */
private static IdentityHashMap<Thread, Thread> hooks;
static {
try {
Shutdown.add(1 /* shutdown hook invocation order */,
false /* not registered if shutdown in progress */,
new Runnable() {
public void run() {
runHooks();
}
}
);
//初始化hooks
hooks = new IdentityHashMap<>();
} catch (IllegalStateException e) {
// application shutdown hooks cannot be added if
// shutdown is in progress.
hooks = null;
}
}
static synchronized void add(Thread hook) {
......
hooks.put(hook, hook);
}
}
hooks是一个map结构,在静态代码块中初始化,add方法就是向这个map中添加元素。需要着重关注静态代码块中的另一个函数调用:
Shutdown.add(1 /* shutdown hook invocation order */,
false /* not registered if shutdown in progress */,
new Runnable() {
public void run() {
runHooks();
}
}
);
看代码也是通过Shutdown.add注册了一个hook,这个hook的作用就是调用本类的runHooks方法:
static void runHooks() {
Collection<Thread> threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
}
for (Thread hook : threads) {
//异步执行注册的所有hook
hook.start();
}
for (Thread hook : threads) {
while (true) {
try {
//阻塞等待所有hook执行完毕
hook.join();
break;
} catch (InterruptedException ignored) {
}
}
}
}
runHooks的逻辑比较简单,就是取出所有注册的hook,然后异步调用,最终会通过join方法等待所有hook执行完毕。
现在回过头来看看Shutdown.add方法干了什么:
class Shutdown {
......
private static final int MAX_SYSTEM_HOOKS = 10;
//固定数组大小为10
private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];
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");
}
//注册hook到hooks数组中
hooks[slot] = hook;
}
}
......
}
通过add方法向Shutdown类的hooks数组中添加了一个hook,这里的hook和ApplicationShutdownHooks的hook不一样,是一个Runnable。
这里需要注意的是,hook数组的长度固定为10,现在使用了三个位置:
- 0:重置控制台(echo)
- 1:我们例子中的应用程序hook
- 2:文件删除钩子,将文件名添加到钩子中,JVM退出时会删除这些文件
ApplicationShutdownHooks中调用Shutdown.add传的参数是1,关于0和2的注册如下:
Console.java
sun.misc.SharedSecrets.getJavaLangAccess()
.registerShutdownHook(0 /* shutdown hook invocation order */,
false /* only register if shutdown is not in progress */,
new Runnable() {
public void run() {
try {
if (echoOff) {
//echo是一个native方法
echo(true);
}
} catch (IOException x) { }
}
});
DeleteOnExitHook.java
sun.misc.SharedSecrets.getJavaLangAccess()
.registerShutdownHook(2 /* Shutdown hook invocation order */,
true /* register even if shutdown in progress */,
new Runnable() {
public void run() {
runHooks();
}
}
);
static void runHooks() {
LinkedHashSet<String> theFiles;
synchronized (DeleteOnExitHook.class) {
theFiles = files;
files = null;
}
ArrayList<String> toBeDeleted = new ArrayList<>(theFiles);
//翻转顺序,后添加的先删除
Collections.reverse(toBeDeleted);
for (String filename : toBeDeleted) {
//删除文件
(new File(filename)).delete();
}
}
现在重新回到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;
}
}
}
}
从数组下标0开始依次调用hook。runHooks方法在sequence()方法中调用:
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();
.....
}
以运行时退出为例,调用exit退出程序:
System.exit(0);
System.exit方法实现如下:
public static void exit(int status) {
Runtime.getRuntime().exit(status);
}
进入Runtime.exit方法:
public void exit(int status) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkExit(status);
}
Shutdown.exit(status);
}
其中会调用Shutdown.exit方法:
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(status);
} else {
runMoreFinalizers = runFinalizersOnExit;
}
break;
}
}
]
synchronized (Shutdown.class) {
......
//调用hooks
sequence();
......
}
}





还没有评论,来说两句吧...