JAVA效能優化思路探究

java架構codi發表於2019-04-10

1、背景介紹


一個系統的上線除了常規的功能性測試外,還需要經過嚴格的效能測試,滿足預期的效能指標(常見的有響應時間,tps等),才允許上生產環境。廣義的效能測試一般還包含負載測試(用於測試系統的容量:即系統在保證一定響應時間的情況下能夠允許多少併發使用者的訪問),壓力測試(用於測試系統的穩定性:即在保證一定壓力的情況下,檢視測試系統的穩定性),併發測試(即測試系統多併發能力:即模擬多使用者訪問同一應用的測試,用於發現併發問題,比如執行緒鎖,資源競爭,資料庫死鎖等)等。

通過效能測試,可以幫助我們儘快發現系統的瓶頸。如果發現未能滿足預期的業務目標,則需要進行效能調優。效能調優的需求,有時候來自於原型的驗證,有時候來自於生產上實際的問題,不管哪一類的效能調優,我們一般按照效能監控,效能分析,效能優化這幾個步驟進行。以下章節會對每個步驟進行詳細分析。

2、效能監控

效能監控是效能調優的第一步,主要目的在於瞭解當前系統執行的狀態,瞭解當前伺服器資源使用情況,JVM的記憶體使用,執行緒使用等情況,以便於第一時間找到瓶頸點。

2.1 檢視伺服器配置

為了更好評估伺服器效能,首先應瞭解當前宿主伺服器的配置情況。以下主要是針對linux伺服器給出的常見的檢視命令。

2.1.1 CPU配置

對於CPU,比較關心的是CPU的總邏輯核數,可以直接使用mpstat檢視。

可以使用cat /proc/cpuinfo檢視CPU的型號:

2.1.2 記憶體配置

使用free命令進行檢視,可以看到總的記憶體,以及使用的情況

2.1.3 磁碟配置

使用fdisk -l可以檢視到所有的磁碟配置情況,使用df -TH可以看到當前磁碟的目錄掛載情況

有時候,需要確認當前磁碟是否為SSD盤,判斷cat /sys/block/*/queue/rotational的返回值(其中*為你的硬碟裝置名稱,例如sda等等),如果返回1則表示磁碟可旋轉,那麼就是HDD了;反之,如果返回0,則表示磁碟不可以旋轉,那麼就有可能是SSD了。如下圖所示,sda是SSD盤。

2.1.4 網路配置

使用ifconfig命令,可以看到網路卡的配置情況。有時候,需要檢視當前網路卡是千M還是萬M,可以通過ethtool檢視speed可以判斷。如下圖所示,eth4是千M網路卡。

2.2 伺服器監控

為了能實時瞭解系統執行時,資源的佔用情況,我們就需要對伺服器的系統資源進行監控,以下列出常見的命令以及常用的監控事項。

2.2.1 CPU監控

使用vmstat命令,vmstat 2代表每2秒統計一次

重點觀察

Ø Procs中r值,它代表排程程式執行佇列的長度,如果該值長時間大於CPU邏輯核數1倍以上,需要關注,超過3-4倍需要馬上採取行動

Ø System中in(中斷),cs(上下文切換)如果兩值較大,說明系統核心消耗CPU較多

Ø Cpu列中,如果us(使用者態)佔比長期大於50%時,就需要考慮優化演算法。根據經驗us+sy佔比參考值為80%

可以使用pidstat -w -I -p pid 2,監控應用的鎖競爭情況

讓步式上下文切換(cswch)時鐘週期佔用3% ~ 5%,說明Java應用面臨鎖競爭,搶佔式上下文切換率(nvcswch)高,說明預備執行的執行緒數多於可用的虛擬處理器數。

2.2.2 記憶體監控

也可以使用上述的vmstat檢視記憶體頁面交換,

重點觀察free,si,so這幾列,如果free變小,而且si,so在變化,說明存在記憶體不足,跟磁碟swap,有發生頁面交換的情況,需要考慮加大記憶體。

2.2.3 網路監控

使用第三方軟體iptraf,它提供了視覺化的頁面,通過它可以實時監控網路流量情況。

2.2.4 磁碟

使用iostat進行監控

cpu屬性值說明:

Ø 如果%iowait的值過高,表示硬碟存在I/O瓶頸,%idle值高,表示CPU較空閒,如果%idle值高但系統響應慢時,有可能是CPU等待分配記憶體,此時應加大記憶體容量。%idle值如果持續低於10,那麼系統的CPU處理能力相對較低,表明系統中最需要解決的資源是CPU。

disk屬性值說明:

Ø 如果%util接近100%,說明產生的I/O請求太多,I/O系統已經滿負荷,該磁碟可能存在瓶頸。如果svctm比較接近await,說明I/O幾乎沒有等待時間;如果await遠大於svctm,說明I/O佇列太長,io響應太慢,則需要進行必要優化。如果avgqu-sz比較大,也表示有當量io在等待。

2.3 JVM監控

使用jdk中自帶的jvisualvm工具,在要連線的遠端java程式,啟動時增加jmx的配置,如下:

這樣jvisualvm就可以通過ip+1111埠偵聽遠端JVM的情況了。

2.4 連線池監控

2.4.1 檢視資料庫連線池數量

使用netstat –an | grep ‘db ip’ | wc –l命令,可以看到與資料庫建立的連線池,看這個值跟設定的資料庫連線池的最小值,以及最大值的關係。如果始終通過最大值,需要考慮調整連線的最大值。

2.4.2 檢視工作執行緒數

方法1:使用jvisualvm工具遠端監控來檢視

方法2:使用命令檢視

2.5 Oracle監控

2.5.1 檢視oracle配置

Ø 使用oracle的使用者登入oracle的伺服器(su - oracle)

Ø 啟動sqlplus命令列模式(sqlplus / as sysdba)

Ø 檢視配置(Show parameter sga;)

2.5.2 效能監控

Ø 使用sqlplus命令列模式

Ø 開始時啟動快照命令,停止時再執行一遍快速命令

備註:快照命令(exec DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT();)

Ø 快照執行完後,取報告(@?/rdbms/admin/awrrpt)

Ø 分析報告(重點關注top 5 time events)

3 效能分析

3.1 JVM分析

3.1.1 堆分析

為了不影響線上的效能,可以使用堆轉儲,命令如下:

jmap -dump:live,format=b,file=heap_dump.hprofpid

然後可以將生成的.hprof檔案匯入mat,或者jvisualvm進行分析,可以瞭解哪些物件正在消耗記憶體。同時對於識別由建立太多某一特定物件所引發的記憶體問題,軟體提供的直方圖方法快速且方便。

3.1.2 垃圾回收分析

Jvm啟動時,可以設定-Xloggc,-XX:PrintGCDetails等引數,開啟gc日誌收集。也可以使用jstat進行監控分析,比如jstat–gcutil pid 2用於每隔2秒列印當前Java堆及GC情況。

3.1.3 執行緒分析

使用jdk自帶的JMC和jstack工具,可以檢視堵塞的執行緒。JMC內部整合的JFR可以很方便的檢索出引發執行緒堵塞的事件。而jstack在一定程度上可以檢查執行緒是堵塞在什麼資源上。以下給出jstack定位思路:

4 效能優化

在深度優化系統前,應該先弄清為何CPU的使用率低。優化程式碼的目的是提升而不是降低更短時間內的CPU使用率。

4.1 JVM啟動引數優化

4.1.1 原生記憶體的優化

對原生記憶體的優化,包含使用壓縮的OOP(jvm啟動引數上增加-XX:+UseCompressedOops)以及調整大記憶體分頁(同時修改linux配置以及jvm啟動引數-XX:LargePageSizeInBytes)等,都可以提升效能。

4.1.2 垃圾回收機制的優化

Ø 合理設定堆的大小,以及合理設定好代空間的劃分:設定太小容易頻繁GC,而設定太大,GC時停頓時間太長。同時為了避免可能使用到虛擬記憶體,記憶體頁交換導致更慢,至少保留1G的實體記憶體。

Ø 如何選擇各分割槽大小應該依賴應用程式中物件生命週期的分佈情況:如果應用存在大量的短期物件,應該選擇較大的年輕代;如果存在相對較多的持久物件,老年代應該適當增大。

Ø 穩定與震盪的堆大小:將-Xms和-Xmx的大小一致,對垃圾回收有利。

4.1.3 大物件分配優化

Ø 大物件儘量分配在TLAB,如果大量發生在TLAB外,需要考慮調整TLAB引數,或者減少分配物件的大小。可以通過-XX:PrintTLAB標誌檢視結果。

Ø 大物件劃入老年代:將大物件直接分配到老年代,保持新生代物件的結構的完整性,以提高GC效率,以通過-XX:PretenureSizeThreshold設定進入老年代的閥值。

4.2 java程式設計優化

因為實際的程式設計中,涉及效能優化的點比較多,以下只是列舉一些常見的優化項供參考。

4.2.1 執行緒池優化

Ø 根據當前伺服器CPU的數量合理設定最大執行緒數,最小執行緒數,執行緒池任務佇列大小。CPU密集型任務配置儘可能小的執行緒,如配置Ncpu+1個執行緒的執行緒池。IO密集型任務則由於執行緒並不是一直在執行任務,則配置儘可能多的執行緒,如2*Ncpu。

Ø 建議使用有界佇列,有界佇列能增加系統的穩定性和預警能力。

Ø 優先順序不同的任務可以使用優先順序佇列PriorityBlockingQueue來處理。

Ø 執行時間不同的任務可以交給不同規模的執行緒池來處理,或者也可以使用優先順序佇列,讓執行時間短的任務先執行。設定執行緒的優先順序。

4.2.2 其它程式設計細節

Ø 儘量減少記憶體的使用,減少物件的大小,設定型別時考慮最小原則,去掉不用的屬性,以及沒有用到的例項變數。

Ø 通過使用物件池以及執行緒區域性變數的方式來加強物件的重複。物件重用跟GC是有點矛盾,所以主要考慮物件初始化時成本比較高的情況(即初始化時間比較長)。

Ø 對於儲存實際不需要在多個執行緒間共享的同步物件,同時又在不同的實際中進行傳遞的物件,可以考慮使用執行緒區域性變數,減少同步競爭。

Ø 在一些場景下,優化使用java8並行流的模式

4.3 資料庫優化

4.3.1 使用預編譯

使用preparedStatement方式,重用預處理語句池可以極大提升效能,同時也要避免出現大量大型物件池化而引起的GC方面的問題。

4.3.2 使用連線池

引入hikari連線池,在啟動時就配置好

Ø 建立連線的代價很大,通過JDBC連線池獲取連線可省去建立連線時間。同時需要合理設定連線池的大小。

Ø 合理設值:比如設定檢索時的批量值,設定最優的預取值,設定ResultSet的批量值,可以提高檢索的效能。

Ø 事務的優化:事務的提交以及事務相關的鎖機制都會影響系統的效能,需要考慮合理設定事務隔離的級別,以及批量提交的策略等。

5 效能實戰經驗彙總

5.1 清算併發效能上不去

5.1.1 問題的現象

使用java8的並行流計算時,發現併發的效能上不去,並且效能會隨著時間推移,不斷的下降

5.1.2 優化點

Ø 引入hikari連線池,將單筆延時降到5ms

Ø 關閉日誌

Ø 將sql改成預編譯的模式

Ø Oracle伺服器的把oracle的記憶體提高

5.2 Hsiar跟中臺的連線上,存在很多FIN_WAIT2連線

5.3 Server_name是否可以隨便配置

6 小結

6.1 效能工具箱

6.1.1 壓測工具jmeter

Jmter是開源的壓測工具,也易於上手。它的使用就不介紹了,這裡主要講一些注意的事項:

Ø 它的實時繪圖依賴於伺服器端的響應,如果壓測機與伺服器時間不同步的話,會出現展示圖斷層現象,為了得到更準確的效能曲線,建議使用命令列的方式。

Ø 有時候發現壓測效能上不去,有可能的原因在於客戶端。主要考慮的因素:客戶端的CPU不足以支援所需數量的客戶端執行緒,或者客戶端需要花大量時間處理響應後才能傳送請求。

6.1.2 JVM相關

除了上文提到的jdk自帶的工具外,還有IBM提供的MemoryAnalyzer,以及商用軟體jprofile都很強大。

6.1.3 資料庫相關

對於資料庫的效能監控,可以使用Spotlight,它有基於mysql以及oracle的不同版本支援。

6.1.4 網路相關

常用Linux自帶的Tcpdump命令匯出抓包,然後使用wireshark進行分析,很強大。




相關文章