4 如何通過JDK命令,分析排查死鎖、死迴圈?
top 命令:顯示當前的活動程式,預設它是按消耗 CPU 的厲害程度進行排序
,每5秒鐘重新整理一次列表,你也可以選擇不同的排序方式,例如 m 是按記憶體佔用方式進行排序的快捷鍵。
命令:top -Hp pid
可以實時的跟蹤並獲取指定程式中最耗cpu的執行緒
。再用 jstack方法提取到對應的執行緒堆疊資訊
。
jps 命令:
jps用來檢視JVM裡面所有程式的具體狀態, 包括程式ID,程式啟動的路徑等等。
jstack 命令:
- 檢視java程式崩潰生成core檔案,獲得core檔案的java stack和native stack的資訊;
- 檢視正在執行的java程式的java stack和native stack的資訊:a) 檢視執行的java程式呈現hung的狀態;b) 跟蹤Java的呼叫棧,剖析程式。
執行緒的狀態分析:
Runnable:該狀態
表示執行緒具備所有執行條件,在執行佇列中準備作業系統的排程,或者正在執行
。Wait on condition:該狀態出現線上程等待某個條件的發生。具體是什麼原因,可以結合 stacktrace來分析。
最常見的情況是執行緒在等待網路的讀寫
;另外一種出現 Wait on condition的常見情況是該執行緒在 sleep,等待 sleep的時間到了時候,將被喚醒
。Waiting for monitor entry 和 in Object.wait():在多執行緒的 JAVA程式中,實現執行緒之間的同步,就要說說 Monitor。
Monitor是Java中用以實現執行緒之間的互斥與協作的主要手段,它可以看成是物件或者 Class的鎖。每一個物件都有,也僅有一個 monitor
。
jinfo 命令:
jinfo可觀察執行中的java程式的執行環境引數:
引數包括Java System屬性和JVM命令列引數
;也可從core檔案裡面知道崩潰的Java應用程式的配置資訊。
jstat 命令:
jstat利用了JVM內建的指令
對Java應用程式的資源和效能進行實時的命令列的監控
,包括了對Heap size和垃圾回收狀況的監控等等。
jmap 命令:
觀察執行中的jvm實體記憶體的佔用情況
,包括Heap size, Perm size等等。
4.1 Java死迴圈分析
-
檢視程式ID:
top 或者 jps
-
按CPU使用率展示當前JAVA程式的所有執行緒:
top -Hp 3230
其實這個地方按CPU的使用率來判定還不太好理解,以執行時間來判定可能更能說明問題些。
-
將執行時間最長的
本地執行緒ID(3244)轉成16進製為0xcac
。 -
生成執行緒堆疊日誌檔案
jstack -l 3230 > jstack.log
; -
開啟堆疊日誌
搜尋“0xcac”
: -
很容易的就找到了無限迴圈的呼叫執行緒堆疊。
4.2 Java死鎖分析
在多執行緒程式的編寫中,如果不適當的運用同步機制,則有可能造成程式的死鎖,經常表現為程式的停頓,或者不再響應使用者的請求。 比如在下面這個示例中,是個較為典型的死鎖情況:
5 Java中j.u.c包原理實現及CAS演算法?
5.1 Java的多執行緒同步機制
在現代的多處理器系統中,提高程式的並行執行能力是 有效利用 CPU 資源的關鍵
。為了有效協調多執行緒間的併發訪問,必須採用適當的同步機制來協調競爭
。當前常用的多執行緒同步機制可以分為下面三種型別:
volatile 變數:輕量級多執行緒同步機制,不會引起上下文切換和執行緒排程。僅提供記憶體可見性保證,不提供原子性。
CAS 原子指令:輕量級多執行緒同步機制,不會引起上下文切換和執行緒排程。它同時提供記憶體可見性和原子化更新保證。
內部鎖和顯式鎖:重量級多執行緒同步機制,可能會引起上下文切換和執行緒排程,它同時提供記憶體可見性和原子性。
在這裡,CAS 指的是現代 CPU 廣泛支援的一種對記憶體中的共享資料進行操作的一種特殊指令。這個指令會對記憶體中的共享資料做原子的讀寫操作
。簡單介紹一下這個指令的操作過程:首先,CPU 會將記憶體中將要被更改的資料與期望的值做比較。然後,當這兩個值相等時,CPU 才會將記憶體中的數值替換為新的值。否則便不做操作。最後,CPU 會將舊的數值返回。這一系列的操作是原子的
。它們雖然看似複雜,但卻是 Java 5 併發機制優於原有鎖機制的根本。簡單來說,CAS 的含義是“我認為原有的值應該是什麼,如果是,則將原有的值更新為新值,否則不做修改,並告訴我原來的值是多少”。
在輕度到中度的爭用情況下,非阻塞演算法的效能會超越阻塞演算法,因為 CAS 的多數時間都在第一次嘗試時就成功
,而發生爭用時的開銷也不涉及執行緒掛起和上下文切換,只多了幾個迴圈迭代。沒有爭用的 CAS 要比沒有爭用的鎖便宜得多(這句話肯定是真的,因為沒有爭用的鎖涉及 CAS 加上額外的處理),而爭用的 CAS 比爭用的鎖獲取涉及更短的延遲。
5.2 Java中j.u.c包原理實現
Java的CAS會使用現代處理器上提供的高效機器級別原子指令,這些原子指令以原子方式對記憶體執行讀-改-寫操作
,這是在多處理器中實現同步的關鍵(從本質上來說,能夠支援原子性讀-改-寫指令的計算機器,是順序計算圖靈機的非同步等價機器,因此 任何現代的多處理器都會去支援某種能對記憶體執行原子性讀-改-寫操作的原子指令
)。同時,volatile變數的讀/寫和CAS可以實現執行緒之間的通訊
。把這些特性整合在一起,就形成了整個concurrent包得以實現的基石
。如果我們仔細分析concurrent包的原始碼實現,會發現一個通用化的實現模式:
- 首先,宣告共享變數為volatile;
- 然後,使用CAS的原子條件更新來實現執行緒之間的同步;
- 同時,配合以volatile的讀/寫和CAS所具有的volatile讀和寫的記憶體語義來實現執行緒之間的通訊。
AQS,非阻塞資料結構和原子變數類(java.util.concurrent.atomic包中的類),這些concurrent包中的基礎類都是使用這種模式來實現的,而concurrent包中的高層類又是依賴於這些基礎類來實現的
。從整體來看,concurrent包的實現示意圖如下:
6 有鎖與無鎖的本質區別?
有鎖、無鎖的本質都是在解決併發情況下競態資源的執行緒安全問題。無鎖只是把“排他性”進一步的弱化,以提高併發量,最大限度的使用CPU。
無鎖只會在CPU控制權切換的等待,而有鎖會在“鎖釋放”、“CPU控制權切換”兩種情況下的等待。
最終無鎖情況下,CPU使用率上不去,吞吐量下降,就受系統資源限制了
。即使執行緒量增加,無非增加的是執行緒CPU控制權的切換成本,統一受CPU的控制排程,出現的也只能是等待了。
7 volatile關鍵字是否是執行緒安全的?
volatile關鍵字僅保證兩點:
- volatile變數執行緒之間可見性;
- 禁止針對volatile變數操作指令重排序;
但最重要一點沒有保證,關係到執行緒安全,就是Volatile變數操作指令的不保證原子性問題。
什麼是原子操作:
多個執行緒執行一個操作時,其中任何一個執行緒要麼完全執行完此操作的步驟,要麼就沒有執行此操作的任何步驟,那麼認為這個操作才是原子的。
關於指令重排序的問題,在單執行緒中,重排序的前後只要不影響JVM執行結果,JVM可以執行指令重排序
。
指令重排序的意義在於:
JVM能夠根據處理器特性(CPU多級快取系統、多核處理器等)適當的重新排序機器指令,使機器指令更符合CPU的執行特點,最大限度的發揮機器效能。
但是 在指令重排序在多執行緒的情況下,重排序的前後結果,可能不會一致了
。這裡導致結果不一致主要是執行緒安全的問題,即使指令不重排序,也會存線上程安全問題。這也就是原子性問題、或者沒有加鎖的問題
;