JVM中的系统线程


发布于 2024-07-26 / 20 阅读 / 0 评论 /
JVM中有大量的用户线程和系统线程,系统线程主要是维护JVM的正常运行。

通过jstack工具可以查看JVM中所有的线程,其中有一些比较特殊的系统线程,下面主要讲讲这些系统线程的作用。

1.Attach Listener线程

Attach Listener线程是负责接收外部的命令,对该命令进行执行,并把结果返回给发送者。通常我们会去用一些命令与JVM进行交互,比如java -version、jmap、jstack等。如果该线程在JVM启动的时候没有初始化,那么用户在第一次执行JVM命令时,该线程会被启动。

堆栈信息如下所示:

"Attach Listener" #173 daemon prio=9 os_prio=0 cpu=0.31ms elapsed=0.20s tid=0x00007f9ea0001000 nid=0x5edf waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

该线程为daemon线程,大部分时间处于waiting on condition状态。

2.DestroyJavaVM线程

JVM的main线程结束后,会调用JNI中的jni_destoryjavavm方法唤起DestroyJavaVM线程。

DestroyJavaVM的线程逻辑定义在threads.cpp中的destroy_vm方法中,如下所示:

void Threads::destroy_vm() {
  JavaThread* thread = JavaThread::current();

#ifdef ASSERT
  _vm_complete = false;
#endif
  // Wait until we are the last non-daemon thread to execute, or
  // if we are a daemon then wait until the last non-daemon thread has
  // executed.
  bool daemon = java_lang_Thread::is_daemon(thread->threadObj());
  int expected = daemon ? 0 : 1;
  {
    MonitorLocker nu(Threads_lock);
    while (Threads::number_of_non_daemon_threads() > expected)
      // This wait should make safepoint checks, wait without a timeout.
      nu.wait(0);
  }

  EventShutdown e;
  if (e.should_commit()) {
    e.set_reason("No remaining non-daemon Java threads");
    e.commit();
  }

  // Hang forever on exit if we are reporting an error.
  if (ShowMessageBoxOnError && VMError::is_error_reported()) {
    os::infinite_sleep();
  }
  os::wait_for_keypress_at_exit();

  // run Java level shutdown hooks
  thread->invoke_shutdown_hooks();

  before_exit(thread);

  thread->exit(true);

  ……
}

JVM在Jboss服务器启动之后就会唤起DestroyJavaVM线程,之后处于等待状态,等待其他线程(java线程和native线程)退出时通知他卸载JVM。线程退出时会判断自己是否是整个JVM中最后一个非daemon线程,如果是最后一个非daemon线程,则通知DestroyJavaVM线程卸载JVM。

如果线程退出时,判断自己不是最后一个非daemon线程,那么调用thread.exit(false),并抛出thread_end事件,JVM不退出。

如果线程退出时,判断自己是最后一个非daemon线程,那么先调用invoke_shutdown_hooks方法触发所有Java级别的shutdown钩子函数的执行(可参考《JVM shutdown hook》文章内容),然后调用before_exit()方法,抛出两个事件:

(1)thread_end线程结束事件

(2)VM的death事件,然后调用thread.exit(true)方法,接下来把线程从active_list卸下、删除线程等一系列工作完成后,通知正在等待的DestroyJavaVM线程执行卸载JVM操作。

堆栈信息如下所示:

"DestroyJavaVM" #170 prio=5 os_prio=0 cpu=17871.39ms elapsed=34.59s tid=0x00007f9f1c016000 nid=0x4ba0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

该线程不是daemon线程。

3.Signal Dispatcher线程

Attach Listener线程负责接收外部JVM命令,当命令接收成功后,会把指令交给Signal Dispatcher线程分发到不同的模块进行处理,并且返回结果。该线程也是在第一次接收外部JVM命令时进行初始化的。

堆栈信息如下所示:

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 cpu=0.50ms elapsed=84.44s tid=0x00007f9f1c091000 nid=0x4bb6 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

该线程是daemon线程。

4.Finalizer线程

这个线程也是main线程之后创建的,优先级为8,主要用于垃圾收集前,调用对象的finalize方法。

(1)只有当一轮垃圾收集时,才会开始调用finalize方法,并不是所有对象的finalize方法都会被执行。

(2)该线程为daemon线程,如果虚拟机中没有其他非daemon线程,不管该线程是否执行完finalize方法,JVM也会退出。

(3)JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,由Finalizer线程来处理。最后将该Finalizer对象的引用置为null,由垃圾收集器来回收。

(4)JVM为什么要单独用一个线程来执行finalize方法呢?如果JVM的垃圾收集线程自己来做,很有可能由于该方法中的误操作导致GC线程停止或不可控,这对GC线程来说事一种灾难。

堆栈信息如下所示:

"Finalizer" #3 daemon prio=8 os_prio=0 cpu=33.64ms elapsed=84.50s tid=0x00007f9f1c083800 nid=0x4bb0 in Object.wait() [0x00007f9eee786000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
        - locked <0x000000070000dab8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

5.Reference Handler线程

JVM在创建main线程后就创建Reference Handler线程,其优先级最高,为10,主要用于引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题。

堆栈信息如下所示:

"Reference Handler" #2 daemon prio=10 os_prio=0 cpu=23.54ms elapsed=84.50s tid=0x00007f9f1c077800 nid=0x4baf in Object.wait() [0x00007f9eeeaa7000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x000000070000df80> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)