深入理解Java虛擬機器——JVM效能優化
一、效能監控
當開發或執行一個Java應用的時候,對JVM的效能進行監控是很重要的。配置JVM不是一次配置就萬事大吉的,特別是你要應對的是Java伺服器應用的情況。你必須持續的檢查堆記憶體和非堆記憶體的分配和使用情況,執行緒數的建立情況和記憶體中載入的類的資料情況等。這些都是核心引數。
使用Anturis控制檯,你可以為任何的硬體元件上執行的JVM配置監控(例如,在一臺電腦上執行的一個Tomcat網頁伺服器)。
JVM監控可以使用以下衡量標準:
1、總記憶體使用情況(MB):即JVM使用的總記憶體。如果JVM使用了所有可用記憶體,這項指標可以衡量底層作業系統的整體效能。
2、堆記憶體使用(MB):即JVM為執行的Java應用所使用的物件分配的所有記憶體。不使用的物件通常會被垃圾回收器從堆中移除。所以,如果這個指數增大,表示你的應用沒有把不使用的物件移除或者你需要更好的配置垃圾回收器的引數。
3、非堆記憶體的使用(MB):即為方法區和程式碼快取分配的所有記憶體。方法區是用於儲存被載入的類的引用,如果這些引用沒有被適當的清理,永生代池會在每次應用被重新部署的時候都會增大,導致非堆的記憶體洩露。這個指標也可能指示了執行緒建立的洩露。
4、池內總記憶體(MB):即JVM所分配的所有變數記憶體池的記憶體和(即除了程式碼快取區外的所有記憶體和)。這個指標能夠讓你明確你的應用在JVM過載前所能使用的總記憶體。
5、執行緒:即所有有效執行緒數。舉個例子,在Tomcat伺服器中每個請求都是一個獨立的執行緒來處理,所以這個衡量指標可以表示當前有多少個請求數,是否影響到了後臺低許可權的執行緒的執行。
6、類:即所有被載入的類的總數。如果你的應用動態的建立很多類,這可能是記憶體洩露的一個原因。
二、效能優化
JVM是執行在一個獨立的程序中的,但它可以併發執行多個執行緒,每個執行緒都執行自己的方法,這是Java必備的一個部分。以即時訊息客戶端這樣一個應用為例,它至少執行兩個執行緒。一個執行緒用於等待使用者輸入,另一個檢查服務端是否有新的訊息傳輸。再以服務端應用為例,有時一個請求可能要涉及多個執行緒併發執行,所以需要多執行緒來處理請求。
在JVM的程序中,所有的執行緒共享記憶體和其他可用的資源。每一個JVM程序在進入點(main方法)處都要啟動一個主執行緒,其他執行緒都從主執行緒啟動,成為執行過程中的一個獨立部分。執行緒可以再不同的處理器上並行執行,同樣也可以共享一個處理器,執行緒排程器負責處理多個執行緒共享一個處理器的情況。
很多應用(特別是服務端應用)會處理很多工,需要並行執行。這些任務中有些是非常重要的,需要實時執行的。而另外一些是後臺任務,可以在CPU空閒時執行。任務是在不同的執行緒中執行的。舉例子來說,服務端可能有一些低優先順序的執行緒,它們會根據一些資料來計算統計資訊。同時也會啟動一些高優先順序的程序用於處理傳入的資料,響應對這些統計資訊的請求。這裡可能有很多的源資料,很多來自客戶端的資料請求,每個請求都會使服務端短暫的停止後臺計算的執行緒以響應這個請求。所以,你必須監控在執行的執行緒數目並且保證有足夠的CPU時間來執行必要的計算。
JVM的效能取決於其配置是否與應用的功能相匹配。儘管垃圾回收器和記憶體回收程序是自動管理記憶體的,但是你必須掌管它們的頻率。通常來說,你的應用可使用的記憶體越多,那麼這些會導致應用暫停的記憶體管理程序需要起作用的就越少。
如果垃圾回收發生的頻率比你想的要多很多,那麼可以在啟動JVM的時候為其配置更大的最大堆大小值。堆被填滿的時間越久,就越能降低垃圾回收發生的頻率。最大堆大小值可以在啟動JVM的時候,用-Xmx引數來設定。預設的最大堆大小是被設定為可用的作業系統記憶體的四分之一,或者最小1GB。
如果問題出在經常重新分配記憶體,那麼你可以把初始化堆大小設定為和最大堆大小一樣。這就意味著JVM永遠不需要為堆重新分配記憶體。但這樣做就會失去動態堆大小適配的優化,堆的大小從一開始就被固定下來。配置初始化對大小是在啟動JVM,用-Xms來設定。預設初始化堆大小會被設定為作業系統可用的實體記憶體的六十四分之一,或者設定一個最小值。這個值是根據不同的平臺來確定的。
如果你清楚是哪種垃圾回收(minor gc或major gc)導致了效能問題,可以在不改變整個堆大小的情況下設定新生代和老生代的大小比例。對於需要產生大量臨時物件的應用,需要增大新生代的比例(當然,後果是減小了老生代的大小)。對於長生命週期物件較多的應用,則需增大老生代的比例(自然需要減少新生代的大小)。
以下幾種方法可以用來設定新生代和老生代的大小:
1、在啟動JVM時,使用-XX:NewRatio引數來具體指定新生代和老生代的大小比例。比如,如果想讓老生代的大小是新生代的五倍,則設定引數為-XX:NewRatio=5,預設這個引數設定為2(即老生代佔用堆空間的三分之二,新生代佔用三分之一)。
2、在啟動JVM時,直接使用-Xmn引數設定初始化和最大新生代大小,那麼堆中的剩餘大小即是老生代的大小。
3、在啟動JVM時,直接使用-XX:NewSize和-XX:MaxNewSize引數設定初始化和最大新生代大小,那麼堆中的剩餘大小即是老生代的大小。
每一個執行緒都有一個棧,用於儲存函式呼叫、返回地址等等,這些棧有著對應的記憶體分配。如果執行緒過多,就會導致OutOfMemory錯誤。即使你有足夠的空間的堆來存放物件,你的應用也可能會因為建立一個新的執行緒而崩潰。這種情況下,需要考慮限制執行緒中的棧大小的最大值。執行緒棧大小可以在JVM啟動的時候,通過-Xss引數來設定,預設這個值被設定為320KB至1024KB之間,這和平臺相關。
內容參考《深入理解Java虛擬機器:JVM高階特性與最佳實踐》(第2版)周志明