點擊這裏,查看常見內存分析思路及其餘重要內容前端
簡介:今天,齊光將會基於以前列舉的衆多指標,給出一些常見的調優分析思路,即:如何在衆多異常性能指標中,找出最核心的那一個,進而定位性能瓶頸點,最後進行性能調優。整篇文章會按照代碼、CPU、內存、網絡、磁盤等方向進行組織,針對對某一各優化點,會有系統的「套路」總結,便於思路的遷移實踐。ios
遇到性能問題,首先應該作的是檢查否與業務代碼相關——不是經過閱讀代碼解決問題,而是經過日誌或代碼,排除掉一些與業務代碼相關的低級錯誤。性能優化的最佳位置,是應用內部。正則表達式
譬如,查看業務日誌,檢查日誌內容裏是否有大量的報錯產生,應用層、框架層的一些性能問題,大多數都能從日誌裏找到端倪(日誌級別設置不合理,致使線上瘋狂打日誌);再者,檢查代碼的主要邏輯,如 for 循環的不合理使用、NPE、正則表達式、數學計算等常見的一些問題,均可以經過簡單地修改代碼修復問題。設計模式
別動輒就把性能優化和緩存、異步化、JVM 調優等名詞掛鉤,複雜問題可能會有簡單解,「二八原則」在性能優化的領域裏裏依然有效。固然了,瞭解一些基本的「代碼經常使用踩坑點」,能夠加速咱們問題分析思路的過程,從 CPU、內存、JVM 等分析到的一些瓶頸點優化思路,也有可能在代碼這裏體現出來。緩存
下面是一些高頻的,容易形成性能問題的編碼要點。性能優化
1)正則表達式很是消耗 CPU(如貪婪模式可能會引發回溯),慎用字符串的 split()、replaceAll() 等方法;正則表達式表達式必定預編譯。網絡
2)String.intern() 在低版本(Java 1.6 以及以前)的 JDK 上使用,可能會形成方法區(永久代)內存溢出。在高版本 JDK 中,若是 string pool 設置過小而緩存的字符串過多,也會形成較大的性能開銷。併發
3)輸出異常日誌的時候,若是堆棧信息是明確的,能夠取消輸出詳細堆棧,異常堆棧的構造是有成本的。注意:同一位置拋出大量重複的堆棧信息,JIT 會將其優化後成,直接拋出一個事先編譯好的、類型匹配的異常,異常堆棧信息就看不到了。框架
4)避免引用類型和基礎類型之間無謂的拆裝箱操做,請儘可能保持一致,自動裝箱發生太頻繁,會很是嚴重消耗性能。dom
5)Stream API 的選擇。複雜和並行操做,推薦使用 Stream API,能夠簡化代碼,同時發揮來發揮出 CPU 多核的優點,若是是簡單操做或者 CPU 是單核,推薦使用顯式迭代。
6)根據業務場景,經過 ThreadPoolExecutor 手動建立線程池,結合任務的不一樣,指定線程數量和隊列大小,規避資源耗盡的風險,統一命名後的線程也便於後續問題排查。
7)根據業務場景,合理選擇併發容器。如選擇 Map 類型的容器時,若是對數據要求有強一致性,可以使用 Hashtable 或者 「Map + 鎖」 ;讀遠大於寫,使用 CopyOnWriteArrayList;存取數據量小、對數據沒有強一致性的要求、變動不頻繁的,使用 ConcurrentHashMap;存取數據量大、讀寫頻繁、對數據沒有強一致性的要求,使用 ConcurrentSkipListMap。
8)鎖的優化思路有:減小鎖的粒度、循環中使用鎖粗化、減小鎖的持有時間(讀寫鎖的選擇)等。同時,也考慮使用一些 JDK 優化後的併發類,如對一致性要求不高的統計場景中,使用 LongAdder 替代 AtomicLong 進行計數,使用 ThreadLocalRandom 替代 Random 類等。
代碼層的優化除了上面這些,還有不少就不一一列出了。咱們能夠觀察到,在這些要點裏,有一些共性的優化思路,是能夠抽取出來的,譬如:
空間換時間:使用內存或者磁盤,換取更寶貴的CPU 或者網絡,如緩存的使用;
時間換空間:經過犧牲部分 CPU,節省內存或者網絡資源,如把一次大的網絡傳輸變成屢次;
其餘諸如並行化、異步化、池化技術等。
前面講到過,咱們更應該關注 CPU 負載,CPU 利用率高通常不是問題,CPU 負載 是判斷系統計算資源是否健康的關鍵依據。
2.1 CPU 利用率高&&平均負載高
這種狀況常見於 CPU 密集型的應用,大量的線程處於可運行狀態,I/O 不多,常見的大量消耗 CPU 資源的應用場景有:
正則操做
數學運算
序列化/反序列化
反射操做
死循環或者不合理的大量循環
基礎/第三方組件缺陷
排查高 CPU 佔用的通常思路:經過 jstack 屢次(> 5次)打印線程棧,通常能夠定位到消耗 CPU 較多的線程堆棧。或者經過 Profiling 的方式(基於事件採樣或者埋點),獲得應用在一段時間內的 on-CPU 火焰圖,也能較快定位問題。
還有一種可能的狀況,此時應用存在頻繁的 GC (包括 Young GC、Old GC、Full GC),這也會致使 CPU 利用率和負載都升高。排查思路:使用 jstat -gcutil 持續輸出當前應用的 GC 統計次數和時間。頻繁 GC 致使的負載升高,通常還伴隨着可用內存不足,可用 free 或者 top 等命令查看下當前機器的可用內存大小。
CPU 利用率太高,是否有多是 CPU 自己性能瓶頸致使的呢?也是有可能的。能夠進一步經過 vmstat 查看詳細的 CPU 利用率。用戶態 CPU 利用率(us)較高,說明用戶態進程佔用了較多的 CPU,若是這個值長期大於50%,應該着重排查應用自己的性能問題。內核態 CPU 利用率(sy)較高,說明內核態佔用了較多的 CPU,因此應該着重排查內核線程或者系統調用的性能問題。若是 us + sy 的值大於 80%,說明 CPU 可能不足。
2.2 CPU 利用率低&&平均負載高
若是CPU利用率不高,說明咱們的應用並無忙於計算,而是在幹其餘的事。CPU 利用率低而平均負載高,常見於 I/O 密集型進程,這很容易理解,畢竟平均負載就是 R 狀態進程和 D 狀態進程的和,除掉了第一種,就只剩下 D 狀態進程了(產生 D 狀態的緣由通常是由於在等待 I/O,例如磁盤 I/O、網絡 I/O 等)。
排查&&驗證思路:使用 vmstat 1 定時輸出系統資源使用,觀察 %wa(iowait) 列的值,該列標識了磁盤 I/O 等待時間在 CPU 時間片中的百分比,若是這個值超過30%,說明磁盤 I/O 等待嚴重,這多是大量的磁盤隨機訪問或直接的磁盤訪問(沒有使用系統緩存)形成的,也可能磁盤自己存在瓶頸,能夠結合 iostat 或 dstat 的輸出加以驗證,如 %wa(iowait) 升高同時觀察到磁盤的讀請求很大,說明多是磁盤讀致使的問題。
此外,耗時較長的網絡請求(即網絡 I/O)也會致使 CPU 平均負載升高,如 MySQL 慢查詢、使用 RPC 接口獲取接口數據等。這種狀況的排查通常須要結合應用自己的上下游依賴關係以及中間件埋點的 trace 日誌,進行綜合分析。
2.3 CPU 上下文切換次數變高
先用 vmstat 查看系統的上下文切換次數,而後經過 pidstat 觀察進程的自願上下文切換(cswch)和非自願上下文切換(nvcswch)狀況。自願上下文切換,是由於應用內部線程狀態發生轉換所致,譬如調用 sleep()、join()、wait()等方法,或使用了 Lock 或 synchronized 鎖結構;非自願上下文切換,是由於線程因爲被分配的時間片用完或因爲執行優先級被調度器調度所致。
若是自願上下文切換次數較高,意味着 CPU 存在資源獲取等待,好比說,I/O、內存等系統資源不足等。若是是非自願上下文切換次數較高,可能的緣由是應用內線程數過多,致使 CPU 時間片競爭激烈,頻頻被系統強制調度,此時能夠結合 jstack 統計的線程數和線程狀態分佈加以佐證。
一、 內存相關
前面提到,內存分爲系統內存和進程內存(含 Java 應用進程),通常咱們遇到的內存問題,絕大多數都會落在進程內存上,系統資源形成的瓶頸佔比較小。對於 Java 進程,它自帶的內存管理自動化地解決了兩個問題:如何給對象分配內存以及如何回收分配給對象的內存,其核心是垃圾回收機制。
垃圾回收雖然能夠有效地防止內存泄露、保證內存的有效使用,但也並非萬能的,不合理的參數配置和代碼邏輯,依然會帶來一系列的內存問題。此外,早期的垃圾回收器,在功能性和回收效率上也不是很好,過多的 GC 參數設置很是依賴開發人員的調優經驗。好比,對於最大堆內存的不恰當設置,可能會引起堆溢出或者堆震盪等一系列問題。
下面看看幾個常見的內存問題分析思路。
關鍵字:設計模式 緩存 JavaScript 網絡協議 前端開發 Java API 調度 Perl 容器