中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java性能如何調優

發布時間:2021-10-20 15:45:39 來源:億速云 閱讀:150 作者:iii 欄目:編程語言

這篇文章主要介紹“Java性能如何調優”,在日常操作中,相信很多人在Java性能如何調優問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java性能如何調優”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

  Java 應用性能的瓶頸點非常多,比如磁盤、內存、網絡 I/O 等系統因素,Java 應用代碼,JVM GC,數據庫,緩存等。筆者根據個人經驗,將 Java 性能優化分為 4 個層級:應用層、數據庫層、框架層、JVM 層。

Java 性能優化分層模型

Java性能如何調優

Java性能調優最強實踐 每層優化難度逐級增加,涉及的知識和解決的問題也會不同。比如應用層需要理解代碼邏輯,通過 Java 線程棧定位有問題代碼行等;數據庫層面需要分析 SQL、定位死鎖等;框架層需要懂源代碼,理解框架機制;JVM 層需要對 GC 的類型和工作機制有深入了解,對各種 JVM 參數作用了然于胸。

圍繞 Java 性能優化,有兩種最基本的分析方法:現場分析法和事后分析法。

現場分析法通過保留現場,再采用診斷工具分析定位。現場分析對線上影響較大,部分場景(特別是涉及到用戶關鍵的在線業務時)不太合適。

事后分析法需要盡可能多收集現場數據,然后立即恢復服務,同時針對收集的現場數據進行事后分析和復現。下面我們從性能診斷工具出發,分享一些案例與實踐。

一、性能診斷工具

性能診斷一種是針對已經確定有性能問題的系統和代碼進行診斷,還有一種是對預上線系統提前性能測試,確定性能是否符合上線要求。

本文主要針對前者,后者可以用各種性能壓測工具(例如 JMeter)進行測試,不在本文討論范圍內。

針對 Java 應用,性能診斷工具主要分為兩層:OS 層面和 Java 應用層面(包括應用代碼診斷和 GC 診斷)。

OS 診斷

OS 的診斷主要關注的是 CPU、Memory、I/O 三個方面。

二、 CPU 診斷

對于 CPU 主要關注平均負載(Load Average),CPU 使用率,上下文切換次數(Context Switch)。

通過 top 命令可以查看系統平均負載和 CPU 使用率,圖 2 為通過 top 命令查看某系統的狀態。

top 命令示例

Java性能如何調優

平均負載有三個數字:63.66,58.39,57.18,分別表示過去 1 分鐘、5 分鐘、15 分鐘機器的負載。按照經驗,若數值小于 0.7*CPU 個數,則系統工作正常;若超過這個值,甚至達到 CPU 核數的四五倍,則系統的負載就明顯偏高。

top 命令示例中 15 分鐘負載已經高達 57.18,1 分鐘負載是 63.66(系統為 16 核),說明系統出現負載問題,且存在進一步升高趨勢,需要定位具體原因了。

通過 vmstat 命令可以查看 CPU 的上下文切換次數,如下圖所示:

vmstat 命令示例 Java性能如何調優

上下文切換次數發生的場景主要有如下幾種:

1)時間片用完,CPU 正常調度下一個任務;

2)被其它優先級更高的任務搶占;

3)執行任務碰到 I/O 阻塞,掛起當前任務,切換到下一個任務;

4)用戶代碼主動掛起當前任務讓出 CPU;

5)多任務搶占資源,由于沒有搶到被掛起;

6)硬件中斷。

Java 線程上下文切換主要來自共享資源的競爭。一般單個對象加鎖很少成為系統瓶頸,除非鎖粒度過大。但在一個訪問頻度高,對多個對象連續加鎖的代碼塊中就可能出現大量上下文切換,成為系統瓶頸。

比如在我們系統中就曾出現 log4j 1.x 在較大并發下大量打印日志,出現頻繁上下文切換,大量線程阻塞,導致系統吞吐量大降的情況,其相關代碼如清單 1 所示,升級到 log4j 2.x 才解決這個問題。

for(Category c = this; c != null; c=c.parent) {
 // Protected against simultaneous call to addAppender, removeAppender,…
 synchronized(c) {
 if (c.aai != null) {
 write += c.aai.appendLoopAppenders(event);
 }
 …
 }
}

三、 Memory

從操作系統角度,內存關注應用進程是否足夠,可以使用 free –m 命令查看內存的使用情況。

通過 top 命令可以查看進程使用的虛擬內存 VIRT 和物理內存 RES,根據公式 VIRT = SWAP + RES 可以推算出具體應用使用的交換分區(Swap)情況,使用交換分區過大會影響 Java 應用性能,可以將 swappiness 值調到盡可能小。

因為對于 Java 應用來說,占用太多交換分區可能會影響性能,畢竟磁盤性能比內存慢太多。

四、 I/O

I/O 包括磁盤 I/O 和網絡 I/O,一般情況下磁盤更容易出現 I/O 瓶頸。通過 iostat 可以查看磁盤的讀寫情況,通過 CPU 的 I/O wait 可以看出磁盤 I/O 是否正常。

如果磁盤 I/O 一直處于很高的狀態,說明磁盤太慢或故障,成為了性能瓶頸,需要進行應用優化或者磁盤更換。

除了常用的 top、 ps、vmstat、iostat 等命令,還有其他 Linux 工具可以診斷系統問題,如 mpstat、tcpdump、netstat、pidstat、sar 等。Brendan 總結列出了 Linux 不同設備類型的性能診斷工具,如下圖所示,可供參考。

Linux 性能觀測工具

Java性能如何調優

五、 Java 應用診斷及工具

應用代碼性能問題是相對好解決的一類性能問題。通過一些應用層面監控報警,如果確定有問題的功能和代碼,直接通過代碼就可以定位;或者通過 top+jstack,找出有問題的線程棧,定位到問題線程的代碼上,也可以發現問題。對于更復雜,邏輯更多的代碼段,通過 Stopwatch 打印性能日志往往也可以定位大多數應用代碼性能問題。

常用的 Java 應用診斷包括線程、堆棧、GC 等方面的診斷。

jstack命令

jstack 命令通常配合 top 使用,通過 top -H -p pid 定位 Java 進程和線程,再利用 jstack -l pid 導出線程棧。由于線程棧是瞬態的,因此需要多次 dump,一般 3 次 dump,一般每次隔 5s 就行。將 top 定位的 Java 線程 pid 轉成 16 進制,得到 Java 線程棧中的 nid,可以找到對應的問題線程棧。

通過 top –H -p 查看運行時間較長 Java 線程

Java性能如何調優

如圖 5 所示,其中的線程 24985 運行時間較長,可能存在問題,轉成 16 進制后,通過 Java 線程棧找到對應線程 0x6199 的棧如下,從而定位問題點,如下圖所示。

jstack 查看線程堆棧

Java性能如何調優

Java性能調優最強實踐 JProfiler

JProfiler 可對 CPU、堆、內存進行分析,功能強大,如下圖所示。同時結合壓測工具,可以對代碼耗時采樣統計。

通過 JProfiler 進行內存分析

Java性能如何調優

六、 GC 診斷

Java GC 解決了程序員管理內存的風險,但 GC 引起的應用暫停成了另一個需要解決的問題。JDK 提供了一系列工具來定位 GC 問題,比較常用的有 jstat、jmap,還有第三方工具 MAT 等。

jstat

jstat 命令可打印 GC 詳細信息,Young GC 和 Full GC 次數,堆信息等。其命令格式為

jstat –gcxxx -t pid <interval> <count>,如下圖所示。

jstat 命令示例 Java性能如何調優

jmap

jmap 打印 Java 進程堆信息 jmap –heap pid。通過 jmap –dump:file=xxx pid 可 dump 堆到文件,然后通過其它工具進一步分析其堆使用情況

MAT

MAT 是 Java 堆的分析利器,提供了直觀的診斷報告,內置的 OQL 允許對堆進行類 SQL 查詢,功能強大,outgoing reference 和 incoming reference 可以對對象引用追根溯源。

MAT 示例

Java性能如何調優

Java性能調優最強實踐 圖 9 是 MAT 使用示例,MAT 有兩列顯示對象大小,分別是 Shallow size 和 Retained size,前者表示對象本身占用內存的大小,不包含其引用的對象,后者是對象自己及其直接或間接引用的對象的 Shallow size 之和,即該對象被回收后 GC 釋放的內存大小,一般說來關注后者大小即可。

對于有些大堆 (幾十 G) 的 Java 應用,需要較大內存才能打開 MAT。

通常本地開發機內存過小,是無法打開的,建議在線下服務器端安裝圖形環境和 MAT,遠程打開查看。或者執行 mat 命令生成堆索引,拷貝索引到本地,不過這種方式看到的堆信息有限。

為了診斷 GC 問題,建議在 JVM 參數中加上-XX:+PrintGCDateStamps。常用的 GC 參數如下圖所示。

常用 GC 參數

Java性能如何調優

對于 Java 應用,通過 top+jstack+jmap+MAT 可以定位大多數應用和內存問題,可謂必備工具。有些時候,Java 應用診斷需要參考 OS 相關信息,可使用一些更全面的診斷工具,比如 Zabbix(整合了 OS 和 JVM 監控)等。在分布式環境中,分布式跟蹤系統等基礎設施也對應用性能診斷提供了有力支持。

七、性能優化實踐

在介紹了一些常用的性能診斷工具后,下面將結合我們在 Java 應用調優中的一些實踐,從 JVM 層、應用代碼層以及數據庫層進行案例分享。

JVM 調優:GC 之痛

XX商業平臺某系統重構時選擇 RMI 作為內部遠程調用協議,系統上線后開始出現周期性的服務停止響應,暫停時間由數秒到數十秒不等。通過觀察 GC 日志,發現服務自啟動后每小時會出現一次 Full GC。由于系統堆設置較大,Full GC 一次暫停應用時間會較長,這對線上實時服務影響較大。

經過分析,在重構前系統沒有出現定期 Full GC 的情況,因此懷疑是 RMI 框架層面的問題。通過公開資料,發現 RMI 的 GDC(Distributed Garbage Collection,分布式垃圾收集)會啟動守護線程定期執行 Full GC 來回收遠程對象,清單 2 中展示了其守護線程代碼。

清單 2.DGC 守護線程源代碼

private static class Daemon extends Thread {
 public void run() {
 for (;;) { 
 //…
 long d = maxObjectInspectionAge();
 if (d >= l) {
 System.gc(); 
 d = 0;
 }
 //…
 }
 }
}

定位問題后解決起來就比較容易了。一種是通過增加-XX:+DisableExplicitGC 參數,直接禁用系統 GC 的顯示調用,但對使用 NIO 的系統,會有堆外內存溢出的風險。

另一種方式是通過調大 -Dsun.rmi.dgc.server.gcInterval 和-Dsun.rmi.dgc.client.gcInterval 參數,增加 Full GC 間隔,同時增加參數-XX:+ExplicitGCInvokesConcurrent,將一次完全 Stop-The-World 的 Full GC 調整為一次并發 GC 周期,減少應用暫停時間,同時對 NIO 應用也不會造成影響。

從下圖可知,調整之后的 Full GC 次數 在 3 月之后明顯減少。

Full GC 監控統計 Java性能如何調優

GC 調優對高并發大數據量交互的應用還是很有必要的,尤其是默認 JVM 參數通常不滿足業務需求,需要進行專門調優。GC 日志的解讀有很多公開的資料,本文不再贅述。

GC 調優目標基本有三個思路:降低 GC 頻率,可以通過增大堆空間,減少不必要對象生成;降低 GC 暫停時間,可以通過減少堆空間,使用 CMS GC 算法實現;避免 Full GC,調整 CMS 觸發比例,避免 Promotion Failure 和 Concurrent mode failure(老年代分配更多空間,增加 GC 線程數加快回收速度),減少大對象生成等。

應用層調優:嗅到代碼的壞味道

從應用層代碼調優入手,剖析代碼效率下降的根源,無疑是提高 Java 應用性能的很好的手段之一。

某商業廣告系統(采用 Nginx 進行負載均衡)某次日常上線后,其中有幾臺機器負載急劇升高,CPU 使用率迅速打滿。我們對線上進行了緊急回滾,并通過 jmap 和 jstack 對其中某臺服務器的現場進行保存。

通過 MAT 分析堆棧現場

Java性能如何調優

Java性能調優最強實踐 堆棧現場如上圖所示,根據 MAT 對 dump 數據的分析,發現最多的內存對象為 byte[] 和 java.util.HashMap $Entry,且 java.util.HashMap $Entry 對象存在循環引用。初步定位在該 HashMap 的 put 過程中有可能出現了死循環問題(圖中 java.util.HashMap $Entry 0x2add6d992cb8 和 0x2add6d992ce8 的 next 引用形成循環)。

查閱相關文檔定位這屬于典型的并發使用的場景錯誤

簡要的說就是 HashMap 本身并不具備多線程并發的特性,在多個線程同時 put 操作的情況下,內部數組進行擴容時會導致 HashMap 的內部鏈表形成環形結構,從而出現死循環。

針對此次上線,最大的改動在于通過內存緩存網站數據來提升系統性能,同時使用了懶加載機制,如清單 3 所示。

清單 3. 網站數據懶加載代碼

private static Map<Long, UnionDomain> domainMap = new HashMap<Long, UnionDomain>();
 private boolean isResetDomains() {
 if (CollectionUtils.isEmpty(domainMap)) {
 // 從遠端 http 接口獲取網站詳情
 List<UnionDomain> newDomains = unionDomainHttpClient
 .queryAllUnionDomain();
 if (CollectionUtils.isEmpty(domainMap)) {
 domainMap = new HashMap<Long, UnionDomain>();
 for (UnionDomain domain : newDomains) {
 if (domain != null) {
 domainMap.put(domain.getSubdomainId(), domain);
 }
 }
 }
 return true;
 }
 return false;
 }

可以看到此處的 domainMap 為靜態共享資源,它是 HashMap 類型,在多線程情況下會導致其內部鏈表形成環形結構,出現死循環。

通過對前端 Nginx 的連接和訪問日志可以看到,由于在系統重啟后 Nginx 積攢了大量的用戶請求,在 Resin 容器啟動,大量用戶請求涌入應用系統,多個用戶同時進行網站數據的請求和初始化工作,導致 HashMap 出現并發問題。在定位故障原因后解決方法則比較簡單,主要的解決方法有:

(1)采用 ConcurrentHashMap 或者同步塊的方式解決上述并發問題; (2)在系統啟動前完成網站緩存加載,去除懶加載等; (3)采用分布式緩存替換本地緩存等。

對于壞代碼的定位,除了常規意義上的代碼審查外,借助諸如 MAT 之類的工具也可以在一定程度對系統性能瓶頸點進行快速定位。但是一些與特定場景綁定或者業務數據綁定的情況,卻需要輔助代碼走查、性能檢測工具、數據模擬甚至線上引流等方式才能最終確認性能問題的出處。以下是我們總結的一些壞代碼可能的一些特征,供大家參考:

(1)代碼可讀性差,無基本編程規范; (2)對象生成過多或生成大對象,內存泄露等; (3)IO 流操作過多,或者忘記關閉; (4)數據庫操作過多,事務過長; (5)同步使用的場景錯誤; (6)循環迭代耗時操作等。

數據庫層調優:死鎖噩夢

對于大部分 Java 應用來說,與數據庫進行交互的場景非常普遍,尤其是 OLTP 這種對于數據一致性要求較高的應用,數據庫的性能會直接影響到整個應用的性能。搜狗商業平臺系統作為廣告主的廣告發布和投放平臺,對其物料的實時性和一致性都有極高的要求,我們在關系型數據庫優化方面也積累了一定的經驗。

對于廣告物料庫來說,較高的操作頻繁度(特別是通過批量物料工具操作)很極易造成數據庫的死鎖情況發生,其中一個比較典型的場景是廣告物料調價。客戶往往會頻繁的對物料的出價進行調整,從而間接給數據庫系統造成較大的負載壓力,也加劇了死鎖發生的可能性。下面以搜狗商業平臺某廣告系統廣告物料調價的案例進行說明。

某商業廣告系統某天訪問量突增,造成系統負載升高以及數據庫頻繁死鎖,死鎖語句如下圖所示。

死鎖語句

Java性能如何調優

其中,groupdomain 表上索引為 idx_groupdomain_accountid (accountid),idx_groupdomain_groupid(groupid),primary(groupdomainid) 三個單索引結構,采用 Mysql innodb 引擎。

此場景發生在更新組出價時,場景中存在著組、組行業(groupindus 表)和組網站(groupdomain 表)。

當更新組出價時,若組行業出價使用組出價(通過 isusegroupprice 標示,若為 1 則使用組出價)。同時若組網站出價使用組行業出價(通過 isuseindusprice 標示,若為 1 則使用組行業出價)時,也需要同時更新其組網站出價。由于每個組下面最大可以有 3000 個網站,因此在更新組出價時會長時間的對相關記錄進行鎖定。

從上面發生死鎖的問題可以看到,事務 1 和事務 2 均選擇了 idx_groupdomain_accountid 的單列索引。根據 Mysql innodb 引擎加鎖的特點,在一次事務中只會選擇一個索引使用,而且如果一旦使用二級索引進行加鎖后,會嘗試將主鍵索引進行加鎖。進一步分析可知事務 1 在請求事務 2 持有的idx_groupdomain_accountid二級索引加鎖(加鎖范圍“space id 5726 page no 8658 n bits 824 index”),但是事務 2 已獲得該二級索引 (“space id 5726 page no 8658 n bits 824 index”) 上所加的鎖,在等待請求鎖定主鍵索引 PRIMARY 索引上的鎖。由于事務 2 等待執行時間過長或長時間不釋放鎖,導致事務 1 最終發生回滾。

通過對當天訪問日志跟蹤可以看到,當天有客戶通過腳本方式發起大量的修改推廣組出價的操作,導致有大量事務在循環等待前一個事務釋放鎖定的主鍵 PRIMARY 索引。該問題的根源實際上在于 Mysql innodb 引擎對于索引利用有限,在 Oracle 數據庫中此問題并不突出。

解決的方式自然是希望單個事務鎖定的記錄數越少越好,這樣產生死鎖的概率也會大大降低。最終使用了(accountid, groupid)的復合索引,縮小了單個事務鎖定的記錄條數,也實現了不同計劃下的推廣組數據記錄的隔離,從而減少該類死鎖的發生幾率。

通常來說,對于數據庫層的調優我們基本上會從以下幾個方面出發:

(1)在 SQL 語句層面進行優化:慢 SQL 分析、索引分析和調優、事務拆分等;

(2)在數據庫配置層面進行優化:比如字段設計、調整緩存大小、磁盤 I/O 等數據庫參數優化、數據碎片整理等;

(3)從數據庫結構層面進行優化:考慮數據庫的垂直拆分和水平拆分等;

(4)選擇合適的數據庫引擎或者類型適應不同場景,比如考慮引入 NoSQL 等。

八、總結與建議

性能調優同樣遵循 2-8 原則,80%的性能問題是由 20%的代碼產生的,因此優化關鍵代碼事半功倍。同時,對性能的優化要做到按需優化,過度優化可能引入更多問題。對于 Java 性能優化,不僅要理解系統架構、應用代碼,同樣需要關注 JVM 層甚至操作系統底層。總結起來主要可以從以下幾點進行考慮:

1)基礎性能的調優

這里的基礎性能指的是硬件層級或者操作系統層級的升級優化,比如網絡調優,操作系統版本升級,硬件設備優化等。比如 F5 的使用和 SDD 硬盤的引入,包括新版本 Linux 在 NIO 方面的升級,都可以極大的促進應用的性能提升;

2)數據庫性能優化

包括常見的事務拆分,索引調優,SQL 優化,NoSQL 引入等,比如在事務拆分時引入異步化處理,最終達到一致性等做法的引入,包括在針對具體場景引入的各類 NoSQL 數據庫,都可以大大緩解傳統數據庫在高并發下的不足;

3)應用架構優化

引入一些新的計算或者存儲框架,利用新特性解決原有集群計算性能瓶頸等;或者引入分布式策略,在計算和存儲進行水平化,包括提前計算預處理等,利用典型的空間換時間的做法等;都可以在一定程度上降低系統負載;

4)業務層面的優化

技術并不是提升系統性能的唯一手段,在很多出現性能問題的場景中,其實可以看到很大一部分都是因為特殊的業務場景引起的,如果能在業務上進行規避或者調整,其實往往是最有效的。

到此,關于“Java性能如何調優”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

门头沟区| 敦化市| 道真| 霞浦县| 怀宁县| 汾阳市| 剑川县| 乳山市| 大宁县| 金山区| 平昌县| 枞阳县| 怀集县| 扶风县| 东光县| 东平县| 德格县| 乳源| 天祝| 吉安县| 赤壁市| 乐昌市| 偃师市| 商河县| 绵阳市| 南城县| 永康市| 报价| 宝鸡市| 杂多县| 靖西县| 双牌县| 静安区| 达孜县| 宣汉县| 勃利县| 晋宁县| 安吉县| 南溪县| 长子县| 依安县|