JVM調優的本質:
並不是顯著的提高系統性能,不是說你調了,性能就能提升幾倍或者上十倍,JVM調優,主要調的是穩定。如果你的系統出現了頻繁的垃圾回收,這個時候系統是不穩定的,所以需要我們來進行JVM調優,調整垃圾回收的頻次。
GC調優原則
調優的原則
1、 大多數的java應用不需要GC調優
2、 大部分需要GC調優的的,不是參數問題,是代碼問題
3、 在實際使用中,分析GC情況優化代碼比優化GC參數要多得多;
4、 GC調優是最后的手段
目的
GC的時間夠小
GC的次數夠少
發生Full GC的周期足夠的長,時間合理,最好是不發生。
注:如果滿足下面的指標,則一般不需要進行GC:
Minor GC執行時間不到50ms;
Minor GC執行不頻繁,約10秒一次;
Full GC執行時間不到1s;
Full GC執行頻率不算頻繁,不低於10分鍾1次;
GC調優
調優步驟
日志分析
1,監控GC的狀態
使用各種JVM工具,查看當前日志,分析當前JVM參數設置,並且分析當前堆內存快照和gc日志,根據實際的各區域內存划分和GC執行時間,覺得是否進行優化;
2,分析結果,判斷是否需要優化
如果各項參數設置合理,系統沒有超時日志出現,GC頻率不高,GC耗時不高,那么沒有必要進行GC優化;如果GC時間超過1-3秒,或者頻繁GC,則必須優化;
3,調整GC類型和內存分配
如果內存分配過大或過小,或者采用的GC收集器比較慢,則應該優先調整這些參數,並且先找1台或幾台機器進行beta,然后比較優化過的機器和沒有優化的機器的性能對比,並有針對性的做出最后選擇;
4,不斷的分析和調整
通過不斷的試驗和試錯,分析並找到最合適的參數
5,全面應用參數
如果找到了最合適的參數,則將這些參數應用到所有服務器,並進行后續跟蹤。
閱讀GC日志
主要關注MinorGC和FullGC 的回收效率(回收前大小和回收比較)、回收的時間。
-XX:+UseSerialGC
以參數-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
收集器參數變為-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),
-XX:+UseConcMarkSweepGC
CMS收集器和G1收集器會有明顯的相關字樣
-XX:+UseG1GC
GC調優實戰
項目啟動GC優化
1、 開啟日志分析 -XX:+PrintGCDetails 發現有多次GC包括FullGC
2、 調整Metadata空間 -XX:MetaspaceSize=64m 減少FullGC
3、 減少Minor gc次數,增加參數 -Xms500m GC減少至4次
4、 減少Minor gc次數,調整參數 -Xms1000m GC減少至2次
5、 增加新生代比重,增加參數 -Xmn900m GC減少至1次
6、 加大新生代,調整參數 -Xms2000m -Xmn1800m 還是避免不了GC,沒有必要調整這么大,資源浪費
項目運行GC優化
使用jmeter同時訪問三個接口,index、time、noblemetal
使用40個線程,循環2500次進行壓力測試,觀察並發的變化
jmeter的聚合報告的參數解釋:
1、使用單線程GC -XX:+UseSerialGC
2、使用多線程GC -XX:+UseParNewGC
多線程的吞吐量有一定的上升
3、使用CMS -XX:+UseConcMarkSweepGC
CMS采用了並發收集,所以STW的時間較小,吞吐量較單線程有一定提高,最大請求時間MAX有明顯的下降。
4、使用G1 -XX:+UseG1GC
G1這里的吞吐量是最大的,最大請求時間MAX有明顯的下降。
一行代碼導致頻繁GC,吞吐量下降很快
推薦策略
- 新生代大小選擇
- 響應時間優先的應用:盡可能設大,直到接近系統的最低響應時間限制(根據實際情況選擇).在此種情況下,新生代收集發生的頻率也是最小的.同時,減少到達老年代的對象.
- 吞吐量優先的應用:盡可能的設置大,可能到達Gbit的程度.因為對響應時間沒有要求,垃圾收集可以並行進行,一般適合8CPU以上的應用.
- 避免設置過小.當新生代設置過小時會導致:1.MinorGC次數更加頻繁 2.可能導致MinorGC對象直接進入老年代,如果此時老年代滿了,會觸發FullGC.
- 2. 老年代大小選擇
響應時間優先的應用:老年代使用並發收集器,所以其大小需要小心設置,一般要考慮並發會話率和會話持續時間等一些參數.如果堆設置小了,可以會造成內存碎 片,高回收頻率以及應用暫停而使用傳統的標記清除方式;
如果堆大了,則需要較長的收集時間.最優化的方案,一般需要參考以下數據獲得:
並發垃圾收集信息、持久代並發收集次數、傳統GC信息、花在新生代和老年代回收上的時間比例。
吞吐量優先的應用:一般吞吐量優先的應用都有一個很大的新生代和一個較小的老年代.原因是,這樣可以盡可能回收掉大部分短期對象,減少中期的對象,而老年代盡存放長期存活對象
GC調優是個很復雜、很細致的過程,要根據實際情況調整,不同的機器、不同的應用、不同的性能要求調優的手段都是不同的,king老師也無法告訴大家全部,即使是jvm參數也是如此,比如說性能有關的操作系統工具,和操作系統本身相關的所謂大頁機制,都需要大家平時去積累,去觀察,去實踐,king老師在這個專題上告訴大家的除了各種java虛擬機基礎知識和內部原理,也告訴大家一個性能優化的一個基本思路和着手的方向。
逃逸分析
是JVM所做的最激進的優化,最好不要調整相關的參數。
n 牽涉到的JVM參數:
-XX:+DoEscapeAnalysis:啟用逃逸分析(默認打開)
-XX:+EliminateAllocations:標量替換(默認打開)
-XX:+UseTLAB 本地線程分配緩沖(默認打開)
如果是逃逸分析出來的對象可以在棧上分配的話,那么該對象的生命周期就跟隨線程了,就不需要垃圾回收,如果是頻繁的調用此方法則可以得到很大的性能提高。
采用了逃逸分析--對象在棧上分配:
沒有逃逸分析---對象都在堆上分配(觸發頻次GC,加重負擔):
常用的性能評價/測試指標
一個web應用不是一個孤立的個體,它是一個系統的部分,系統中的每一部分都會影響整個系統的性能
響應時間
提交請求和返回該請求的響應之間使用的時間,一般比較關注平均響應時間。
常用操作的響應時間列表:
操作 |
響應時間 |
打開一個站點 |
幾秒 |
數據庫查詢一條記錄(有索引) |
十幾毫秒 |
機械磁盤一次尋址定位 |
4毫秒 |
從機械磁盤順序讀取1M數據 |
2毫秒 |
從SSD磁盤順序讀取1M數據 |
0.3毫秒 |
從遠程分布式換成Redis讀取一個數據 |
0.5毫秒 |
從內存讀取1M數據 |
十幾微妙 |
Java程序本地方法調用 |
幾微妙 |
網絡傳輸2Kb數據 |
1微妙 |
並發數
同一時刻,對服務器有實際交互的請求數。
和網站在線用戶數的關聯:1000個同時在線用戶數,可以估計並發數在5%到15%之間,也就是同時並發數在50~150之間。
吞吐量
對單位時間內完成的工作量(請求)的量度
關系
系統吞吐量和系統並發數以及響應時間的關系:
理解為高速公路的通行狀況:
吞吐量是每天通過收費站的車輛數目(可以換算成收費站收取的高速費),
並發數是高速公路上的正在行駛的車輛數目,
響應時間是車速。
車輛很少時,車速很快。但是收到的高速費也相應較少;隨着高速公路上車輛數目的增多,車速略受影響,但是收到的高速費增加很快;
隨着車輛的繼續增加,車速變得越來越慢,高速公路越來越堵,收費不增反降;
如果車流量繼續增加,超過某個極限后,任務偶然因素都會導致高速全部癱瘓,車走不動,當然后也收不着,而高速公路成了停車場(資源耗盡)。
常用的性能優化手段
避免過早優化
不應該把大量的時間耗費在小的性能改進上,過早考慮優化是所有噩夢的根源。
所以,我們應該編寫清晰,直接,易讀和易理解的代碼,真正的優化應該留到以后,等到性能分析表明優化措施有巨大的收益時再進行。
但是過早優化,不表示我們就可以隨便寫代碼,還是需要注重編寫高效優雅的代碼。
進行系統性能測試
所有的性能調優,都有應該建立在性能測試的基礎上,直覺很重要,但是要用數據說話,可以推測,但是要通過測試求證。
尋找系統瓶頸,分而治之,逐步優化
性能測試后,對整個請求經歷的各個環節進行分析,排查出現性能瓶頸的地方,定位問題,分析影響性能的的主要因素是什么?內存、磁盤IO、網絡、CPU,還是代碼問題?架構設計不足?或者確實是系統資源不足?
前端優化常用手段
瀏覽器/App
減少請求數
合並CSS,Js,圖片,
生產服務器提供的all的js文件
http中的keep-alive(http1.1中默認開啟)包括nginx
使用客戶端緩沖
靜態資源文件(css、圖標等)緩存在瀏覽器中,有關的屬性Cache-Control(相對時間)和Expires
如果文件發生了變化,需要更新,則通過改變文件名來解決。
啟用壓縮
瀏覽器(zip),壓縮率80%以上。
減少網絡傳輸量,但會給瀏覽器和服務器帶來性能的壓力,需要權衡使用。
資源文件加載順序
css放在頁面最上面,js放在最下面。這樣頁面的體驗才會比較好。
瀏覽器會加載完CSS才會對頁面進行渲染
JS只要加載后就會立刻執行。(有些JS可能執行時間比較長)
減少Cookie傳輸
cookie包含在每次的請求和響應中,因此哪些數據寫入cookie需要慎重考慮(靜態資源不需要放入cookie)
友好的提示(非技術手段)
有時候在前端給用戶一個提示,就能收到良好的效果。畢竟用戶需要的是不要不理他。
CDN加速
CDN,又稱內容分發網絡,本質是一個緩存,而且是將數據緩存在用戶最近的地方。無法自行實現CDN的時候,可以根據經濟實力考慮商用CDN服務。
反向代理緩存
將靜態資源文件緩存在反向代理服務器上,一般是Nginx。
WEB組件分離
將js,css和圖片文件放在不同的域名下。可以提高瀏覽器在下載web組件的並發數。因為瀏覽器在下載同一個域名的的數據存在並發數限制。
應用服務性能優化
緩存
網站性能優化第一定律:優先考慮使用緩存優化性能
優先原則:緩存離用戶越近越好
緩存的基本原理和本質
緩存是將數據存在訪問速度較高的介質中。可以減少數據訪問的時間,同時避免重復計算。
合理使用緩存的准則
頻繁修改的數據,盡量不要緩存,讀寫比2:1以上才有緩存的價值。
緩存一定是熱點數據。
應用需要容忍一定時間的數據不一致。
緩存可用性問題,一般通過熱備或者集群來解決。
分布式緩存與一致性哈希
以集群的方式提供緩存服務,有兩種實現;
1、需要更新同步的分布式緩存,所有的服務器保存相同的緩存數據,帶來的問題就是,緩存的數據量受限制,其次,數據要在所有的機器上同步,代價很大。
2、每台機器只緩存一部分數據,然后通過一定的算法選擇緩存服務器。常見的余數hash算法存在當有服務器上下線的時候,大量緩存數據重建的問題。所以提出了一致性哈希算法。
一致性哈希:
1. 首先求出服務器(節點)的哈希值,並將其配置到0~2的32次方的圓(continuum)上。
2. 然后采用同樣的方法求出存儲數據的鍵的哈希值,並映射到相同的圓上。
3. 然后從數據映射到的位置開始順時針查找,將數據保存到找到的第一個服務器上。如果超過232仍然找不到服務器,就會保存到第一台服務器上。
一致性哈希算法對於節點的增減都只需重定位環空間中的一小部分數據,具有較好的容錯性和可擴展性。
數據傾斜:
一致性哈希算法在服務節點太少時,容易因為節點分部不均勻而造成數據傾斜問題,此時必然造成大量數據集中到Node A上,而只有極少量會定位到Node B上。為了解決這種數據傾斜問題,一致性哈希算法引入了虛擬節點機制,即對每一個服務節點計算多個哈希,每個計算結果位置都放置一個此服務節點,稱為虛擬節點。具體做法可以在服務器ip或主機名的后面增加編號來實現。例如,可以為每台服務器計算三個虛擬節點,於是可以分別計算 “Node A#1”、“Node A#2”、“Node A#3”、“Node B#1”、“Node B#2”、“Node B#3”的哈希值,於是形成六個虛擬節點:同時數據定位算法不變,只是多了一步虛擬節點到實際節點的映射,例如定位到“Node A#1”、“Node A#2”、“Node A#3”三個虛擬節點的數據均定位到Node A上。這樣就解決了服務節點少時數據傾斜的問題。在實際應用中,通常將虛擬節點數設置為32甚至更大,因此即使很少的服務節點也能做到相對均勻的數據分布。
集群
可以很好的將用戶的請求分配到多個機器處理,對總體性能有很大的提升
異步
同步和異步,阻塞和非阻塞
同步和異步關注的是結果消息的通信機制
同步:同步的意思就是調用方需要主動等待結果的返回
異步:異步的意思就是不需要主動等待結果的返回,而是通過其他手段比如,狀態通知,回調函數等。
阻塞和非阻塞主要關注的是等待結果返回調用方的狀態
阻塞:是指結果返回之前,當前線程被掛起,不做任何事
非阻塞:是指結果在返回之前,線程可以做一些其他事,不會被掛起。
1.同步阻塞:同步阻塞基本也是編程中最常見的模型,打個比方你去商店買衣服,你去了之后發現衣服賣完了,那你就在店里面一直等,期間不做任何事(包括看手機),等着商家進貨,直到有貨為止,這個效率很低。jdk里的BIO就屬於 同步阻塞
2.同步非阻塞:同步非阻塞在編程中可以抽象為一個輪詢模式,你去了商店之后,發現衣服賣完了,這個時候不需要傻傻的等着,你可以去其他地方比如奶茶店,買杯水,但是你還是需要時不時的去商店問老板新衣服到了嗎。jdk里的NIO就屬於 同步非阻塞
3.異步阻塞:異步阻塞這個編程里面用的較少,有點類似你寫了個線程池,submit然后馬上future.get(),這樣線程其實還是掛起的。有點像你去商店買衣服,這個時候發現衣服沒有了,這個時候你就給老板留給電話,說衣服到了就給我打電話,然后你就守着這個電話,一直等着他響什么事也不做。這樣感覺的確有點傻,所以這個模式用得比較少。
4.異步非阻塞:好比你去商店買衣服,衣服沒了,你只需要給老板說這是我的電話,衣服到了就打。然后你就隨心所欲的去玩,也不用操心衣服什么時候到,衣服一到,電話一響就可以去買衣服了。jdk里的AIO就屬於異步
常見異步的手段
Servlet異步
servlet3中才有,支持的web容器在tomcat7和jetty8以后。
多線程
消息隊列
程序
代碼級別
一個應用的性能歸根結底取決於代碼是如何編寫的。
選擇合適的數據結構
選擇ArrayList和LinkedList對我們的程序性能影響很大,為什么?因為ArrayList內部是數組實現,存在着不停的擴容和數據復制。
選擇更優的算法
舉個例子,如何判斷一個數是否為n的多少次方
*類說明:選擇更優的算法
*/
public class BetterAlg {
//如何判斷一個數是否為n的多少次方
public static void main(String[] args) throws Exception{
int n =2;
Scanner scanner=new Scanner(System.in);
while(scanner.hasNext()){//控制台輸入
int input =scanner.nextInt();
while (true){
if(input ==n){
System.out.println("是("+n+")的次方");
break;
}
if(input%2 !=0){
System.out.println("不是("+n+")的次方");
break;
}else{
input = input/2;
}
}
// if((input&(input-1)) ==0){
// System.out.println("是("+n+")的次方");
// }else{
// System.out.println("不是("+n+")的次方");
// }
}
}
編寫更少的代碼
同樣正確的程序,小程序比大程序要快,這點無關乎編程語言。
並發編程
資源的復用
目的是減少開銷很大的系統資源的創建和銷毀,比如數據庫連接,網絡通信連接,線程資源等等。
單例模式
Spring中的bean
池化技術
存儲性能優化
盡量使用SSD
定時清理數據或者按數據的性質分開存放
結果集處理
用setFetchSize控制jdbc每次從數據庫中返回多少數據。
總結:
調優是個很復雜、很細致的過程,要根據實際情況調整,不同的機器、不同的應用、不同的性能要求調優的手段都是不同的。也沒有一個放之四海而皆准的配置或者公式。即使是jvm參數也是如此,再比如說性能有關的操作系統工具,和操作系統本身相關的所謂大頁機制,都需要大家平時去積累,去觀察,去實踐。
在這個專題上告訴大家的除了各種java虛擬機基礎知識、內部原理,也告訴大家一個性能優化的一個基本思路和着手的方向。