从 JVM 视角看看 Java 守护线程
作者: / 2019-10-19 / 浏览次数:

The Java Virtual Machine exits when the only threads running are all daemon threads.

This method must be invoked before the thread is started.

里面提到了 3 点信息,一一来做下解释:

把 Java 线程分成 2 类,一类是用户线程,也就是我们创建线程时,默认的一类线程,属性 daemon = false;另一类是守护线程,当我们设置 daemon = true 时,就是这类线程。

两者的一般关系是:用户线程就是运行在前台的线程,守护线程就是运行在后台的线程,一般情况下,守护线程是为用户线程提供一些服务。比如在 Java 中,我们常说的 GC 内存回收线程就是守护线程。

上面第二点翻译过来是:当所有用户线程都执行完,只存在守护线程在运行时,JVM 就退出。看了网上资料以及一些书籍,全都有这句话,但是也都只是有这句话,没有讲明是为啥,好像这句话就成了定理,不需要证明的样子。既然咱最近搭建了 JVM Debug 环境,那就得来查个究竟。

我们看到 JVM 源码 thread.cpp 文件,这里是实现线程的代码。我们通过上面那句话,说明是有一个地方监测着当前非守护线程的数量,不然怎么知道现在只剩下守护线程呢?很有可能是在移除线程的方法里面,跟着这个思路,我们看看该文件的 remove 方法。代码如下。

/**
 * 移除线程 p
 */void Threads::remove { // Reclaim the ObjectMonitors from the omInUseList and omFreeList of the moribund thread.
 ObjectSynchronizer::omFlush; /**
 * 创建一个监控锁对象 ml
 */
 // Extra scope needed for Thread_lock, so we can check
 // that we do not remove thread without safepoint code notice
 { MonitorLocker ml;
 assert- includes, "p must be present"); // Maintain fast thread list
 ThreadsSMRSupport::remove_thread; // 当前线程数减 1
 _number_of_threads--; if  { /**
 * 非守护线程数量减 1
 */
 _number_of_non_daemon_threads--; /**
 * 当非守护线程数量为 1 时,唤醒在 destroy_vm 方法等待的线程
 */
 // Only one thread left, do a notify on the Threads_lock so a thread waiting
 // on destroy_vm will wake up.
 if  == 1) {
 ml.notify_all;
 }
 } /**
 * 移除掉线程
 */
 ThreadService::remove_thread; // Make sure that safepoint code disregard this thread. This is needed since
 // the thread might mess around with locks after this point. This can cause it
 // to do callbacks into the safepoint code. However, the safepoint code is not aware
 // of this thread since it is removed from the queue.
 p- set_terminated_value;
 } // unlock Threads_lock
 // Since Events::log uses a lock, we grab it outside the Threads_lock
 Events::log);
}

我在里面加了一些注释,可以发现,果然是我们想的那样,里面有记录着非守护线程的数量,而且当非守护线程为 1 时,就会唤醒在 destory_vm 方法里面等待的线程,我们确认已经找到 JVM 在非守护线程数为 1 时会触发唤醒监控 JVM 退出的线程代码。紧接着我们看看 destory_vm 代码,同样是在 thread.cpp 文件下。

bool Threads::destroy_vm {
 JavaThread* thread = JavaThread::current;#ifdef ASSERT
 _vm_complete = false;#endif
 /**
 * 等待自己是最后一个非守护线程条件
 */
 // Wait until we are the last non-daemon thread to execute
 { MonitorLocker nu; while    1) /**
 * 非守护线程数大于 1,则一直等待
 */
 // This wait should make safepoint checks, wait without a timeout,
 // and wait as a suspend-equivalent condition.
 nu.wait;
 } /**
 * 下面代码是关闭 VM 的逻辑
 */
 EventShutdown e; if ) {
 e.set_reason;
 e.commit;
 }
 ...... 省略余下代码
}

我们这里看到当非守护线程数量大于 1 时,就一直等待,直到剩下一个非守护线程时,就会在线程执行完后,退出 JVM。这时候又有一个点需要定位,什么时候调用 destroy_vm 方法呢?还是通过查看代码以及注释,发现是在 main 方法执行完成后触发的。

在 java.c 文件的 JavaMain 方法里面,最后执行完调用了 LEAVE 方法,该方法调用了 - DestroyJavaVM;来触发 JVM 退出,最终调用 destroy_vm 方法。

#define LEAVE \
 do { \ if - DetachCurrentThread != JNI_OK) { \
 JLI_ReportErrorMessage; \
 ret = 1; \
 } \ if  { \
 - DestroyJavaVM; \ return ret; \
 } \
 } while 

所以我们也知道了,为啥 main 线程可以比子线程先退出?虽然 main 线程退出前调用了 destroy_vm 方法,但是在 destroy_vm 方法里面等待着非守护线程执行完,子线程如果是非守护线程,则 JVM 会一直等待,不会立即退出。

我们对这个点总结一下:Java 程序在 main 线程执行退出时,会触发执行 JVM 退出操作,但是 JVM 退出方法 destroy_vm会等待所有非守护线程都执行完,里面是用变量 number_of_non_daemon_threads 统计非守护线程的数量,这个变量在新增线程和删除线程时会做增减操作。

另外衍生一点就是:当 JVM 退出时,所有还存在的守护线程会被抛弃,既不会执行 finally 部分代码,也不会执行 stack unwound 操作。这个很明显,JVM 都退出了,守护线程自然退出了,当然这是守护线程的一个特性。

这个比较好理解,就是线程是用户线程还是守护线程,在线程还未启动时就得确定。在调用 start 方法之前,还只是个对象,没有映射到 JVM 中的线程,这个时候可以修改 daemon 属性,调用 start 方法之后,JVM 中就有一个线程映射这个线程对象,所以不能做修改了。

这个咱就不用写代码来验证了,直接看 Thread 源代码构造方法里面就可以知道,代码如下所示。郑州治疗不孕不育哪里好:http://mobile.xbzztj.com/

private Thread {
 ...省略一堆代码 this.daemon = parent.isDaemon;
 ...省略一堆代码
}

看到很多书籍和资料都这么说,我也很怀疑。所以写了下面代码来测试是不是守护线程优先级比用户线程低?

public class TestDaemon { static AtomicLong daemonTimes = new AtomicLong; static AtomicLong userTimes = new AtomicLong; public static void main { int count = 2000;
 List MyThread  threads = new ArrayList ; for  {
 MyThread userThread = new MyThread;
 userThread.setDaemon;
 threads.add;
 MyThread daemonThread = new MyThread;
 daemonThread.setDaemon;
 threads.add;
 } for  {
 threads.get.start;
 } try {
 Thread.sleep;
 } catch  {
 e.printStackTrace;
 }
 System.out.println);
 System.out.println);
 System.out.println - userTimes.get) + "ms");
 } static class MyThread extends Thread {
 @Override public void run { if ) {
 daemonTimes.getAndAdd);
 } else {
 userTimes.getAndAdd);
 }
 }
 }
}

运行结果如下。郑州女性不孕不育医院:http://wapjbk.39.net/yiyuanzaixian/zztjyy/

结果1:
daemon 统计:1570785465411405
user 统计:1570785465411570
daemon 和 user 相差时间:-165ms
daemon 统计:1570786615081403
user 统计:1570786615081398
daemon 和 user 相差时间:5ms

是不是很惊讶,居然相差无几,但是这个案例我也不能下定义说:守护线程和用户线程优先级是一样的。看了 JVM 代码也没找到守护线程优先级比用户线程低,这个点还是保持怀疑,有了解的朋友可以留言说一些,互相交流学习。

总结一下这篇文章讲解的点,一个是线程被分为 2 种类型,一种是用户线程,另一种是守护线程;如果要把线程设置为守护线程,需要在线程调用start方法前设置 daemon 属性;还有从 JVM 源码角度分析为什么当用户线程都执行完的时候,JVM 会自动退出。接着讲解了守护线程有继承性,父线程是守护线程,那么子线程默认就是守护线程;另外对一些书籍和资料所说的 守护线程优先级比用户线程低 提出自己的疑问,并希望有了解的朋友能帮忙解答。http://www.chacha8.cn/detail/1132398214.html


【某某业务】网站建设、网站设计、服务器空间租售、网站维护、网站托管、网站优化、百度推广、自媒体营销、微信公众号
如有意向---联系我们
热门栏目
热门资讯

网站建设 网站托管 成功案例 新闻动态 关于我们 联系我们 服务器空间 加盟合作 网站优化

备案号: 

公司地址:江苏省南京市玄武区玄武湖 咨询QQ:9490489 手机: 电话: