天天看點

JVM線程知多少

本文已在黃金檔上發表,原文連結:http://www.goldendoc.org/2011/11/jvm-thread/

兩個問題

  1. 什麼是守護線程?守護線程與非守護線程有什麼差別?其應用場景有哪些?
  2. 一個簡單的Java程式,啟動後JVM建立了哪些線程,它們的作用是什麼?

熟悉上面兩個問題的同學可以繞過了,不太熟的同學可以繼續往下看,哈哈!

守護線程

守護線程,又叫Daemon線程,它有以下幾個特點:

  1. 守護線程通常由虛拟機自己使用,比如垃圾收集器的線程;
  2. Java程式可以把它任何建立的線程标記為守護線程;但必須線上程運作前設定;
  3. Java初始線程(即開始于main方法的線程)是非守護線程;
  4. 隻要還有任何非守護線程在運作,那麼這個Java程式也在運作,即這個JVM執行個體還存活着;當JVM中的所有非守護線程都終止時,JVM執行個體将自動退出;

看下面這個例子:

1

public

class

DaemonTest {

2

3

public

static

void

main(String[] args) 

throws

InterruptedException,

4

IOException {

5

Thread daemon = 

new

Thread(

new

DaemonThread());

6

daemon.setName(

"My Daemon Thread"

);

7

daemon.setDaemon(

true

);

8

daemon.start();

9

10

for

(

int

i = 

; i < 

10

; i++) {

11

System.out.println(

"Main thread: "

+ i);

12

Thread.sleep(

1000

);

13

}

14

}

15

}

16

17

class

DaemonThread 

implements

Runnable {

18

private

int

index = 

;

19

20

@Override

21

public

void

run() {

22

while

(index < 

100

) {

23

System.out.println(

"========= Daemon thread: "

+ index++);

24

try

{

25

Thread.sleep(

1000

);

26

catch

(InterruptedException e) {

27

e.printStackTrace();

28

}

29

}

30

}

31

}

主線程在建立一個Daemon線程後,循環10次結束;此時JVM中沒有任何非Daemon線程,是以JVM将自動退出,其結果是導緻建立的Daemon線程沒有執行完既定任務就被迫終止了;程式結果如下:

1

Main thread: 

2

========= Daemon thread: 

3

========= Daemon thread: 

1

4

Main thread: 

1

5

Main thread: 

2

6

......

7

Main thread: 

9

8

========= Daemon thread: 

10

結論:不要将業務邏輯封裝在Daemon線程中執行,否則結果會不穩定;

JVM中的線程

上面例子中,虛拟機在啟動後,一共建立了多少個線程呢?不用猜,我們通過程式或工具來看;

1、通過程式

代碼如下:

1

public

class

JVMThreadTest {

2

3

public

static

void

main(String[] args) {

4

Thread[] ts = getAllThread();

5

for

(Thread t : ts) {

6

System.out.println(t.getId() + 

": "

+ t.getName() + 

", Priority: "

7

+ t.getPriority());

8

}

9

}

10

11

public

static

Thread[] getAllThread() {

12

ThreadGroup root = Thread.currentThread().getThreadGroup();

13

ThreadGroup ttg = root;

14

15

while

((ttg = ttg.getParent()) != 

null

) {

16

root = ttg;

17

}

18

19

Thread[] tlist = 

new

Thread[(

int

) (root.activeCount() * 

1.2

)];

20

return

java.util.Arrays.copyOf(tlist, root.enumerate(tlist, 

true

));

21

}

22

}

上面的代碼先取得所有的線程,然後依次列印其線程Id、線程名和優先級,結果如下:

1

2

: Reference Handler, Priority: 

10

2

3

: Finalizer, Priority: 

8

3

4

: Signal Dispatcher, Priority: 

9

4

5

: Attach Listener, Priority: 

5

5

1

: main, Priority: 

5

2、通過jstack

通過jstack -l pid,可以得到如下棧資訊:

1

"My Daemon Thread"

daemon prio=

6

tid=

0x01e82400

nid=

0x1740

waiting on condition

2

[

0x0414f000

]

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)

7

8

Locked ownable synchronizers:

9

- None

10

11

"Low Memory Detector"

daemon prio=

6

tid=

0x01e50400

nid=

0xc78

runnable [

0x0000000

12

]

13

java.lang.Thread.State: RUNNABLE

14

15

Locked ownable synchronizers:

16

- None

17

18

"C1 CompilerThread0"

daemon prio=

10

tid=

0x01e4dc00

nid=

0x410

waiting on conditio

19

n [

0x00000000

]

20

java.lang.Thread.State: RUNNABLE

21

22

Locked ownable synchronizers:

23

- None

24

25

"Attach Listener"

daemon prio=

10

tid=

0x01e4a800

nid=

0x109c

waiting on condition

26

[

0x00000000

]

27

java.lang.Thread.State: RUNNABLE

28

29

Locked ownable synchronizers:

30

- None

31

32

"Signal Dispatcher"

daemon prio=

10

tid=

0x01e47800

nid=

0x9b0

runnable [

0x00000000

33

]

34

java.lang.Thread.State: RUNNABLE

35

36

Locked ownable synchronizers:

37

- None

38

39

"Finalizer"

daemon prio=

8

tid=

0x01e40400

nid=

0x17d0

in Object.wait() [

0x03f6f000

40

]

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)

48

49

Locked ownable synchronizers:

50

- None

51

52

"Reference Handler"

daemon prio=

10

tid=

0x01e3b800

nid=

0x1064

in Object.wait() [

53

x03f1f000]

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)

60

61

Locked ownable synchronizers:

62

- None

63

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

)

68

69

Locked ownable synchronizers:

70

- None

71

72

"VM Thread"

prio=

10

tid=

0x01dffc00

nid=

0xab8

runnable

73

74

"VM Periodic Task Thread"

prio=

10

tid=

0x01e6f400

nid=

0xcd8

waiting on condition

3、通過jconsole

通過JDK自帶的JConsole也可以檢視JVM中的線程,如下圖:

JVM線程知多少

各線程介紹

  1. 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線程

關于上面三個線程,我同樣存在以上兩個疑問;

希望牛人能指點一二,謝謝!

繼續閱讀