JVM調優不知道怎麼回答,阿里總結四大模塊,學不會就背過來

一個 web 應用不是一個孤立的個體,它是一個系統的部分,系統中的每一部分都會影響整個系統的性能,而併發量就是這個系統最重要的組成部分之一,它最大程度的影響着用戶體驗度,就像是一條高速公路,在這條高速上奔跑的汽車最關心的不就是這條高速是否會堵車啊,所以在高速設計(系統開發)的時候就要着手考慮這件事,尤其是現在的生活中,很多的朋友在面試的時候也經常被問到一個問題:JVM調優,那不清楚應該怎麼處理怎麼辦,沒關係,我來了,看完這篇文章,哪怕你從來沒有調優經驗也可以和麪試官扯皮

話不多說,看重點

個人公衆號:Java架構師聯盟,每日更新技術好文

1. 常用的性能評價/測試指標

在調優之前,起碼你要清楚你再進行調優的時候都要有哪些關注點吧,知己知彼才能百戰不殆啊,那我們就來看一下都有哪些常用的性能測試指標

1.1 響應時間

提交請求和返回該請求的響應之間使用的時間,一般比較關注平均響應時間。 常用操作的響應時間列表:

1.2 併發數

同一時刻,對服務器有實際交互的請求數。

和網站在線用戶數的關聯:1000 個同時在線用戶數,可以估計併發數在 5%到 15%之間, 也就是同時併發數在 50~150 之間。

1.3 吞吐量

對單位時間內完成的工作量(請求)的量度

1.4 關係

系統吞吐量和系統併發數以及響應時間的關係:

以高速公路的通行狀況:

吞吐量是每天通過收費站的車輛數目(可以換算成收費站收取的高速費), 併發數是高速公路上的正在行駛的車輛數目,響應時間是車速。

車輛很少時,車速很快。但是收到的高速費也相應較少;隨着高速公路上車輛數目的增多, 車速略受影響,但是收到的高速費增加很快;

隨着車輛的繼續增加,車速變得越來越慢,高速公路越來越堵,收費不增反降;

如果車流量繼續增加,超過某個極限後,任何偶然因素都會導致高速全部癱瘓,車走不動, 當然後也收不着,而高速公路成了停車場(資源耗盡)。

2. 常用的性能優化手段

在知道了常用的性能優化測評的指標之後,那我們看一下平時的時候都會有哪些相應的優化手段,這樣,兩方進行結合,就是我們的最佳的優化手段

2.1 避免過早優化

不應該把大量的時間耗費在小的性能改進上,過早考慮優化是所有噩夢的根源。

所以,我們應該編寫清晰,直接,易讀和易理解的代碼,真正的優化應該留到以後,等到性能分析表明優化措施有巨大的收益時再進行。

但是過早優化,不表示我們應該編寫已經知道的對性能不好的的代碼結構。

2.2 進行系統性能測試

所有的性能調優,都有應該建立在性能測試的基礎上,直覺很重要,但是要用數據說話,可 以推測,但是要通過測試求證。

2.3 尋找系統瓶頸,分而治之,逐步優化

性能測試後,對整個請求經歷的各個環節進行分析,排查出現性能瓶頸的地方,定位問題, 分析影響性能的的主要因素是什麼?內存、磁盤 IO、網絡、CPU,還是代碼問題?架構設計不足?或者確實是系統資源不足?

2.4 前端優化常用手段

2.4.1 瀏覽器/App

1、減少請求數;

2、合併 CSS,Js,圖片使用客戶端緩衝;

3、靜態資源文件緩存在瀏覽器中,有關的屬性 Cache-Control 和 Expires

4、如果文件發生了變化,需要更新,則通過改變文件名來解決。 啓用壓縮

5、減少網絡傳輸量,但會給瀏覽器和服務器帶來性能的壓力,需要權衡使用。 資源文件加載順序

6、css 放在頁面最上面,js 放在最下面減少 Cookie 傳輸

7、cookie 包含在每次的請求和響應中,因此哪些數據寫入 cookie 需要慎重考慮給用戶一個提示,有時候在前端給用戶一個提示,就能收到良好的效果。畢竟用戶需要的是不要不理他。

2.4.2 CDN 加速

CDN,又稱內容分發網絡,本質仍然是一個緩存,而且是將數據緩存在用戶最近的地方。 無法自行實現 CDN 的時候,可以考慮商用 CDN 服務。

2.4.3 反向代理緩存

將靜態資源文件緩存在反向代理服務器上,一般是 Nginx。

2.4.4 WEB 組件分離

將 js,css 和圖片文件放在不同的域名下。可以提高瀏覽器在下載 web 組件的併發數。因爲瀏覽器在下載同一個域名的的數據存在併發數限制。****

3 應用服務性能優化

3.1 緩存

網站性能優化第一定律:優先考慮使用緩存優化性能

Mark 老師的推論:緩存離用戶越近越好

3.1.1 緩存的基本原理和本質

緩存是將數據存在訪問速度較高的介質中。可以減少數據訪問的時間,同時避免重複計算。

3.1.2 合理使用緩衝的準則

頻繁修改的數據,儘量不要緩存,讀寫比 2:1 以上纔有緩存的價值。緩存一定是熱點數據。

應用需要容忍一定時間的數據不一致。

緩存可用性問題,一般通過設備或者集羣來解決。

緩存預熱,新啓動的緩存系統沒有任何數據,可以考慮將一些熱點數據提前加載到緩存系統。

解決緩存擊穿:

1、布隆過濾器

2、把不存在的數據也緩存起來 ,比如有請求總是訪問 key = 23 的數據,但是這個key = 23 的數據在系統中不存在,可以考慮在緩存中構建一個( key=23 value= null)的數據。

3.1.3 分佈式緩存與一致性哈希

以集羣的方式提供緩存服務,有兩種實現;

1、需要更新同步的分佈式緩存,所有的服務器保存相同的緩存數據,帶來的問題就是,緩 存的數據量受限制,其次,數據要在所有的機器上同步,代價很大。

2、每臺機器只緩存一部分數據,然後通過一定的算法選擇緩存服務器。常見的餘數 hash 算法存在當有服務器上下線的時候,大量緩存數據重建的問題。所以提出了一致性哈希算法。

一致性哈希:

\1. 首先求出服務器(節點)的哈希值,並將其配置到 0~232 的圓(continuum)上。

\2. 然後採用同樣的方法求出存儲數據的鍵的哈希值,並映射到相同的圓上。

\3. 然後從數據映射到的位置開始順時針查找,將數據保存到找到的第一個服務器上。如果超過232仍然找不到服務器,就會保存到第一臺服務器上。

一致性哈希算法對於節點的增減都只需重定位環空間中的一小部分數據,具有較好的容錯性 和可擴展性。

注意:一致性哈希算法在服務節點太少時,容易因爲節點分佈不均勻而造成數據傾斜問題,此時必然造成大量數據集中到 Node A 上,而只有極少量會定位到 Node B 上。爲了解決這種數據傾斜問題,一致性哈希算法引入了虛擬節點機制,即對每一個服務節點計算多個哈希,每個 計算結果位置都放置一個此服務節點,稱爲虛擬節點。具體做法可以在服務器 ip 或主機名的後面增加編號來實現。

3.2 異步

3.2.1 同步和異步,阻塞和非阻塞

同步和異步關注的是結果消息的通信機制

同步:同步的意思就是調用方需要主動等待結果的返回

異步:異步的意思就是不需要主動等待結果的返回,而是通過其他手段比如,狀態通知,回   調函數等。

阻塞和非阻塞主要關注的是等待結果返回調用方的狀態阻塞:是指結果返回之前,當前線程被掛起,不做任何事

非阻塞:是指結果在返回之前,線程可以做一些其他事,不會被掛起。

\1. 同步阻塞:同步阻塞基本也是編程中最常見的模型,打個比方你去商店買衣服,你去了之 後發現衣服賣完了,那你就在店裏面一直等,期間不做任何事(包括看手機),等着商家進貨, 直到有貨爲止,這個效率很低。jdk 裏的 BIO 就屬於 同步阻塞

\2. 同步非阻塞:同步非阻塞在編程中可以抽象爲一個輪詢模式,你去了商店之後,發現衣服賣完了,這個時候不需要傻傻的等着,你可以去其他地方比如奶茶店,買杯水,但是你還是 需要時不時的去商店問老闆新衣服到了嗎。jdk 裏的 NIO 就屬於 同步非阻塞

\3. 異步阻塞:異步阻塞這個編程裏面用的較少,有點類似你寫了個線程池,submit 然後馬上future.get(),這樣線程其實還是掛起的。有點像你去商店買衣服,這個時候發現衣服沒有了, 這個時候你就給老闆留給電話,說衣服到了就給我打電話,然後你就守着這個電話,一直等着他響什麼事也不做。這樣感覺的確有點傻,所以這個模式用得比較少。

\4. 異步非阻塞:好比你去商店買衣服,衣服沒了,你只需要給老闆說這是我的電話,衣服到了就打。然後你就隨心所欲的去玩,也不用操心衣服什麼時候到,衣服一到,電話一響就可 以去買衣服了。jdk 裏的 AIO 就屬於異步

3.2.2 常見異步的手段

Servlet 異步、多線程、消息隊列

注意:servlet3 中才有,支持的 web 容器在 tomcat7 和 jetty8 以後。

3.3 集羣

可以很好的將用戶的請求分配到多個機器處理,對總體性能有很大的提升

3.4 應用相關

3.4.1 代碼級別

一個應用的性能歸根結底取決於代碼是如何編寫的。

選擇合適的數據結構

選擇 ArrayList 和 LinkedList 對我們的程序性能影響很大,爲什麼?因爲 ArrayList 內部是數組實現,存在着不停的擴容和數據複製。

選擇更優的算法

舉個例子,最大子列和問題:

給定一個整數序列,a0, a1, a2, …… , an(項可以爲負數),求其中最大的子序列和。如果所有整數都是負數,那麼最大子序列和爲 0;

例如(a[1],a[2],a[3],a[4],a[5],a[6])=(-2,11,-4,13,-5,-2)時,

最大子段和爲 20,子段爲 a[2],a[3],a[4]。

最壞的算法:窮舉法,所需要的的計算時間是 O(n^3).

一般的算法:分治法的計算時間複雜度爲 O(nlogn).

最好的算法:最大子段和的動態規劃算法,計算時間複雜度爲 O(n)

n 越大,時間就相差越大,比如 10000 個元素,最壞的算法和最好的算法之間的差距絕非多線程或者集羣化能輕鬆解決的。

編寫更少的代碼

同樣正確的程序,小程序比大程序要快,這點無關乎編程語言。

3.4.2 併發編程

充分利用 CPU 多核,

實現線程安全的類,避免線程安全問題同步下減少鎖的競爭

3.4.3 資源的複用

目的是減少開銷很大的系統資源的創建和銷燬,比如數據庫連接,網絡通信連接,線程資源 等等。類似單例模式、池化技術都可以

3.4.4 JVM

與 JIT 編譯器相關的優化

選擇編譯器是運行 java 程序首先要做的選擇之一

熱點編譯的概念

對於程序來說,通常只有一部分代碼被經常執行,這些關鍵代碼被稱爲應用的熱點,執行的越多就認爲是越熱。將這些代碼編譯爲本地機器特定的二進制碼,可以有效提高應用性能。

選擇編譯器類型

# -server

更晚編譯,但是編譯後的優化更多,性能更高

# -client:

很早就開始編譯

# -XX:+TieredCompilation:

開啓分層編譯,可以讓 jvm 在啓動時啓用 client 編譯,隨着代碼變熱後再轉爲 server 編譯。

缺省編譯器取決於機器位數、操作系統和 CPU 數目。32 位的機器上,一般默認都是 client

編譯,64 位機器上一般都是 server 編譯,多核機器一般是 server 編譯。

# -Xint

表示禁用 JIT,所有字節碼都被解釋執行,這個模式的速度最慢的。

# -Xcomp

表示所有字節碼都首先被編譯成本地代碼,然後再執行。

# -Xmixed

默認模式,讓 JIT 根據程序運行的情況,有選擇地將某些代碼編譯成本地代碼。

# -Xcomp 和-Xmixed

到底誰的速度快,針對不同的程序可能有不同的結果,基本還是推薦用默認模式。

代碼緩存相關

在編譯後,會有一個代碼緩存保存編譯後的代碼,一旦這個緩存滿了,jvm 將無法繼續編譯代碼。

當 jvm 提示: CodeCache is full,就表示需要增加代碼緩存大小。

# –XX:ReservedCodeCacheSize=N 可以用來調整這個大小。

編譯閾值

代碼是否進行編譯,取決於代碼執行的頻度,是否到達編譯閾值。 計數器有兩種:方法調用計數器和方法裏的循環回邊計數器

一個方法是否達到編譯閾值取決於方法中的兩種計數器之和。編譯閾值調整的參數爲:

#-XX:CompileThreshold=N

這個地方看一下了解一下就好,據我所知很少有會問到這一步的

方法調用計數器

統計的並不是方法被調用的絕對次數,而是一個相對的執行頻率,即一段時間之內方法被調用的次數。當超過一定的時間限度,如果方法的調用次數仍然不足以讓它提交給即時編譯器編譯,那這個方法的調用計數器就會被減少一半,這個過程稱爲方法調用計數器熱度的衰減(Counter Decay),而這段時間就稱爲此方法統計的半衰週期(Counter Half Life Time)。進行熱度衰減的動作是在虛擬機進行垃圾收集時順便進行的,可以使用虛擬機參數-XX:-UseCounterDecay 來關閉熱度衰減,讓方法計數器統計方法調用的絕對次數, 這樣,只要系統運行時間足夠長,絕大部分方法都會被編譯成本地代碼。另外,可以使用-XX: CounterHalfLifeTime 參數設置半衰週期的時間,單位是秒。

回邊計數器沒有計數熱度衰減的過程,因此這個計數器統計的就是該方 法循環執行的絕對次數。

編譯線程

進行代碼編譯的時候,是採用多線程進行編譯的。

方法內聯

內聯默認開啓,-XX:-Inline,可以關閉,但是不要關閉,一旦關閉對性能有巨大影響。 方法是否內聯取決於方法有多熱和方法的大小,

很熱的方法如果方法字節碼小於 325 字節纔會內聯,這個大小有參數

-XX:MaxFreqInlinesSzie=N 調整,但是這個很熱與熱點編譯不同,沒有任何參數可以調整熱度。

方法小於 35 個字節碼,一定會內聯,這個大小可以通過參數-XX:MaxInlinesSzie=N 調整。

逃逸分析

是 JVM 所做的最激進的優化,最好不要調整相關的參數。

3.4.5 GC 調優目的

GC 的時間夠小

GC 的次數夠少

發生 Full GC 的週期足夠的長,時間合理,最好是不發生。

調優的原則和步驟

\1. 大多數的 java 應用不需要 GC 調優

\2. 大部分需要 GC 調優的的,不是參數問題,是代碼問題

\3. 在實際使用中,分析 GC 情況優化代碼比優化 GC 參數要多得多;

\4. GC 調優是最後的手段

GC 調優的最重要的三個選項:

第一位:選擇合適的 GC 回收器

第二位:選擇合適的堆大小

第三位:選擇年輕代在堆中的比重

步驟

1,監控 GC 的狀態

使用各種 JVM 工具,查看當前日誌,分析當前 JVM 參數設置,並且分析當前堆內存快照和

gc 日誌,根據實際的各區域內存劃分和 GC 執行時間,覺得是否進行優化;

2,分析結果,判斷是否需要優化

如果各項參數設置合理,系統沒有超時日誌出現,GC 頻率不高,GC 耗時不高,那麼沒有必要進行 GC 優化;如果 GC 時間超過 1-3 秒,或者頻繁 GC,則必須優化;

注:如果滿足下面的指標,則一般不需要進行 GC:

Minor GC 執 行 時 見 不 到 50ms; Minor GC 執行不頻繁,約 10 秒一次; Full GC 執行時間不到 1s;

Full GC 執行頻率不算頻繁,不低於 10 分鐘 1 次;

3,調整 GC 類型和內存分配

如果內存分配過大或過小,或者採用的 GC 收集器比較慢,則應該優先調整這些參數,並且先找 1 臺或幾臺機器進行 beta,然後比較優化過的機器和沒有優化的機器的性能對比,並有針對性的做出最後選擇;

4,不斷的分析和調整

通過不斷的試驗和試錯,分析並找到最合適的參數

5,全面應用參數

如果找到了最合適的參數,則將這些參數應用到所有服務器,並進行後續跟蹤。

# 學會閱讀 GC 日誌

//以參數-Xms5m -Xmx5m -XX:+PrintGCDetails -XX:+UseSerialGC 爲例:

[DefNew: 1855K->1855K(1856K), 0.0000148 secs][Tenured: 2815K->4095K(4096K), 0.0134819 secs] 4671K

DefNew 指明瞭收集器類型,而且說明了收集發生在新生代。

1855K->1855K(1856K)表示,回收前 新生代佔用 1855K,回收後佔用 1855K,新生代大小

1856K。

0.0000148 secs 表明新生代回收耗時。

Tenured 表明收集發生在老年代

2815K->4095K(4096K), 0.0134819 secs:含義同新生代

最後的 4671K 指明堆的大小。

收集器參數變爲-XX:+UseParNewGC,日誌變爲:

[ParNew: 1856K->1856K(1856K), 0.0000107 secs][Tenured: 2890K->4095K(4096K), 0.0121148 secs]

收集器參數變爲-XX:+ UseParallelGC 或 UseParallelOldGC,日誌變爲: [PSYoungGen: 1024K->1022K(1536K)] [ParOldGen: 3783K->3782K(4096K)] 4807K->4804K(5632K),

CMS 收集器和 G1 收集器會有明顯的相關字樣

其他與 GC 相關的參數

#調試跟蹤之 打印簡單的 GC 信息 參數: -verbose:gc, -XX:+PrintGC

#打印詳細的 GC 信息 -XX:+PrintGCDetails, +XX:+PrintGCTimeStamps

#-Xlogger:logpath 設置 gc 的日誌庫,

如: -Xlogger:log/gc.log, 將 gc.log 的路徑設置到當前目錄的 log 目錄下.

應用場景: 將 gc 的日誌獨立寫入日誌文件,將 GC 日誌與系統業務日誌進行了分離,方便開發人員進行追蹤分析。

  #-XX:+PrintHeapAtGC, 打印推信息

參數設置: -XX:+PrintHeapAtGC

應用場景: 獲取 Heap 在每次垃圾回收前後的使用狀況

  #-XX:+TraceClassLoading

參數方法: -XX:+TraceClassLoading

應用場景: 在系統控制檯信息中看到 class 加載的過程和具體的 class 信息,可用以分析類的加載順序以及是否可進行精簡操作。

  #-XX:+DisableExplicitGC 禁止在運行期顯式地調用 System.gc()

   #-XX:-HeapDumpOnOutOfMemoryError 默認關閉,建議開啓,在java.lang.OutOfMemoryError 異常出現時,輸出一個 dump.core 文件,記錄當時的堆內存快照。

     #-XX:HeapDumpPath=./java_pid<pid>.hprof 默認是 java 進程啓動位置,用來設置堆內存快照的存儲文件路徑。

推薦策略

年輕代大小選擇

· 響應時間優先的應用:儘可能設大,直到接近系統的最低響應時間限制(根據實際情況選擇). 在此種情況下,年輕代收集發生的頻率也是最小的.同時,減少到達年老代的對象.

· 吞吐量優先的應用:儘可能的設置大,可能到達 Gbit 的程度.因爲對響應時間沒有要求,垃圾收集可以並行進行,一般適合 8CPU 以上的應用.

· 避免設置過小.當新生代設置過小時會導致:1.YGC 次數更加頻繁 2.可能導致 YGC 對象直接進入舊生代,如果此時舊生代滿了,會觸發 FGC.

年老代大小選擇

響應時間優先的應用:

年老代使用併發收集器,所以其大小需要小心設置,一般要考慮併發會話率和會話持續時間等一些參數.如果堆設置小了,可以會造成內存碎 片,高回收頻率以及應用暫停而使用傳統的標記清除方式;如果堆大了,則需要較長的收集時間.最優化的方案,一般需要參考以下數據獲得:

併發垃圾收集信息、持久代併發收集次數、傳統 GC 信息、花在年輕代和年老代回收上的時間比例。

吞吐量優先的應用:一般吞吐量優先的應用都有一個很大的年輕代和一個較小的年老代.原因是,這樣可以儘可能回收掉大部分短期對象,減少中期的對象,而年老代盡存放長期存活對 象

3.4.6 調優實戰不同的內存大小

不同的 GC 回收器

3.4.7 存儲性能優化

儘量使用 SSD

定時清理數據或者按數據的性質分開存放結果集處理

用 setFetchSize 控制 jdbc 每次從數據庫中返回多少數據。

總結

從個人理解來說,調優這個事情,除了在日常生活中,編碼的時候稍微注意一下,有一些回影響系統性能的編碼方式儘量別用之外,其餘的參數設置都是後話,需要自己去實踐總結,有點類似老中醫的態度,而最簡單粗暴的方法就是加機器唄,就像釘釘或者淘寶,資源不夠了,阿里雲兄弟先來撐一下場子,對吧,簡單粗暴解決了所有問題,不過一般公司都不會有這麼土豪

編程路上,任重而道遠,時代對程序員們提出了更高的要求 ,只有不斷提升自己,纔不會被淘汰,加油吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章