1. 程式人生 > >美女程式媛發福利,讀懂ANR的trace檔案So easy

美女程式媛發福利,讀懂ANR的trace檔案So easy

一、java執行緒的狀態轉換介紹


1.1新建狀態(New)

用new語句建立的執行緒處於新建狀態,此時它和其他Java物件一樣,僅僅在堆區中被分配了記憶體。

1.2就緒狀態(Runnable)

當一個執行緒物件建立後,其他執行緒呼叫它的start()方法,該執行緒就進入就緒狀態,Java虛擬機器會為它建立方法呼叫棧和程式計數器。處於這個狀態的執行緒位於可執行池中,等待獲得CPU的使用權。

1.3執行狀態(Running)

處於這個狀態的執行緒佔用CPU,執行程式程式碼。只有處於就緒狀態的執行緒才有機會轉到執行狀態。

1.4阻塞狀態(Blocked)

阻塞狀態是指執行緒因為某些原因放棄CPU,暫時停止執行。當執行緒處於阻塞狀態時,Java虛擬機器不會給執行緒分配CPU。直到執行緒重新進入就緒狀態,它才有機會轉到執行狀態。

阻塞狀態可分為以下3種:

1)位於物件等待池中的阻塞狀態(Blocked in object’s wait pool):當執行緒處於執行狀態時,如果執行了某個物件的wait()方法,Java虛擬機器就會把執行緒放到這個物件的等待池中,這涉及到“執行緒通訊”的內容。

2)位於物件鎖池中的阻塞狀態(Blocked in object’s lock pool):當執行緒處於執行狀態時,試圖獲得某個物件的同步鎖時,如果該物件的同步鎖已經被其他執行緒佔用,Java虛擬機器就會把這個執行緒放到這個物件的鎖池中,這涉及到“執行緒同步”的內容。

3)其他阻塞狀態(Otherwise Blocked):當前執行緒執行了sleep()方法,或者呼叫了其他執行緒的join()方法,或者發出了I/O請求時,就會進入這個狀態。

1.5死亡狀態(Dead)

當執行緒退出run()方法時,就進入死亡狀態,該執行緒結束生命週期。

二、Thread Dump分析

2.1首先介紹一下Thread Dump資訊的各個部分

執行緒info資訊塊:

1. "Timer-0" daemon prio=10tid=0xac190c00 nid=0xaef in Object.wait() [0xae77d000]

2. java.lang.Thread.State: TIMED_WAITING (on object monitor)

3. atjava.lang.Object.wait(Native Method)

4. -waiting on <0xb3885f60> (a java.util.TaskQueue) ###繼續wait

5. atjava.util.TimerThread.mainLoop(Timer.java:509)

6. -locked <0xb3885f60> (a java.util.TaskQueue) ###已經locked

7. atjava.util.TimerThread.run(Timer.java:462)

* 執行緒名稱:Timer-0

* 執行緒型別:daemon

* 優先順序:10,預設是5

* jvm執行緒id:tid=0xac190c00,jvm內部執行緒的唯一標識(一般通過java.lang.Thread.getId()獲取,通常用自增方式實現。)

* 對應系統執行緒id(Native Thread ID):nid=0xaef,和top命令檢視的執行緒pid對應,不過一個是10進位制,一個是16進位制。(可以通過命令:top -H -p pid,檢視該程序的所有執行緒資訊)

* 執行緒狀態:in Object.wait().

* 起始棧地址:[0xae77d000]

* Java thread statck trace:上面2~7行的資訊。到目前為止這是最重要的資料,Java stack trace提供了大部分資訊來精確定位問題根源。

對於thread dump資訊,主要關注的是執行緒的狀態和其執行堆疊。現在針對這兩個重點部分進行講解.

1、Java thread statck trace詳解

堆疊資訊應該逆向解讀:程式先執行的是第7行,然後是第6行,依次類推。

- locked <0xb3885f60> (a java.util.ArrayList)

- waiting on <0xb3885f60> (a java.util.ArrayList)

也就是說物件先上鎖,鎖住物件0xb3885f60,然後釋放該物件鎖,進入waiting狀態。

為啥會出現這樣的情況呢?看看下面的java程式碼示例,就會明白:

synchronized(obj) {

.........

obj.wait();

.........

} 

在堆疊的第一行資訊中,進一步標明瞭執行緒在程式碼級的狀態,例如:

java.lang.Thread.State: TIMED_WAITING (parking)

解釋如下:

|blocked|

This thread tried to enter asynchronized block, but the lock was taken by another thread. This thread isblocked until the lock gets released.

|blocked (on thin lock)|

This is the same state asblocked, but the lock in question is a thin lock.

|waiting|

This thread calledObject.wait() on an object. The thread will remain there until some otherthread sends a notification to that object.

|sleeping|

This thread calledjava.lang.Thread.sleep().

|parked|

This thread calledjava.util.concurrent.locks.LockSupport.park().

|suspended|

The thread's execution wassuspended by java.lang.Thread.suspend() or a JVMTI agent call.

2、執行緒狀態詳解

Runnable

_The thread is either running or ready to run when it gets its CPU turn._

Wait on condition

_The thread is either sleeping or waiting to be notified by another thread._

該狀態出現線上程等待某個條件的發生或者sleep。具體是什麼原因,可以結合 stacktrace來分析。

最常見的情況是執行緒在等待網路的讀寫,比如當網路資料沒有準備好讀時,執行緒處於這種等待狀態,而一旦有資料準備好讀之後,執行緒會重新啟用,讀取並處理資料。在Java引入New IO之前,對於每個網路連線,都有一個對應的執行緒來處理網路的讀寫操作,即使沒有可讀寫的資料,執行緒仍然阻塞在讀寫操作上,這樣有可能造成資源浪費,而且給作業系統的執行緒排程也帶來壓力。在New IO裡採用了新的機制,編寫的伺服器程式的效能和可擴充套件性都得到提高。

如果發現有大量的執行緒都處在Wait on condition,從執行緒 stack看, 正等待網路讀寫,這可能是一個網路瓶頸的徵兆。因為網路阻塞導致執行緒無法執行。

一種情況是網路非常忙,幾乎消耗了所有的頻寬,仍然有大量資料等待網路讀寫;另一種情況也可能是網路空閒,但由於路由等問題,導致包無法正常的到達。

所以要結合系統的一些效能觀察工具來綜合分析,比如netstat統計單位時間的傳送包的數目,看是否很明顯超過了所在網路頻寬的限制;觀察CPU的利用率,看系統態的CPU時間是否明顯大於使用者態的CPU時間;這些都指向由於網路頻寬所限導致的網路瓶頸。另外一種出現 Wait on condition的常見情況是該執行緒在 sleep,等待 sleep的時間到了,將被喚醒。

Waiting for Monitor Entry and in Object.wait()

_The thread is waiting to getthe lock for an object (some other thread may be holding the lock). Thishappens if two or more threads try to execute synchronized code. Note that thelock is always for an object and not for individual methods._

在多執行緒的 JAVA程式中,實現執行緒之間的同步,就要說說Monitor。

Monitor是Java中用以實現執行緒之間的互斥與協作的主要手段,它可以看成是物件或者 Class的鎖。每一個物件都有,也僅有一個monitor。每個Monitor在某個時刻,只能被一個執行緒擁有,該執行緒就是 “ActiveThread”,而其它執行緒都是“Waiting Thread”,分別在兩個佇列“ Entry Set”和“Wait Set”裡面等候。在“Entry Set”中等待的執行緒狀態是“Waiting for monitorentry”,而在“Wait Set”中等待的執行緒狀態是“in Object.wait()”。

先看 “Entry Set”裡面的執行緒。

我們稱被synchronized保護起來的程式碼段為臨界區。當一個執行緒申請進入臨界區時,它就進入了 “Entry Set”佇列。對應的 code就像:

synchronized(obj) {

.........

}

這時有兩種可能性:

1)該 monitor不被其它執行緒擁有, Entry Set裡面也沒有其它等待執行緒。本執行緒即成為相應類或者物件的 Monitor的 Owner,執行臨界區的程式碼。

2)該 monitor被其它執行緒擁有,本執行緒在 Entry Set佇列中等待。

在第一種情況下,執行緒將處於 “Runnable”的狀態,而第二種情況下,執行緒 DUMP會顯示處於 “waiting for monitor entry”。

臨界區的設定,是為了保證其內部的程式碼執行的原子性和完整性。但是因為臨界區在任何時間只允許執行緒序列通過,這和我們多執行緒的程式的初衷是相反的。如果在多執行緒的程式中,大量使用 synchronized,或者不適當的使用了它,會造成大量執行緒在臨界區的入口等待,造成系統的效能大幅下降。如果線上程 DUMP中發現了這個情況,應該審查原始碼,改程序序。

再看“Wait Set”裡面的執行緒。當執行緒獲得了Monitor,進入了臨界區之後,如果發現執行緒繼續執行的條件沒有滿足,它則呼叫物件(一般就是被synchronized的物件)的wait() 方法,放棄Monitor,進入“Wait Set”佇列。只有當別的執行緒在該物件上呼叫了notify() 或者notifyAll(),“Wait Set”佇列中執行緒才得到機會去競爭,但是隻有一個執行緒獲得物件的Monitor,恢復到執行態。在“Wait Set”中的執行緒, DUMP中表現為:in Object.wait()。

一般,CPU很忙時,則關注runnable的執行緒,CPU很閒時,則關注waiting for monitor entry的執行緒。

看到這裡,不知道各位同學對於trace檔案是否有了新的認識。如果各位同學還有什麼困惑,可以關注騰訊Bugly的微信官方帳號,與小編進行線上交流,我們在那裡等待各位的迴音哦~