HotSpot JVM中的signal

Posted 底层技术研究

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HotSpot JVM中的signal相关的知识,希望对你有一定的参考价值。

本文将会从源码角度看下HotSpot JVM中是如何处理signal的。有关signal机制的简单介绍,可以看下另外一篇文章: 。


OpenJDK源码的版本:


➜  jdk hg id

76072a077ee1 jdk-11+28


在JVM启动的过程代码中,我们首先会找到 SR_initialize 方法


C++文件src/hotspot/os/linux/os_linux.cpp


// Signal number used to suspend/resume a thread

static int SR_signum = SIGUSR2;

...

static int SR_initialize() {

  struct sigaction act;

  char *s;


  // Get signal number to use for suspend/resume

  if ((s = ::getenv("_JAVA_SR_SIGNUM")) != 0) {

    int sig = ::strtol(s, 0, 10);

    if (sig > MAX2(SIGSEGV, SIGBUS) &&  

        sig < NSIG) {                   

      SR_signum = sig;

    } else {

      warning(...);

    }

  }

  ...

  act.sa_flags = SA_RESTART|SA_SIGINFO;

  act.sa_handler = (void (*)(int)) SR_handler;

  ...

  if (sigaction(SR_signum, &act, 0) == -1) {

    return -1;

  }

  ...

  return 0;

}


该方法为SIGUSR2信号(可通过_JAVA_SR_SIGNUM环境变量修改)设置了处理函数SR_handler,用于支持线程的suspend/resume。


接着,我们会找到 os::Linux::install_signal_handlers 方法


C++文件src/hotspot/os/linux/os_linux.cpp


// install signal handlers for signals that HotSpot needs to

// handle in order to support Java-level exception handling.


void os::Linux::install_signal_handlers() {

  if (!signal_handlers_are_installed) {

    signal_handlers_are_installed = true;

    ...

    set_signal_handler(SIGSEGV, true);

    set_signal_handler(SIGPIPE, true);

    set_signal_handler(SIGBUS, true);

    set_signal_handler(SIGILL, true);

    set_signal_handler(SIGFPE, true);

#if defined(PPC64)

    set_signal_handler(SIGTRAP, true);

#endif

    set_signal_handler(SIGXFSZ, true);

    ...

  }

}


该方法涉及到了很多signal,而且都是调用set_signal_handler方法设置的handler,继续看下这个方法


C++文件src/hotspot/os/linux/os_linux.cpp


void os::Linux::set_signal_handler(int sig, bool set_installed) {

  ...

  struct sigaction sigAct;

  sigfillset(&(sigAct.sa_mask));

  sigAct.sa_handler = SIG_DFL;

  if (!set_installed) {

    sigAct.sa_flags = SA_SIGINFO|SA_RESTART;

  } else {

    sigAct.sa_sigaction = signalHandler;

    sigAct.sa_flags = SA_SIGINFO|SA_RESTART;

  }

  ...

  int ret = sigaction(sig, &sigAct, &oldAct);

  assert(ret == 0, "check");

  ...

}


根据set_signal_handler方法我们可以看到,install_signal_handlers方法为SIGSEGV、SIGPIPE等很多signal都设置了同样的处理函数signalHandler,用于实现Java-level exception handling。


继续JVM的启动代码,我们会找到 os::initialize_jdk_signal_support 方法,这个方法以及其相关逻辑也是这篇文章要重点讲解的内容。


C++文件src/hotspot/share/runtime/os.cpp


void os::initialize_jdk_signal_support(TRAPS) {

  if (!ReduceSignalUsage) {

    // Setup JavaThread for processing signals

    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());

    Handle thread_oop = JavaCalls::construct_new_instance(SystemDictionary::Thread_klass(),

                           vmSymbols::threadgroup_string_void_signature(),

                           thread_group,

                           string,

                           CHECK);

    ...

    { MutexLocker mu(Threads_lock);

      JavaThread* signal_thread = new JavaThread(&signal_thread_entry);

      ...

      java_lang_Thread::set_thread(thread_oop(), signal_thread);

      ...

      Thread::start(signal_thread);

    }

    // Handle ^BREAK

    os::signal(SIGBREAK, os::user_handler());

  }

}


该方法启动了一个名叫Signal Dispatcher的线程,其入口函数为signal_thread_entry。然后为SIGBREAK信号(其实就是SIGQUIT信号)注册了处理函数os::user_handler()。


看下 signal_thread_entry 入口函数


C++文件src/hotspot/share/runtime/os.cpp


static void signal_thread_entry(JavaThread* thread, TRAPS) {

  os::set_priority(thread, NearMaxPriority);

  while (true) {

    int sig;

    {

      ...

      sig = os::signal_wait();

    }

    if (sig == os::sigexitnum_pd()) {

       // Terminate the signal thread

       return;

    }


    switch (sig) {

      case SIGBREAK: {

        // Check if the signal is a trigger to start the Attach Listener - in that

        // case don't print stack traces.

        if (!DisableAttachMechanism && AttachListener::is_init_trigger()) {

          continue;

        }

        // Print stack traces

        // Any SIGBREAK operations added here should make sure to flush

        // the output stream (e.g. tty->flush()) after output.  See 4803766.

        // Each module also prints an extra carriage return after its output.

        VM_PrintThreads op;

        VMThread::execute(&op);

        VM_PrintJNI jni_op;

        VMThread::execute(&jni_op);

        VM_FindDeadlocks op1(tty);

        VMThread::execute(&op1);

        Universe::print_heap_at_SIGBREAK();

        if (PrintClassHistogram) {

          VM_GC_HeapInspection op1(tty, true /* force full GC before heap inspection */);

          VMThread::execute(&op1);

        }

        if (JvmtiExport::should_post_data_dump()) {

          JvmtiExport::post_data_dump();

        }

        break;

      }

      default: {

        // Dispatch the signal to java

        HandleMark hm(THREAD);

        Klass* klass = SystemDictionary::resolve_or_null(vmSymbols::jdk_internal_misc_Signal(), THREAD);

        if (klass != 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

          );

        }

        ...

      }

    }

  }

}


该方法大体的处理逻辑为,等待signal,然后看signal是否是SIGBREAK(即SIGQUIT),如果是就启动attach listener或者打印各种信息,比如线程堆栈信息。如果不是,就会调用Java类jdk.internal.misc.Signal的dispatch方法,交给其进一步处理。


看下dispatch方法


Java类jdk.internal.misc.Signal


/* Called by the VM to execute Java signal handlers. */

private static void dispatch(final int number) {

    final Signal sig = signals.get(number);

    final Signal.Handler handler = handlers.get(sig);


    Runnable runnable = new Runnable () {

        public void run() {

          // Don't bother to reset the priority. Signal handler will

          // run at maximum priority inherited from the VM signal

          // dispatch thread.

          // Thread.currentThread().setPriority(Thread.NORM_PRIORITY);

            handler.handle(sig);

        }

    };

    if (handler != null) {

        new Thread(null, runnable, sig + " handler", 0, false).start();

    }

}


该方法就是根据signal获取其handler,然后开启一个新的Java线程执行这个handler。


看下signal和其handler的注册


Java类jdk.internal.misc.Signal


/**

 * Registers a signal handler.

 */

public static synchronized Signal.Handler handle(Signal sig,

                                                Signal.Handler handler)

    throws IllegalArgumentException {

    ...

    long newH = (handler instanceof NativeHandler) ?

                  ((NativeHandler)handler).getHandler() : 2;

    long oldH = handle0(sig.number, newH);

    if (oldH == -1) {

        throw new IllegalArgumentException

            ("Signal already used by VM or OS: " + sig);

    }

    signals.put(sig.number, sig);

    synchronized (handlers) {

        Signal.Handler oldHandler = handlers.get(sig);

        handlers.remove(sig);

        if (newH == 2) {

            handlers.put(sig, handler);

        }

        ...

    }

}

...

/* Registers a native signal handler, and returns the old handler.

 * Handler values:

 *   0     default handler

 *   1     ignore the signal

 *   2     call back to Signal.dispatch

 *   other arbitrary native signal handlers

 */

private static native long handle0(int sig, long nativeH);


该方法会为signal注册其Java版本的Handler。首先,它会保存下signal和其handler的映射关系,供dispatch方法使用,然后再调用handle0这个native方法,去操作系统层面进行真正的signal注册。


看下handle0方法


C文件src/java.base/share/native/libjava/Signal.c


JNIEXPORT jlong JNICALL

Java_jdk_internal_misc_Signal_handle0(JNIEnv *env, jclass cls, jint sig, jlong handler)

{

    return ptr_to_jlong(JVM_RegisterSignal(sig, jlong_to_ptr(handler)));

}


再看下JVM_RegisterSignal


C++文件src/hotspot/os/posix/jvm_posix.cpp


JVM_ENTRY_NO_ENV(void*, JVM_RegisterSignal(jint sig, void* handler))

  void* newHandler = handler == (void *)2

                   ? os::user_handler()

                   : handler;

  switch (sig) {

  ...

  }


  void* oldHandler = os::signal(sig, newHandler);

  if (oldHandler == os::user_handler()) {

      return (void *)2;

  } else {

      return oldHandler;

  }

JVM_END


该方法中,如果handler参数为2的话,使用的真实handler是os::user_handler(),否则的话就是使用传入的handler。之后调用os::signal为signal注册这个处理函数。


细心的朋友可能还记得,在Signal Dispatcher线程启动的最后,也是调用了os::signal方法,为SIGBREAK (SIGQUIT) 注册处理函数,而那个处理函数,也是os::user_handler()。


接下来我们看下 os::user_handler 及其相关函数


C++文件src/hotspot/os/linux/os_linux.cpp


static void UserHandler(int sig, void *siginfo, void *context) {

  ...

  os::signal_notify(sig);

}


void* os::user_handler() {

  return CAST_FROM_FN_PTR(void*, UserHandler);

}

...

// a counter for each possible signal value

static volatile jint pending_signals[NSIG+1] = { 0 };


// Linux(POSIX) specific hand shaking semaphore.

static Semaphore* sig_sem = NULL;

...

void os::signal_notify(int sig) {

  if (sig_sem != NULL) {

    Atomic::inc(&pending_signals[sig]);

    sig_sem->signal();

  } else {

    ...

  }

}


static int check_pending_signals() {

  ...

  for (;;) {

    for (int i = 0; i < NSIG + 1; i++) {

      jint n = pending_signals[i];

      if (n > 0 && n == Atomic::cmpxchg(n - 1, &pending_signals[i], n)) {

        return i;

      }

    }

    JavaThread *thread = JavaThread::current();

    ThreadBlockInVM tbivm(thread);


    bool threadIsSuspended;

    do {

      ...

      sig_sem->wait();


      // were we externally suspended while we were waiting?

      threadIsSuspended = thread->handle_special_suspend_equivalent_condition();

      ...

    } while (threadIsSuspended);

  }

}


int os::signal_wait() {

  return check_pending_signals();

}


上面几个方法都是相关联的,所以放在了一起。我们还继续看os::user_handler方法。


由这个方法我们可以知道,signal的真实处理函数为UserHandler,而UserHandler的处理逻辑为调用os::signal_notify,通知signal发生。


在os::signal_notify方法里,对应的pending_signals加1,然后调用Semaphore类型变量sig_sem的signal方法,通知等待端有signal了。


而os::signal_wait就是这个等待端,它调用check_pending_signals方法,内部逻辑是检查pending_signals,如果有就返回,如果没有就调用sig_sem->wait()等待。


那谁在调用os::signal_wait呢?对,就是Signal Dispatcher这个线程,该线程启动后就开始调用os::signal_wait,等待signal,wait方法返回之后,获取这个signal,然后调用Java类jdk.internal.misc.Signal的dispatch方法,交给其进一步处理。


至此,整个signal的处理逻辑就终于形成了一个闭环。


 

整体逻辑我们再梳理下:


首先是启动Signal Dispatcher线程,调用os::signal_wait方法等待signal的发生,如果发生了,就调用Java类jdk.internal.misc.Signal的dispatch方法,交给其进一步处理。


之后我们可以调用Java类jdk.internal.misc.Signal的handle方法,为signal注册Java版的handler。


在这个handle方法里面,先是保存signal及其handler的映射关系,供dispatch方法使用,接着调用handle0这个native方法,进行signal的系统级注册。


handle0里为signal注册的handler都是统一的UserHandler方法。


再之后signal发生,触发UserHandler函数,该函数又会调用os::signal_notify通知Signal Dispatcher线程有新signal了。


Signal Dispatcher线程从os::signal_wait方法中返回,获取到这个signal,然后调用了Java类jdk.internal.misc.Signal的dispatch方法。


该dispatch方法找到对应的Java版的handler,开启新Java Thread执行这个handler。


 

整个逻辑清楚了,那我们接下来看下,都是谁调用了handle方法,注册了handler。


Java类java.lang.Terminator


class Terminator {


    private static Signal.Handler handler = null;


    /* Invocations of setup and teardown are already synchronized

     * on the shutdown lock, so no further synchronization is needed here

     */


    static void setup() {

        if (handler != null) return;

        Signal.Handler sh = new Signal.Handler() {

            public void handle(Signal sig) {

                Shutdown.exit(sig.getNumber() + 0200);

            }

        };

        handler = sh;

        // When -Xrs is specified the user is responsible for

        // ensuring that shutdown hooks are run by calling

        // System.exit()

        try {

            Signal.handle(new Signal("HUP"), sh);

        } catch (IllegalArgumentException e) {

        }

        try {

            Signal.handle(new Signal("INT"), sh);

        } catch (IllegalArgumentException e) {

        }

        try {

            Signal.handle(new Signal("TERM"), sh);

        } catch (IllegalArgumentException e) {

        }

    }


    static void teardown() {

        /* The current sun.misc.Signal class does not support

         * the cancellation of handlers

         */

    }

}


可以看到,是Terminator的setup方法为HUP、INI、TERM信号注册了handler,而handler的处理逻辑就是shutdown整个JVM。


而这个方法又是被System的initPhase1方法调用的。


Java类java.lang.System


private static void initPhase1() {

    ...

    // Setup Java signal handlers for HUP, TERM, and INT (where available).

    Terminator.setup();

    ...

}


最后看下Java进程的的线程堆栈,确认下Signal Dispatcher线程是真实存在的。


➜  ~ jstack 30460

2019-01-03 16:17:08

Full thread dump Java HotSpot(TM) 64-Bit Server VM (11+28 mixed mode):

...


"Signal Dispatcher" #4 daemon prio=9 os_prio=0 cpu=0.22ms elapsed=49.34s tid=0x00007f15b013b000 nid=0x7711 runnable  [0x0000000000000000]

   java.lang.Thread.State: RUNNABLE

...


JNI global refs: 15, weak refs: 0


完。 


更好的排版及最新的校正请点击阅读原文。

以上是关于HotSpot JVM中的signal的主要内容,如果未能解决你的问题,请参考以下文章

HotSpot JVM中的signal

JVM——HotSpot中的GC实现

JVM:Hotspot虚拟机中的对象

JVM详解之:HotSpot VM中的Intrinsic methods

关于JVM和Hotspot,你也许有这么几个容易晕的问题

具有大量引用字段(数组除外)的对象是否会破坏Hotspot JVM的GC堆遍历性能?