90%的人會遇到性能問題,如何用1行代碼快速定位?
今天,齊光將會基于之前列舉的眾多指標,給出一些常見的調優(yōu)分析思路,即:如何在眾多異常性能指標中,找出最核心的那一個,進而定位性能瓶頸點,最后進行性能調優(yōu)。整篇文章會按照代碼、CPU、內存、網絡、磁盤等方向進行組織,針對對某一各優(yōu)化點,會有系統(tǒng)的「套路」總結,便于思路的遷移實踐。
1. 代碼相關
遇到性能問題,首先應該做的是檢查否與業(yè)務代碼相關——不是通過閱讀代碼解決問題,而是通過日志或代碼,排除掉一些與業(yè)務代碼相關的低級錯誤。性能優(yōu)化的最佳位置,是應用內部。
譬如,查看業(yè)務日志,檢查日志內容里是否有大量的報錯產生,應用層、框架層的一些性能問題,大多數(shù)都能從日志里找到端倪(日志級別設置不合理,導致線上瘋狂打日志);再者,檢查代碼的主要邏輯,如 for 循環(huán)的不合理使用、NPE、正則表達式、數(shù)學計算等常見的一些問題,都可以通過簡單地修改代碼修復問題。
別動輒就把性能優(yōu)化和緩存、異步化、JVM 調優(yōu)等名詞掛鉤,復雜問題可能會有簡單解,「二八原則」在性能優(yōu)化的領域里里依然有效。當然了,了解一些基本的「代碼常用踩坑點」,可以加速我們問題分析思路的過程,從 CPU、內存、JVM 等分析到的一些瓶頸點優(yōu)化思路,也有可能在代碼這里體現(xiàn)出來。
下面是一些高頻的,容易造成性能問題的編碼要點。
1)正則表達式非常消耗 CPU(如貪婪模式可能會引起回溯),慎用字符串的 split()、replaceAll() 等方法;正則表達式表達式一定預編譯。
2)String.intern() 在低版本(Java 1.6 以及之前)的 JDK 上使用,可能會造成方法區(qū)(永久代)內存溢出。在高版本 JDK 中,如果 string pool 設置太小而緩存的字符串過多,也會造成較大的性能開銷。
3)輸出異常日志的時候,如果堆棧信息是明確的,可以取消輸出詳細堆棧,異常堆棧的構造是有成本的。注意:同一位置拋出大量重復的堆棧信息,JIT 會將其優(yōu)化后成,直接拋出一個事先編譯好的、類型匹配的異常,異常堆棧信息就看不到了。
4)避免引用類型和基礎類型之間無謂的拆裝箱操作,請盡量保持一致,自動裝箱發(fā)生太頻繁,會非常嚴重消耗性能。
5)Stream API 的選擇。復雜和并行操作,推薦使用 Stream API,可以簡化代碼,同時發(fā)揮來發(fā)揮出 CPU 多核的優(yōu)勢,如果是簡單操作或者 CPU 是單核,推薦使用顯式迭代。
6)根據業(yè)務場景,通過 ThreadPoolExecutor 手動創(chuàng)建線程池,結合任務的不同,指定線程數(shù)量和隊列大小,規(guī)避資源耗盡的風險,統(tǒng)一命名后的線程也便于后續(xù)問題排查。
7)根據業(yè)務場景,合理選擇并發(fā)容器。如選擇 Map 類型的容器時,如果對數(shù)據要求有強一致性,可使用 Hashtable 或者 「Map + 鎖」 ;讀遠大于寫,使用 CopyOnWriteArrayList;存取數(shù)據量小、對數(shù)據沒有強一致性的要求、變更不頻繁的,使用 ConcurrentHashMap;存取數(shù)據量大、讀寫頻繁、對數(shù)據沒有強一致性的要求,使用 ConcurrentSkipListMap。
8)鎖的優(yōu)化思路有:減少鎖的粒度、循環(huán)中使用鎖粗化、減少鎖的持有時間(讀寫鎖的選擇)等。同時,也考慮使用一些 JDK 優(yōu)化后的并發(fā)類,如對一致性要求不高的統(tǒng)計場景中,使用 LongAdder 替代 AtomicLong 進行計數(shù),使用 ThreadLocalRandom 替代 Random 類等。
代碼層的優(yōu)化除了上面這些,還有很多就不一一列出了。我們可以觀察到,在這些要點里,有一些共性的優(yōu)化思路,是可以抽取出來的,譬如:
- 空間換時間:使用內存或者磁盤,換取更寶貴的CPU 或者網絡,如緩存的使用;
- 時間換空間:通過犧牲部分 CPU,節(jié)省內存或者網絡資源,如把一次大的網絡傳輸變成多次;
- 其他諸如并行化、異步化、池化技術等。
2. CPU 相關
前面講到過,我們更應該關注 CPU 負載,CPU 利用率高一般不是問題,CPU 負載 是判斷系統(tǒng)計算資源是否健康的關鍵依據。
2.1 CPU 利用率高&&平均負載高
這種情況常見于 CPU 密集型的應用,大量的線程處于可運行狀態(tài),I/O 很少,常見的大量消耗 CPU 資源的應用場景有:
- 正則操作
- 數(shù)學運算
- 序列化/反序列化
- 反射操作
- 死循環(huán)或者不合理的大量循環(huán)
- 基礎/第三方組件缺陷
排查高 CPU 占用的一般思路:通過 jstack 多次(> 5次)打印線程棧,一般可以定位到消耗 CPU 較多的線程堆棧。或者通過 Profiling 的方式(基于事件采樣或者埋點),得到應用在一段時間內的 on-CPU 火焰圖,也能較快定位問題。
還有一種可能的情況,此時應用存在頻繁的 GC (包括 Young GC、Old GC、Full GC),這也會導致 CPU 利用率和負載都升高。排查思路:使用 jstat -gcutil 持續(xù)輸出當前應用的 GC 統(tǒng)計次數(shù)和時間。頻繁 GC 導致的負載升高,一般還伴隨著可用內存不足,可用 free 或者 top 等命令查看下當前機器的可用內存大小。
CPU 利用率過高,是否有可能是 CPU 本身性能瓶頸導致的呢?也是有可能的??梢赃M一步通過 vmstat 查看詳細的 CPU 利用率。用戶態(tài) CPU 利用率(us)較高,說明用戶態(tài)進程占用了較多的 CPU,如果這個值長期大于50%,應該著重排查應用本身的性能問題。內核態(tài) CPU 利用率(sy)較高,說明內核態(tài)占用了較多的 CPU,所以應該著重排查內核線程或者系統(tǒng)調用的性能問題。如果 us + sy 的值大于 80%,說明 CPU 可能不足。
2.2 CPU 利用率低&&平均負載高
如果CPU利用率不高,說明我們的應用并沒有忙于計算,而是在干其他的事。CPU 利用率低而平均負載高,常見于 I/O 密集型進程,這很容易理解,畢竟平均負載就是 R 狀態(tài)進程和 D 狀態(tài)進程的和,除掉了第一種,就只剩下 D 狀態(tài)進程了(產生 D 狀態(tài)的原因一般是因為在等待 I/O,例如磁盤 I/O、網絡 I/O 等)。
排查&&驗證思路:使用 vmstat 1 定時輸出系統(tǒng)資源使用,觀察 %wa(iowait) 列的值,該列標識了磁盤 I/O 等待時間在 CPU 時間片中的百分比,如果這個值超過30%,說明磁盤 I/O 等待嚴重,這可能是大量的磁盤隨機訪問或直接的磁盤訪問(沒有使用系統(tǒng)緩存)造成的,也可能磁盤本身存在瓶頸,可以結合 iostat 或 dstat 的輸出加以驗證,如 %wa(iowait) 升高同時觀察到磁盤的讀請求很大,說明可能是磁盤讀導致的問題。
此外,耗時較長的網絡請求(即網絡 I/O)也會導致 CPU 平均負載升高,如 MySQL 慢查詢、使用 RPC 接口獲取接口數(shù)據等。這種情況的排查一般需要結合應用本身的上下游依賴關系以及中間件埋點的 trace 日志,進行綜合分析。
2.3 CPU 上下文切換次數(shù)變高
先用 vmstat 查看系統(tǒng)的上下文切換次數(shù),然后通過 pidstat 觀察進程的自愿上下文切換(cswch)和非自愿上下文切換(nvcswch)情況。自愿上下文切換,是因為應用內部線程狀態(tài)發(fā)生轉換所致,譬如調用 sleep()、join()、wait()等方法,或使用了 Lock 或 synchronized 鎖結構;非自愿上下文切換,是因為線程由于被分配的時間片用完或由于執(zhí)行優(yōu)先級被調度器調度所致。
如果自愿上下文切換次數(shù)較高,意味著 CPU 存在資源獲取等待,比如說,I/O、內存等系統(tǒng)資源不足等。如果是非自愿上下文切換次數(shù)較高,可能的原因是應用內線程數(shù)過多,導致 CPU 時間片競爭激烈,頻頻被系統(tǒng)強制調度,此時可以結合 jstack 統(tǒng)計的線程數(shù)和線程狀態(tài)分布加以佐證。
3. 內存相關
前面提到,內存分為系統(tǒng)內存和進程內存(含 Java 應用進程),一般我們遇到的內存問題,絕大多數(shù)都會落在進程內存上,系統(tǒng)資源造成的瓶頸占比較小。對于 Java 進程,它自帶的內存管理自動化地解決了兩個問題:如何給對象分配內存以及如何回收分配給對象的內存,其核心是垃圾回收機制。
垃圾回收雖然可以有效地防止內存泄露、保證內存的有效使用,但也并不是萬能的,不合理的參數(shù)配置和代碼邏輯,依然會帶來一系列的內存問題。此外,早期的垃圾回收器,在功能性和回收效率上也不是很好,過多的 GC 參數(shù)設置非常依賴開發(fā)人員的調優(yōu)經驗。比如,對于最大堆內存的不恰當設置,可能會引發(fā)堆溢出或者堆震蕩等一系列問題。
下面看看幾個常見的內存問題分析思路。
3.1 系統(tǒng)內存不足
Java 應用一般都有單機或者集群的內存水位監(jiān)控,如果單機的內存利用率大于 95%,或者集群的內存利用率大于80%,就說明可能存在潛在的內存問題(注:這里的內存水位是系統(tǒng)內存)。
除了一些較極端的情況,一般系統(tǒng)內存不足,大概率是由 Java 應用引起的。使用 top 命令時,我們可以看到 Java 應用進程的實際內存占用,其中 RES 表示進程的常駐內存使用,VIRT 表示進程的虛擬內存占用,內存大小的關系為:VIRT > RES > Java 應用實際使用的堆大小。除了堆內存,Java 進程整體的內存占用,還有方法區(qū)/元空間、JIT 緩存等,主要組成如下:
Java 應用內存占用 = Heap(堆區(qū))+ Code Cache(代碼緩存區(qū)) + Metaspace(元空間)+ Symbol tables(符號表)+ Thread stacks(線程棧區(qū))+ Direct buffers(堆外內存)+ JVM structures(其他的一些 JVM 自身占用)+ Mapped files(內存映射文件)+ Native Libraries(本地庫)+ ...
Java 進程的內存占用,可以使用 jstat -gc 命令查看,輸出的指標中可以得到當前堆內存各分區(qū)、元空間的使用情況。堆外內存的統(tǒng)計和使用情況,可以利用 NMT(Native Memory Tracking,HotSpot VM Java8 引入)獲取。線程棧使用的內存空間很容易被忽略,雖然線程棧內存采用的是懶加載的模式,不會直接使用 +Xss 的大小來分配內存,但是過多的線程也會導致不必要的內存占用,可以使用 jstackmem 這個腳本統(tǒng)計整體的線程占用。
系統(tǒng)內存不足的排查思路:
首先使用 free 查看當前內存的可用空間大小,然后使用 vmstat 查看具體的內存使用情況及內存增長趨勢,這個階段一般能定位占用內存最多的進程;
分析緩存 / 緩沖區(qū)的內存使用。如果這個數(shù)值在一段時間變化不大,可以忽略。如果觀察到緩存 / 緩沖區(qū)的大小在持續(xù)升高,則可以使用 pcstat、cachetop、slabtop 等工具,分析緩存 / 緩沖區(qū)的具體占用;
排除掉緩存 / 緩沖區(qū)對系統(tǒng)內存的影響后,如果發(fā)現(xiàn)內存還在不斷增長,說明很有可能存在內存泄漏。
3.2 Java 內存溢出
內存溢出是指應用新建一個對象實例時,所需的內存空間大于堆的可用空間。內存溢出的種類較多,一般會在報錯日志里看到 OutOfMemoryError 關鍵字。常見內存溢出種類及分析思路如下:
1)java.lang.OutOfMemoryError: Java heap space。原因:堆中(新生代和老年代)無法繼續(xù)分配對象了、某些對象的引用長期被持有沒有被釋放,垃圾回收器無法回收、使用了大量的 Finalizer 對象,這些對象并不在 GC 的回收周期內等。一般堆溢出都是由于內存泄漏引起的,如果確認沒有內存泄漏,可以適當通過增大堆內存。
2)java.lang.OutOfMemoryError:GC overhead limit exceeded。原因:垃圾回收器超過98%的時間用來垃圾回收,但回收不到2%的堆內存,一般是因為存在內存泄漏或堆空間過小。
3)java.lang.OutOfMemoryError: Metaspace或java.lang.OutOfMemoryError: PermGen space。排查思路:檢查是否有動態(tài)的類加載但沒有及時卸載,是否有大量的字符串常量池化,永久代/元空間是否設置過小等。
4)java.lang.OutOfMemoryError : unable to create new native Thread。原因:虛擬機在拓展??臻g時,無法申請到足夠的內存空間??蛇m當降低每個線程棧的大小以及應用整體的線程個數(shù)。此外,系統(tǒng)里總體的進程/線程創(chuàng)建總數(shù)也受到系統(tǒng)空閑內存和操作系統(tǒng)的限制,請仔細檢查。注:這種棧溢出,和 StackOverflowError 不同,后者是由于方法調用層次太深,分配的棧內存不夠新建棧幀導致。
此外,還有 Swap 分區(qū)溢出、本地方法棧溢出、數(shù)組分配溢出等 OutOfMemoryError 類型,由于不是很常見,就不一一介紹了。
3.3 Java 內存泄漏
Java 內存泄漏可以說是開發(fā)人員的噩夢,內存泄漏與內存溢出不同則,后者簡單粗暴,現(xiàn)場也比較好找。內存泄漏的表現(xiàn)是:應用運行一段時間后,內存利用率越來越高,響應越來越慢,直到最終出現(xiàn)進程「假死」。
Java 內存泄漏可能會造成系統(tǒng)可用內存不足、進程假死、OOM 等,排查思路卻不外乎下面兩種:
通過 jmap 定期輸出堆內對象統(tǒng)計,定位數(shù)量和大小持續(xù)增長的對象;
使用 Profiler 工具對應用進行 Profiling,尋找內存分配熱點。
此外,在堆內存持續(xù)增長時,建議 dump 一份堆內存的快照,后面可以基于快照做一些分析。快照雖然是瞬時值,但也是有一定的意義的。
3.4 垃圾回收相關
GC(垃圾回收,下同)的各項指標,是衡量 Java 進程內存使用是否健康的重要標尺。垃圾回收最核心指標:GC Pause(包括 MinorGC 和 MajorGC) 的頻率和次數(shù),以及每次回收的內存詳情,前者可以通過 jstat 工具直接得到,后者需要分析 GC 日志。需要注意的是,jstat 輸出列中的 FGC/FGCT 表示的是一次老年代垃圾回收中,出現(xiàn) GC Pause (即 Stop-the-World)的次數(shù),譬如對于 CMS 垃圾回收器,每次老年代垃圾回收這個值會增加2(初始標記和重新標記著兩個 Stop-the-World 的階段,這個統(tǒng)計值會是 2。
什么時候需要進行 GC 調優(yōu)?這取決于應用的具體情況,譬如對響應時間的要求、對吞吐量的要求、系統(tǒng)資源限制等。一些經驗:GC 頻率和耗時大幅上升、GC Pause 平均耗時超過 500ms、Full GC 執(zhí)行頻率小于1分鐘等,如果 GC 滿足上述的一些特征,說明需要進行 GC 調優(yōu)了。
由于垃圾回收器種類繁多,針對不同的應用,調優(yōu)策略也有所區(qū)別,因此下面介紹幾種通用的的 GC 調優(yōu)策略。
1)選擇合適的 GC 回收器。根據應用對延遲、吞吐的要求,結合各垃圾回收器的特點,合理選用。推薦使用 G1 替換 CMS 垃圾回收器,G1 的性能是在逐步優(yōu)化的,在 8GB 內存及以下的機器上,其各方面的表現(xiàn)也在趕上甚至有超越之勢。G1 調參較方便,而 CMS 垃圾回收器參數(shù)太過復雜、容易造成空間碎片化、對 CPU 消耗較高等弊端,也使其目前處于廢棄狀態(tài)。Java 11 里新引入的 ZGC 垃圾回收器,基本可用做到全階段并發(fā)標記和回收,值得期待。
2)合理的堆內存大小設置。堆大小不要設置過大,建議不要超過系統(tǒng)內存的 75%,避免出現(xiàn)系統(tǒng)內存耗盡。最大堆大小和初始化堆的大小保持一致,避免堆震蕩。新生代的大小設置比較關鍵,我們調整 GC 的頻率和耗時,很多時候就是在調整新生代的大小,包括新生代和老年代的占比、新生代中 Eden 區(qū)和 Survivor 區(qū)的比例等,這些比例的設置還需要考慮各代中對象的晉升年齡,整個過程需要考慮的東西還是比較多的。如果使用 G1 垃圾回收器,新生代大小這一塊需要考慮的東西就少很多了,自適應的策略會決定每一次的回收集合(CSet)。新生代的調整是 GC 調優(yōu)的核心,非常依賴經驗,但是一般來說,Young GC 頻率高,意味著新生代太小(或 Eden 區(qū)和 Survivor 配置不合理),Young GC 時間長,意味著新生代過大,這兩個方向大體不差。
3)降低 Full GC 的頻率。如果出現(xiàn)了頻繁的 Full GC 或者 老年代 GC,很有可能是存在內存泄漏,導致對象被長期持有,通過 dump 內存快照進行分析,一般能較快地定位問題。除此之外,新生代和老年代的比例不合適,導致對象頻頻被直接分配到老年代,也有可能會造成 Full GC,這個時候需要結合業(yè)務代碼和內存快照綜合分析。此外,通過配置 GC 參數(shù),可以幫助我們獲取很多 GC 調優(yōu)所需的關鍵信息,如配置-XX:+PrintGCApplicationStoppedTime-XX:+PrintSafepointStatistics-XX:+PrintTenuringDistribution,分別可以獲取 GC Pause 分布、安全點耗時統(tǒng)計、對象晉升年齡分布的信息,加上 -XX:+PrintFlagsFinal 可以讓我們了解最終生效的 GC 參數(shù)等。
4. 磁盤I/O和網絡I/O
4.1 磁盤 I/O 問題排查思路:
- 使用工具輸出磁盤相關的輸出的指標,常用的有 %wa(iowait)、%util,根據輸判斷磁盤 I/O 是否存在異常,譬如 %util 這個指標較高,說明有較重的 I/O 行為;
- 使用 pidstat 定位到具體進程,關注下讀或寫的數(shù)據大小和速率;
- 使用 lsof + 進程號,可查看該異常進程打開的文件列表(含目錄、塊設備、動態(tài)庫、網絡套接字等),結合業(yè)務代碼,一般可定位到 I/O 的來源,如果需要具體分析,還可以使用 perf 等工具進行 trace 定位 I/O 源頭。
需要注意的是,%wa(iowait)的升高不代表一定意味著磁盤 I/O 存在瓶頸,這是數(shù)值代表 CPU 上 I/O 操作的時間占用的百分比,如果應用進程的在這段時間內的主要活動就是 I/O,那么也是正常的。
4.2 網絡 I/O 存在瓶頸,可能的原因如下:
- 一次傳輸?shù)膶ο筮^大,可能會導致請求響應慢,同時 GC 頻繁;
- 網絡 I/O 模型選擇不合理,導致應用整體 QPS 較低,響應時間長;
- RPC 調用的線程池設置不合理??墒褂?jstack 統(tǒng)計線程數(shù)的分布,如果處于 TIMED_WAITING 或 WAITING 狀態(tài)的線程較多,則需要重點關注。舉例:數(shù)據庫連接池不夠用,體現(xiàn)在線程棧上就是很多線程在競爭一把連接池的鎖;
- RPC 調用超時時間設置不合理,造成請求失敗較多;
Java 應用的線程堆??煺辗浅S杏?,除了上面提到的用于排查線程池配置不合理的問題,其他的一些場景,如 CPU 飆高、應用響應較慢等,都可以先從線程堆棧入手。
5. 有用的一行命令
這一小節(jié)給出若干在定位性能問題的命令,用于快速定位。
1)查看系統(tǒng)當前網絡連接數(shù)
- netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
2)查看堆內對象的分布 Top 50(定位內存泄漏)
- jmap –histo:live $pid | sort-n -r -k2 | head-n 50
3)按照 CPU/內存的使用情況列出前10 的進程
- #內存
- ps axo %mem,pid,euser,cmd | sort -nr | head -10
- #CPU
- ps -aeo pcpu,user,pid,cmd | sort -nr | head -10
4)顯示系統(tǒng)整體的 CPU利用率和閑置率
- grep "cpu " /proc/stat | awk -F ' ' '{total = $2 + $3 + $4 + $5} END {print "idle \t used\n" $5*100/total "% " $2*100/total "%"}'
5)按線程狀態(tài)統(tǒng)計線程數(shù)(加強版)
- jstack $pid | grep java.lang.Thread.State:|sort|uniq -c | awk '{sum+=$1; split($0,a,":");gsub(/^[ \t]+|[ \t]+$/, "", a[2]);printf "%s: %s\n", a[2], $1}; END {printf "TOTAL: %s",sum}';
6)查看最消耗 CPU 的 Top10 線程機器堆棧信息
推薦大家使用 show-busy-java-threads 腳本,該腳本可用于快速排查 Java 的 CPU 性能問題(top us值過高),自動查出運行的 Java 進程中消耗 CPU 多的線程,并打印出其線程棧,從而確定導致性能問題的方法調用,該腳本已經用于阿里線上運維環(huán)境。鏈接地址:https://github.com/oldratlee/useful-scripts/。
7)火焰圖生成(需要安裝 perf、perf-map-agent、FlameGraph 這三個項目):
- # 1. 收集應用運行時的堆棧和符號表信息(采樣時間30秒,每秒99個事件);
- sudo perf record -F 99 -p $pid -g -- sleep 30; ./jmaps
- # 2. 使用 perf script 生成分析結果,生成的 flamegraph.svg 文件就是火焰圖。
- sudo perf script | ./pkgsplit-perf.pl | grep java | ./flamegraph.pl > flamegraph.svg
8)按照 Swap 分區(qū)的使用情況列出前 10 的進程
- for file in /proc/*/status ; do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{ print ""}' $file; done | sort -k 3 -n -r | head -10
9)JVM 內存使用及垃圾回收狀態(tài)統(tǒng)計
- #顯示最后一次或當前正在發(fā)生的垃圾收集的誘發(fā)原因
- jstat -gccause $pid
- #顯示各個代的容量及使用情況
- jstat -gccapacity $pid
- #顯示新生代容量及使用情況
- jstat -gcnewcapacity $pid
- #顯示老年代容量
- jstat -gcoldcapacity $pid
- #顯示垃圾收集信息(間隔1秒持續(xù)輸出)
- jstat -gcutil $pid 1000
10)其他的一些日常命令
- # 快速殺死所有的 java 進程
- ps aux | grep java | awk '{ print $2 }' | xargs kill -9
- # 查找/目錄下占用磁盤空間最大的top10文件
- find / -type f -print0 | xargs -0 du -h | sort -rh | head -n 10
6. 總結
性能優(yōu)化是一個很大的領域,這里面的每一個小點,都可以拓展為數(shù)十篇文章去闡述。對應用進行性能優(yōu)化,除了上面介紹的之外,還有前端優(yōu)化、架構優(yōu)化(分布式、緩存使用等)、數(shù)據存儲優(yōu)化、代碼優(yōu)化(如設計模式優(yōu)化)等,限于篇幅所限,在這里并未一一展開,本文的這些內容,只是起一個拋磚引玉的作用。同時,本文的東西是我的一些經驗和知識,并不一定全對,希望大家指正和補充。
性能優(yōu)化是一個綜合性的工作,需要不斷地去實踐,將工具學習、經驗學習融合到實戰(zhàn)中去,不斷完善,形成一套屬于自己的調優(yōu)方法論。
此外,雖然性能優(yōu)化很重要,但是不要過早在優(yōu)化上投入太多精力(當然完善的架構設計和編碼是必要的),過早優(yōu)化是萬惡之源。一方面,提前做的優(yōu)化工作,可能會不適用快速變化的業(yè)務需求,反倒給新需求、新功能起了阻礙的作用;另一方面,過早優(yōu)化使得應用復雜性升高,降低了應用的可維護性。何時進行優(yōu)化、優(yōu)化到什么樣的程度,是一個需要多方權衡的命題。
參考資料:[1]https://github.com/superhj1987/awesome-scripts?[2]https://github.com/jvm-profiling-tools/perf-map-agent?
[3]https://github.com/brendangregg/FlameGraph?
[4] https://github.com/apangin/jstackmem/blob/master/jstackmem.py
【本文為51CTO專欄作者“阿里巴巴官方技術”原創(chuàng)稿件,轉載請聯(lián)系原作者】