本文已在黃金檔上發表,原文連結:http://www.goldendoc.org/2011/11/jvm-thread/
兩個問題
- 什麼是守護線程?守護線程與非守護線程有什麼差別?其應用場景有哪些?
- 一個簡單的Java程式,啟動後JVM建立了哪些線程,它們的作用是什麼?
熟悉上面兩個問題的同學可以繞過了,不太熟的同學可以繼續往下看,哈哈!
守護線程
守護線程,又叫Daemon線程,它有以下幾個特點:
- 守護線程通常由虛拟機自己使用,比如垃圾收集器的線程;
- Java程式可以把它任何建立的線程标記為守護線程;但必須線上程運作前設定;
- Java初始線程(即開始于main方法的線程)是非守護線程;
- 隻要還有任何非守護線程在運作,那麼這個Java程式也在運作,即這個JVM執行個體還存活着;當JVM中的所有非守護線程都終止時,JVM執行個體将自動退出;
看下面這個例子:
1 | public class DaemonTest { |
3 | public static void main(String[] args) throws InterruptedException, |
5 | Thread daemon = new Thread( new DaemonThread()); |
6 | daemon.setName( "My Daemon Thread" ); |
7 | daemon.setDaemon( true ); |
10 | for ( int i = ; i < 10 ; i++) { |
11 | System.out.println( "Main thread: " + i); |
17 | class DaemonThread implements Runnable { |
22 | while (index < 100 ) { |
23 | System.out.println( "========= Daemon thread: " + index++); |
26 | } catch (InterruptedException e) { |
主線程在建立一個Daemon線程後,循環10次結束;此時JVM中沒有任何非Daemon線程,是以JVM将自動退出,其結果是導緻建立的Daemon線程沒有執行完既定任務就被迫終止了;程式結果如下:
2 | ========= Daemon thread: |
3 | ========= Daemon thread: 1 |
8 | ========= Daemon thread: 10 |
結論:不要将業務邏輯封裝在Daemon線程中執行,否則結果會不穩定;
JVM中的線程
上面例子中,虛拟機在啟動後,一共建立了多少個線程呢?不用猜,我們通過程式或工具來看;
1、通過程式
代碼如下:
1 | public class JVMThreadTest { |
3 | public static void main(String[] args) { |
4 | Thread[] ts = getAllThread(); |
6 | System.out.println(t.getId() + ": " + t.getName() + ", Priority: " |
11 | public static Thread[] getAllThread() { |
12 | ThreadGroup root = Thread.currentThread().getThreadGroup(); |
13 | ThreadGroup ttg = root; |
15 | while ((ttg = ttg.getParent()) != null ) { |
19 | Thread[] tlist = new Thread[( int ) (root.activeCount() * 1.2 )]; |
20 | return java.util.Arrays.copyOf(tlist, root.enumerate(tlist, true )); |
上面的代碼先取得所有的線程,然後依次列印其線程Id、線程名和優先級,結果如下:
1 | 2 : Reference Handler, Priority: 10 |
2 | 3 : Finalizer, Priority: 8 |
3 | 4 : Signal Dispatcher, Priority: 9 |
4 | 5 : Attach Listener, Priority: 5 |
2、通過jstack
通過jstack -l pid,可以得到如下棧資訊:
1 | "My Daemon Thread" daemon prio= 6 tid= 0x01e82400 nid= 0x1740 waiting on condition |
3 | java.lang.Thread.State: TIMED_WAITING (sleeping) |
4 | at java.lang.Thread.sleep(Native Method) |
5 | at inside.jvm.daemon.DaemonThread.run(DaemonTest.java: 34 ) |
6 | at java.lang.Thread.run(Unknown Source) |
8 | Locked ownable synchronizers: |
11 | "Low Memory Detector" daemon prio= 6 tid= 0x01e50400 nid= 0xc78 runnable [ 0x0000000 |
13 | java.lang.Thread.State: RUNNABLE |
15 | Locked ownable synchronizers: |
18 | "C1 CompilerThread0" daemon prio= 10 tid= 0x01e4dc00 nid= 0x410 waiting on conditio |
20 | java.lang.Thread.State: RUNNABLE |
22 | Locked ownable synchronizers: |
25 | "Attach Listener" daemon prio= 10 tid= 0x01e4a800 nid= 0x109c waiting on condition |
27 | java.lang.Thread.State: RUNNABLE |
29 | Locked ownable synchronizers: |
32 | "Signal Dispatcher" daemon prio= 10 tid= 0x01e47800 nid= 0x9b0 runnable [ 0x00000000 |
34 | java.lang.Thread.State: RUNNABLE |
36 | Locked ownable synchronizers: |
39 | "Finalizer" daemon prio= 8 tid= 0x01e40400 nid= 0x17d0 in Object.wait() [ 0x03f6f000 |
41 | java.lang.Thread.State: WAITING (on object monitor) |
42 | at java.lang.Object.wait(Native Method) |
43 | - waiting on < 0x23d51148 > (a java.lang.ref.ReferenceQueue$Lock) |
44 | at java.lang.ref.ReferenceQueue.remove(Unknown Source) |
45 | - locked < 0x23d51148 > (a java.lang.ref.ReferenceQueue$Lock) |
46 | at java.lang.ref.ReferenceQueue.remove(Unknown Source) |
47 | at java.lang.ref.Finalizer$FinalizerThread.run(Unknown Source) |
49 | Locked ownable synchronizers: |
52 | "Reference Handler" daemon prio= 10 tid= 0x01e3b800 nid= 0x1064 in Object.wait() [ |
54 | java.lang.Thread.State: WAITING (on object monitor) |
55 | at java.lang.Object.wait(Native Method) |
56 | - waiting on < 0x23d51048 > (a java.lang.ref.Reference$Lock) |
57 | at java.lang.Object.wait(Object.java: 485 ) |
58 | at java.lang.ref.Reference$ReferenceHandler.run(Unknown Source) |
59 | - locked < 0x23d51048 > (a java.lang.ref.Reference$Lock) |
61 | Locked ownable synchronizers: |
64 | "main" prio= 6 tid= 0x014f9400 nid= 0x1154 waiting on condition [ 0x001ff000 ] |
65 | java.lang.Thread.State: TIMED_WAITING (sleeping) |
66 | at java.lang.Thread.sleep(Native Method) |
67 | at inside.jvm.daemon.DaemonTest.main(DaemonTest.java: 21 ) |
69 | Locked ownable synchronizers: |
72 | "VM Thread" prio= 10 tid= 0x01dffc00 nid= 0xab8 runnable |
74 | "VM Periodic Task Thread" prio= 10 tid= 0x01e6f400 nid= 0xcd8 waiting on condition |
3、通過jconsole
通過JDK自帶的JConsole也可以檢視JVM中的線程,如下圖:
JVM線程知多少 各線程介紹
- main線程
這個不用多解釋了,主線程,JVM建立的第一個線程,屬于非daemon線程;從jstack的結果可以看出,main線程通過Thread.sleep()而進入TIMED_WAITING狀态;
Reference Handler線程
JVM在建立main線程後就建立Reference Handler線程,其優先級最高,為10,它主要用于處理引用對象本身(軟引用、弱引用、虛引用)的垃圾回收問題;關于引用,可以參考Java:對象的強、軟、弱、虛引用這篇blog;
Finalizer線程
這個線程也是在main線程之後建立的,其優先級為8,主要用于在垃圾收集前,調用對象的finalize()方法;關于Finalizer線程的幾點:
1) 隻有當開始一輪垃圾收集時,才會開始調用finalize()方法;是以并不是所有對象的finalize()方法都會被執行;
2) 該線程也是daemon線程,是以如果虛拟機中沒有其他非daemon線程,不管該線程有沒有執行完finalize()方法,JVM也會退出;
3) JVM在垃圾收集時會将失去引用的對象包裝成Finalizer對象(Reference的實作),并放入ReferenceQueue,由Finalizer線程來處理;最後将該Finalizer對象的引用置為null,由垃圾收集器來回收;
4) JVM為什麼要單獨用一個線程來執行finalize()方法呢?如果JVM的垃圾收集線程自己來做,很有可能由于在finalize()方法中誤操作導緻GC線程停止或不可控,這對GC線程來說是一種災難;
Signal Dispatcher線程
該線程用于處理作業系統發送給的JVM信号;關于JVM信号處理可以參考Revelations on Java signal handling and termination這篇blog;
My Daemon Thread線程
這個就是在main線程裡建立出來的daemon線程;
Attach Listener線程
這個線程暫時還查不到相關的資料,了解的同學請跟貼回複一下,謝謝!
Low Memory Detector線程
網上查資料,說這個線程是負責對可使用記憶體進行檢測,如果發現可用記憶體低,配置設定新的記憶體空間;我有兩個疑問:
1) 在哪裡配置設定新的記憶體?
2) 這個線程什麼時候被建立的?代碼在哪?
C1 CompilerThread0線程
用來調用JITing,實時編譯裝卸class;
VM Thread、VM Periodic Task Thread線程
關于上面三個線程,我同樣存在以上兩個疑問;
希望牛人能指點一二,謝謝!