jdk版本:jdk8u40
在看到Saturn唯品会的分布式调度框架时,看到此框架使用了信号处理,并且外面关于Java信号处理机制的文章很少有写到JVM层面,所以笔者心血来潮写下了这篇关于Java信号处理机制的文章~
因为Java信号处理机制是依赖于底层操作系统的信号处理机制,本文重点关注于Java信号处理机制,所以并不会过度的去介绍操作系统的信号处理机制~
因为Java信号处理是可以Java层面自定义的,所以肯定分为Java层面的源码,和JVM层面源码的处理。由浅到深,所以接下来看到Java层面的处理。
先从一个案例看到Java层面如何自定义信号。
public class SignalTest {
public static void main(String[] args) throws InterruptedException {
SignalHandler signalHandler = signal -> System.out.println("关闭不了的,不要试啦");
Signal.handle(new Signal("INT"), signalHandler);
Thread.sleep(1000000);
}
}
这里自定义了SIGINT信号(shell的ctrl+c),这样的话ctrl+c并不会关闭JVM,反而仅仅是输出一句话而已。所以接下来我们需要明白他是如何执行的。
看到Signal.java类的构造方法
public Signal(String name) {
// findSignal方法会根据name找到JVM的映射编号
number = findSignal(name);
this.name = name;
// 如果传入的名字有误,那么直接抛出非法逻辑异常
if (number < 0) {
throw new IllegalArgumentException("Unknown signal: " + name);
}
}
构造方法很简单,会调用findSignal这个native方法去根据传入的名字找到对应的编码,如果没找到的话就直接抛出非法逻辑异常,本文后续JVM源码会介绍findSignal方法。
接下来继续看到Signal的静态方法handle。
public static synchronized SignalHandler handle(Signal sig,
SignalHandler handler)
throws IllegalArgumentException {
// 如果类型是NativeSignalHandler就调用getHandler,无需关心,这个给JNI使用
// 如果不是的话就是2.
long newH = (handler instanceof NativeSignalHandler) ?
((NativeSignalHandler)handler).getHandler() : 2;
// 拿到原有sig.number对应的处理函数。
// 这里顺带检测了当前Java程序是否有资格去修改某些信号。
long oldH = handle0(sig.number, newH);
signals.put(sig.number, sig);
synchronized (handlers) {
// 删除原有的SignalHandler
SignalHandler oldHandler = handlers.get(sig);
handlers.remove(sig);
// 如果是2就代表不是NativeSignalHandler,所以直接添加到集合中。
if (newH == 2) {
handlers.put(sig, handler);
}
………… // 省略一些判断
}
}
这里也非常的简单,调用handle0这个native方法获取到sig.number之前注册的处理函数,并且会顺带去检测当前是否有资格去修改信息(因为有些信号是强制不能修改的,有些信号是JVM内部要使用的),如果不能修改的话会抛出非法逻辑异常。最终会把Signal对象作为key,SignalHandler作为value放入到Hashtable中(其实也很明显了,放入一张表中,后续查表)
// 此方法由JVM调用,传入的参数为信号的编号
private static void dispatch(final int number) {
// 通过编号找到Signal对象
final Signal sig = signals.get(number);
// 通过Signal对象找到SignalHandler
final SignalHandler handler = handlers.get(sig);
// 包装一个Runnable
Runnable runnable = new Runnable () {
public void run() {
handler.handle(sig);
}
};
// 异步处理,不能影响后续信号的处理
if (handler != null) {
new Thread(runnable, sig + " handler").start();
}
}
Signal.java类中存在dispatch方法,此方法由JVM调用,所以在Java层面你会找不到调用点,这个方法很简单,就是查表,然后异步执行对应的SignalHandler(而SignalHandler就是你自定义的逻辑代码)~
所以下文会介绍findSignal、handle0这两个native方法,以及JVM如何调用的dispatch方法。
先介绍findSignal这个native方法,由上文我们得知,此方法入参为信号名称,返回值为JVM的信号编号,所以肯定是一个查表的过程,src/os/linux/vm/jvm_linux.cpp 文件中定义
struct siglabel siglabels[] = {
/* derived from /usr/include/bits/signum.h on RH7.2 */
"HUP", SIGHUP, /* Hangup (POSIX). */
"INT", SIGINT, /* Interrupt (ANSI). */
"QUIT", SIGQUIT, /* Quit (POSIX). */
"ILL", SIGILL, /* Illegal instruction (ANSI). */
"TRAP", SIGTRAP, /* Trace trap (POSIX). */
"ABRT", SIGABRT, /* Abort (ANSI). */
"IOT", SIGIOT, /* IOT trap (4.2 BSD). */
"BUS", SIGBUS, /* BUS error (4.2 BSD). */
"FPE", SIGFPE, /* Floating-point exception (ANSI). */
"KILL", SIGKILL, /* Kill, unblockable (POSIX). */
"USR1", SIGUSR1, /* User-defined signal 1 (POSIX). */
"SEGV", SIGSEGV, /* Segmentation violation (ANSI). */
"USR2", SIGUSR2, /* User-defined signal 2 (POSIX). */
"PIPE", SIGPIPE, /* Broken pipe (POSIX). */
"ALRM", SIGALRM, /* Alarm clock (POSIX). */
"TERM", SIGTERM, /* Termination (ANSI). */
#ifdef SIGSTKFLT
"STKFLT", SIGSTKFLT, /* Stack fault. */
#endif
"CLD", SIGCLD, /* Same as SIGCHLD (System V). */
"CHLD", SIGCHLD, /* Child status has changed (POSIX). */
"CONT", SIGCONT, /* Continue (POSIX). */
"STOP", SIGSTOP, /* Stop, unblockable (POSIX). */
"TSTP", SIGTSTP, /* Keyboard stop (POSIX). */
"TTIN", SIGTTIN, /* Background read from tty (POSIX). */
"TTOU", SIGTTOU, /* Background write to tty (POSIX). */
"URG", SIGURG, /* Urgent condition on socket (4.2 BSD). */
"XCPU", SIGXCPU, /* CPU limit exceeded (4.2 BSD). */
"XFSZ", SIGXFSZ, /* File size limit exceeded (4.2 BSD). */
"VTALRM", SIGVTALRM, /* Virtual alarm clock (4.2 BSD). */
"PROF", SIGPROF, /* Profiling alarm clock (4.2 BSD). */
"WINCH", SIGWINCH, /* Window size change (4.3 BSD, Sun). */
"POLL", SIGPOLL, /* Pollable event occurred (System V). */
"IO", SIGIO, /* I/O now possible (4.2 BSD). */
"PWR", SIGPWR, /* Power failure restart (System V). */
#ifdef SIGSYS
"SYS", SIGSYS /* Bad system call. Only on some Linuxen! */
#endif
};
这里就已经限定了你传入的名称,比如操作系统SIGINT信号就只能传"INT"。
// src/share/native/sun/misc/Signal.c
JNIEXPORT jint JNICALL
Java_sun_misc_Signal_findSignal(JNIEnv *env, jclass cls, jstring name)
{
………… // 省略无关代码
res = JVM_FindSignal(cname);
return res;
}
// src/os/linux/vm/jvm_linux.cpp 文件
JVM_ENTRY_NO_ENV(jint, JVM_FindSignal(const char *name))
// 查表
for(uint i=0; i
这里非常的简单,就是一个查表的过程,如果查到了就返回对应的编号,如果没查找就返回-1.
接下来看到handle0这个native方法的处理细节
// src/share/native/sun/misc/Signal.c
JNIEXPORT jlong JNICALL
Java_sun_misc_Signal_handle0(JNIEnv *env, jclass cls, jint sig, jlong handler)
{
return ptr_to_jlong(JVM_RegisterSignal(sig, jlong_to_ptr(handler)));
}
// src/os/linux/vm/jvm_linux.cpp 文件
JVM_ENTRY_NO_ENV(void*, JVM_RegisterSignal(jint sig, void* handler))
// 默认情况下为2,特殊情况下比如JNI信号处理可能是一个方法地址
// 为2的情况下,使用JVM默认的信号处理函数os::user_handler()
void* newHandler = handler == (void *)2
? os::user_handler()
: handler;
switch (sig) {
// JVM内部需要使用的
case INTERRUPT_SIGNAL:
case SIGFPE:
case SIGILL:
case SIGSEGV:
case BREAK_SIGNAL:
return (void *)-1;
// 这些信号需要判断JVM参数ReduceSignalUsage是否开启
case SHUTDOWN1_SIGNAL:
case SHUTDOWN2_SIGNAL:
case SHUTDOWN3_SIGNAL:
if (ReduceSignalUsage) return (void*)-1;
if (os::Linux::is_sig_ignored(sig)) return (void*)1;
}
// 调用系统调用,将sig编号的处理函数变换成newHandler
void* oldHandler = os::signal(sig, newHandler);
if (oldHandler == os::user_handler()) {
return (void *)2;
} else {
return oldHandler;
}
JVM_END
此时,我们调用handle0这个native 方法把信号对应的处理函数更改成os::user_handler(),所以当其他进程给当前JVM进程发送信号时,JVM会调用os::user_handler()方法。
所以接下来看到os::user_handler()方法,src/os/linux/vm/os_linux.cpp 文件
void* os::user_handler() {
return CAST_FROM_FN_PTR(void*, UserHandler);
}
// int sig 信号编号
// void *siginfo 信号信息
// void *context 上下文信息
// 这三个参数都是操作系统传入的
static void
UserHandler(int sig, void *siginfo, void *context) {
// 在Java层面的信号处理也看到了,如果来一个信号就会使用一个线程,
// 如果其他进程一致发送SIGINT就会立马创建很多线程
// 所以需要做限制。
if (sig == SIGINT && Atomic::add(1, &sigint_count) > 1)
return;
// 通讯给signal dispatcher线程
os::signal_notify(sig);
}
目前,我们得明白,当其他进程发送信号给JVM进程时,JVM会调用user_handler方法,转而调用UserHandler方法,而在UserHandler方法中会调用os::signal_notify(sig)方法,此方法会通过队列把当前信号编号传给signal dispatcher线程,所以接下来,我们需要分析signal dispatcher线程如何接受到数据,以及如何处理的数据。
在JVM启动时,会调用os::signal_init的方法初始化关于信号的处理,src/share/vm/runtime/os.cpp文件中os::signal_init方法
void os::signal_init() {
if (!ReduceSignalUsage) {
EXCEPTION_MARK;
Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(), true, CHECK);
instanceKlassHandle klass (THREAD, k);
instanceHandle thread_oop = klass->allocate_instance_handle(CHECK);
const char thread_name[] = "Signal Dispatcher";
Handle string = java_lang_String::create_from_str(thread_name, CHECK);
// Initialize thread_oop to put it into the system threadGroup
Handle thread_group (THREAD, Universe::system_thread_group());
JavaValue result(T_VOID);
JavaCalls::call_special(&result, thread_oop,
klass,
vmSymbols::object_initializer_name(),
vmSymbols::threadgroup_string_void_signature(),
thread_group,
string,
CHECK);
KlassHandle group(THREAD, SystemDictionary::ThreadGroup_klass());
JavaCalls::call_special(&result,
thread_group,
group,
vmSymbols::add_method_name(),
vmSymbols::thread_void_signature(),
thread_oop, // ARG 1
CHECK);
os::signal_init_pd();
{ MutexLocker mu(Threads_lock);
JavaThread* signal_thread = new JavaThread(&signal_thread_entry);
java_lang_Thread::set_thread(thread_oop(), signal_thread);
java_lang_Thread::set_priority(thread_oop(), NearMaxPriority);
java_lang_Thread::set_daemon(thread_oop());
signal_thread->set_threadObj(thread_oop());
Threads::add(signal_thread);
Thread::start(signal_thread);
}
// 注册SIGQUIT信号的处理函数为os::user_handler()
os::signal(SIGBREAK, os::user_handler());
}
}
这里代码虽然很多,但是非常的简单,就是用c++的代码在创建一个Java线程,等同于在Java层面new Thread一致。而不管是在c++层面还是Java层面创建一个线程,都需要有一个线程启动后的回调点。这里的回调点为signal_thread_entry函数。
static void signal_thread_entry(JavaThread* thread, TRAPS) {
os::set_priority(thread, NearMaxPriority);
while (true) {
int sig;
{
// 来信号后,其他线程 会往这个线程发送信号编号,这边就会被唤醒。
// 获取到信号的编号。
sig = os::signal_wait();
}
………… // 省略无关代码
// 这里调用Java的Signal类的dispatch方法
// 到此为止,我们就整个环都闭上了
HandleMark hm(THREAD);
Klass* k = SystemDictionary::resolve_or_null(vmSymbols::sun_misc_Signal(), THREAD);
KlassHandle klass (THREAD, k);
if (klass.not_null()) {
JavaValue result(T_VOID);
JavaCallArguments args;
args.push_int(sig);
JavaCalls::call_static(
&result,
klass,
vmSymbols::dispatch_name(),
vmSymbols::int_void_signature(),
&args,
THREAD
);
}
}
}
从Java到JVM,再从JVM到Java,跨度是非常的大,所以笔者也只能把Java和JVM分为2部分来讲解,尽量用简单的方式讲述明白~ 并且当我们研究Java的信号处理时,就可以把操作系统的信号处理当作黑盒来理解即可,只需要明白当来信号时操作系统会回调JVM中那一个方法即可~