深入理解JVM - 系統(tǒng)性能優(yōu)化

系統(tǒng)性能優(yōu)化并不是一上來(lái)就是JVM優(yōu)化,相反JVM優(yōu)化幾乎是最后的手段了。影響一個(gè)系統(tǒng)的性能的因素非常多,如圖:

image.png

從服務(wù)本身來(lái)看,影響服務(wù)性能的主要包扣:

  • 我們寫(xiě)代碼時(shí)所選擇的數(shù)據(jù)結(jié)構(gòu)和算法
  • 服務(wù)開(kāi)啟的線程時(shí)是否合理
  • WEB應(yīng)用,WEB服務(wù)
  • JVM方面的影響
  • 最后是操作系統(tǒng)的影響

從整個(gè)服務(wù)架構(gòu)上來(lái)看還有:

  • 數(shù)據(jù)持久化
  • 服務(wù)間的遠(yuǎn)程調(diào)用
  • 消息緩存等中間件的選擇

常用的性能測(cè)試指標(biāo)

響應(yīng)時(shí)間

一個(gè)請(qǐng)求從提交到響應(yīng)所耗費(fèi)的時(shí)間,一般比較關(guān)注平均響應(yīng)時(shí)間,和最大響應(yīng)時(shí)間。

常用組件的響應(yīng)時(shí)間:

操作 響應(yīng)時(shí)間
打開(kāi)一個(gè)站點(diǎn) 幾秒
數(shù)據(jù)庫(kù)查詢一條記錄(有索引) 十幾毫秒
機(jī)械磁盤(pán)一次尋址定位 4毫秒
從機(jī)械磁盤(pán)順序讀取1M數(shù)據(jù) 2毫秒
從SSD磁盤(pán)順序讀取1M數(shù)據(jù) 0.3毫秒
從遠(yuǎn)程分布式緩存Redis讀取一個(gè)數(shù)據(jù) 0.5毫秒
從內(nèi)存讀取1M數(shù)據(jù) 十幾微妙
Java程序本地方法調(diào)用 幾微妙
網(wǎng)絡(luò)傳輸2Kb數(shù)據(jù) 1微妙

從上述表格我們能看出:

  1. 數(shù)據(jù)持久化,使用SSD與使用機(jī)械硬盤(pán)相比性能可以提高將近10倍;
  2. 數(shù)據(jù)查詢,數(shù)據(jù)如果直接在本地內(nèi)存,那么它的讀取效率比數(shù)據(jù)庫(kù)快將近1000倍,比redis快30倍左右;如果數(shù)據(jù)在redis緩存,那么它的讀取速度比數(shù)據(jù)庫(kù)快30倍左右;這也是為什么使用緩存是提升系統(tǒng)性能的“銀彈”的原因。

為監(jiān)控而生的多級(jí)緩存框架 layering-cache這是我開(kāi)源的一個(gè)多級(jí)緩存框架的實(shí)現(xiàn),如果有興趣可以看一下。

并發(fā)數(shù)

并發(fā)數(shù)是指同一時(shí)刻,對(duì)服務(wù)器有實(shí)際交互的請(qǐng)求數(shù)。一般并發(fā)數(shù)是在在線用戶數(shù)的5%-15%之間,如在線用戶數(shù)是1000,那么可以預(yù)估并發(fā)數(shù)在50-150之間。

吞吐量

吞吐量是單位時(shí)間內(nèi)完成的工作量(請(qǐng)求)的數(shù)量。如:每分鐘的數(shù)據(jù)庫(kù)事務(wù),每秒傳送的文件千字節(jié)數(shù),每分鐘的 Web 服務(wù)器命中數(shù)。

關(guān)系

通常,平均響應(yīng)時(shí)間越短,系統(tǒng)吞吐量越大;平均響應(yīng)時(shí)間越長(zhǎng),系統(tǒng)吞吐量越小。但是,系統(tǒng)吞吐量越大, 未必平均響應(yīng)時(shí)間越短。

性能優(yōu)化原則

避免過(guò)早優(yōu)化

不應(yīng)該把大量的時(shí)間耗費(fèi)在小的性能改進(jìn)上,過(guò)早考慮優(yōu)化是所有噩夢(mèng)的根源。在開(kāi)發(fā)初期我們的首要目標(biāo)是完成功能,編寫(xiě)清晰,直接,易懂的代碼。

進(jìn)行系統(tǒng)性能測(cè)試

所有調(diào)優(yōu)都應(yīng)該建立在新能測(cè)試的基礎(chǔ)上,不要滿目靠猜測(cè)進(jìn)行優(yōu)化。

尋找系統(tǒng)瓶頸,分而治之,逐步優(yōu)化

性能測(cè)試后,對(duì)整個(gè)請(qǐng)求經(jīng)歷的各個(gè)環(huán)節(jié)進(jìn)行分析,排查出現(xiàn)性能瓶頸的地方,定位問(wèn)題,分析影響性能的的主要因素是什么??jī)?nèi)存、磁盤(pán)IO、網(wǎng)絡(luò)、CPU,還是代碼問(wèn)題?架構(gòu)設(shè)計(jì)不足?或者確實(shí)是系統(tǒng)資源不足?

常用的性能優(yōu)化手段

高并發(fā)優(yōu)化

提高系統(tǒng)并發(fā)能力的方式,方法論上主要有兩種:垂直擴(kuò)展(Scale Up)與水平擴(kuò)展(Scale Out)。

垂直擴(kuò)展

提升單機(jī)處理能力。垂直擴(kuò)展的方式又有兩種:

  1. 增強(qiáng)單機(jī)硬件性能,例如:增加CPU核數(shù)如32核,升級(jí)更好的網(wǎng)卡如萬(wàn)兆,升級(jí)更好的硬盤(pán)如SSD,擴(kuò)充硬盤(pán)容量如2T,擴(kuò)充系統(tǒng)內(nèi)存如128G;
  2. 提升單機(jī)架構(gòu)性能,例如:使用Cache來(lái)減少I(mǎi)O次數(shù),使用異步來(lái)增加單服務(wù)吞吐量,使用無(wú)鎖數(shù)據(jù)結(jié)構(gòu)來(lái)減少響應(yīng)時(shí)間;

水平擴(kuò)展

只要增加服務(wù)器數(shù)量,就能線性擴(kuò)充系統(tǒng)性能。水平擴(kuò)展對(duì)系統(tǒng)架構(gòu)設(shè)計(jì)是有要求的,如何在架構(gòu)各層進(jìn)行可水平擴(kuò)展的設(shè)計(jì),以及互聯(lián)網(wǎng)公司架構(gòu)各層常見(jiàn)的水平擴(kuò)展實(shí)踐,是本文重點(diǎn)討論的內(nèi)容。

互聯(lián)網(wǎng)分層架構(gòu)中,各層次水平擴(kuò)展的實(shí)踐又有所不同:

  1. 反向代理層可以通過(guò)“DNS輪詢”的方式來(lái)進(jìn)行水平擴(kuò)展;
  2. 站點(diǎn)層可以通過(guò)nginx來(lái)進(jìn)行水平擴(kuò)展;
  3. 服務(wù)層可以通過(guò)服務(wù)連接池來(lái)進(jìn)行水平擴(kuò)展;
  4. 數(shù)據(jù)庫(kù)可以按照數(shù)據(jù)范圍,或者數(shù)據(jù)哈希的方式來(lái)進(jìn)行水平擴(kuò)展;

高可用優(yōu)化

高可用的核心思想是:資源保護(hù)和冗余。
資源保護(hù)常用手段是服務(wù)限流和熔斷降級(jí);
冗余是是指資源備份,如數(shù)據(jù)庫(kù)主從設(shè)計(jì),redis的哨兵機(jī)制。

前端優(yōu)化常用手段

瀏覽器/App

  1. 減少請(qǐng)求數(shù)
    合并CSS,Js,圖片;
  2. 使用客戶端緩存;
    靜態(tài)資源文件緩存在瀏覽器中,如果文件發(fā)生了變化,則通過(guò)改變文件名來(lái)更新緩存。
  3. 啟用壓縮
    它可以減少網(wǎng)絡(luò)傳輸量,但會(huì)給瀏覽器和服務(wù)器帶來(lái)性能的壓力,需要權(quán)衡使用。
  4. 資源文件加載順序
    css放在頁(yè)面最上面,js放在最下面。
  5. 減少Cookie傳輸
    cookie會(huì)包含在每次的請(qǐng)求和響應(yīng)中,因此哪些數(shù)據(jù)寫(xiě)入cookie需要慎重考慮。

CDN加速

CDN,又稱內(nèi)容分發(fā)網(wǎng)絡(luò),本質(zhì)仍然是一個(gè)緩存,而且是將數(shù)據(jù)緩存在用戶最近的地方。免費(fèi)的CDN加速器七牛云

反向代理緩存

將靜態(tài)資源文件緩存在反向代理服務(wù)器上,一般是Nginx。

WEB組件分離

瀏覽器在同一個(gè)域名下下載資源存在并發(fā)限制,所以,將js,css和圖片文件放在不同的域名下,可以提高瀏覽器在下載web組件的并發(fā)數(shù)。

應(yīng)用服務(wù)性能優(yōu)化

緩存

緩存的本質(zhì)是將數(shù)據(jù)存放在訪問(wèn)速度較高的介質(zhì)中,可以減少數(shù)據(jù)訪問(wèn)的時(shí)間,同時(shí)避免重復(fù)計(jì)算。

從上面響應(yīng)時(shí)間表格我們可以看出,使用緩存將數(shù)據(jù)緩存在本地或者redis服務(wù)器,將會(huì)對(duì)查詢效率有較大的提升。網(wǎng)站性能優(yōu)化的第一定律也是使用緩存,為監(jiān)控而生的多級(jí)緩存框架 layering-cache這是我開(kāi)源的一個(gè)多級(jí)緩存框架的實(shí)現(xiàn),如果有興趣可以看一下。

集群

負(fù)載均衡服務(wù)器(nginx,f5等)使用負(fù)責(zé)均衡算法,將請(qǐng)求分發(fā)到多個(gè)節(jié)點(diǎn)上進(jìn)行處理。

異步

同步和異步關(guān)注的是結(jié)果消息的通信機(jī)制:

  • 同步:同步的意思就是調(diào)用方需要主動(dòng)等待結(jié)果的返回;
  • 異步:異步的意思就是不需要主動(dòng)等待結(jié)果的返回,而是通過(guò)其他手段比如,狀態(tài)通知,回調(diào)函數(shù)等;

阻塞和非阻塞主要關(guān)注的是等待結(jié)果返回調(diào)用方的狀態(tài):

  • 阻塞:是指結(jié)果返回之前,當(dāng)前線程被掛起,不做任何事;
  • 非阻塞:是指結(jié)果在返回之前,線程可以做一些其他事,不會(huì)被掛起;

BIO、NIO和AIO

  • 同步阻塞:去商店買(mǎi)衣服,你去了之后發(fā)現(xiàn)衣服賣完了,商家說(shuō)要去庫(kù)房拿,那你就在店里面一直等,期間不做任何事(包括看手機(jī)),等著商家拿貨,這就是同步阻塞,效率低;
  • 同步非阻塞:去商店買(mǎi)衣服,你去了之后發(fā)現(xiàn)衣服賣完了,商家說(shuō)要去庫(kù)房拿,這時(shí)你可以去繼續(xù)逛街,但是時(shí)不時(shí)需要回去問(wèn)商家貨到了沒(méi),這就是同步非阻塞;
  • 異步阻塞:去商店買(mǎi)衣服,你去了之后發(fā)現(xiàn)衣服賣完了,這個(gè)時(shí)候時(shí)候你給商家留下電話,等貨到了電話通知你,然后你就啥事都不敢,守著電話等通知,這個(gè)模式有點(diǎn)傻,用的很少;
  • 異步非阻塞:去商店買(mǎi)衣服,你去了之后發(fā)現(xiàn)衣服賣完了,這時(shí)候你給商家留下電話,然后就可以去逛街了,等貨物到了商家會(huì)回電話通知你。

jdk里的BIO就屬于同步阻塞;jdk里的NIO就屬于同步非阻塞;jdk里的AIO就屬于異步。

常見(jiàn)的異步組件

  • Servlet3
  • 多線程
  • 消息隊(duì)列

代碼級(jí)別

  1. 選擇合適的數(shù)據(jù)結(jié)構(gòu),比如ArrayList和LinkedList的適用場(chǎng)景;
  2. 選擇更優(yōu)的算法,比如最大子序列和問(wèn)題,選擇窮舉算法那么時(shí)間復(fù)雜度是O(n^3);如果選擇動(dòng)態(tài)規(guī)劃算法,那么時(shí)間復(fù)雜度就是O(n)了;
  3. 編寫(xiě)更精簡(jiǎn)的代碼,同樣正確的程序,小程序比大程序要快;
  4. 并發(fā)編程,充分利用多核CPU資源;
  5. 同步情況下減少鎖的競(jìng)爭(zhēng);
  6. 資源的復(fù)用,比如單例模式,池化技術(shù);
  7. 序列化優(yōu)化,比如redis的使用默認(rèn)的JDK序列化和FastJson序列化,最后JDK序列化所暫用的空間是FastJson的3倍左右;

GC優(yōu)化

GC優(yōu)化的終極目的

  • GC的時(shí)間夠小
  • GC的次數(shù)夠少,發(fā)生Full GC的周期足夠的長(zhǎng),時(shí)間合理,最好是不發(fā)生。

GC運(yùn)行指標(biāo)

如果滿足則一般不需要調(diào)優(yōu):

  • Minor GC執(zhí)行時(shí)間不到50ms;
  • Minor GC執(zhí)行不頻繁,約10秒一次;
  • Full GC執(zhí)行時(shí)間不到1s;
  • Full GC執(zhí)行頻率不算頻繁,不低于10分鐘1次;

調(diào)優(yōu)的原則

  1. 大多數(shù)的java應(yīng)用不需要GC調(diào)優(yōu)
  2. 大部分需要GC調(diào)優(yōu)的的,不是參數(shù)問(wèn)題,是代碼問(wèn)題
  3. 在實(shí)際使用中,分析GC情況優(yōu)化代碼比優(yōu)化GC參數(shù)要多得多;
  4. GC調(diào)優(yōu)是最后的手段

GC調(diào)優(yōu)的最重要的三個(gè)選項(xiàng)

  1. 選擇合適的GC回收器
  2. 選擇合適的堆大小
  3. 選擇年輕代在堆中的比重

GC調(diào)優(yōu)的步驟

  1. 監(jiān)控GC的狀態(tài)
    使用各種JVM工具,查看當(dāng)前日志,分析當(dāng)前JVM參數(shù)設(shè)置,并且分析當(dāng)前堆內(nèi)存快照和gc日志,根據(jù)實(shí)際的各區(qū)域內(nèi)存劃分和GC執(zhí)行時(shí)間,覺(jué)得是否進(jìn)行優(yōu)化;

  2. 分析結(jié)果,判斷是否需要優(yōu)化
    如果各項(xiàng)參數(shù)設(shè)置合理,系統(tǒng)沒(méi)有超時(shí)日志出現(xiàn),GC頻率不高,GC耗時(shí)不高,那么沒(méi)有必要進(jìn)行GC優(yōu)化;如果GC時(shí)間超過(guò)1-3秒,或者頻繁GC,則必須優(yōu)化;

  3. 調(diào)整GC類型和內(nèi)存分配
    如果內(nèi)存分配過(guò)大或過(guò)小,或者采用的GC收集器比較慢,則應(yīng)該優(yōu)先調(diào)整這些參數(shù),并且先找1臺(tái)或幾臺(tái)機(jī)器進(jìn)行beta,然后比較優(yōu)化過(guò)的機(jī)器和沒(méi)有優(yōu)化的機(jī)器的性能對(duì)比,并有針對(duì)性的做出最后選擇;

  4. 不斷的分析和調(diào)整
    通過(guò)不斷的試驗(yàn)和試錯(cuò),分析并找到最合適的參數(shù)

  5. 全面應(yīng)用參數(shù)
    如果找到了最合適的參數(shù),則將這些參數(shù)應(yīng)用到所有服務(wù)器,并進(jìn)行后續(xù)跟蹤。

GC日志

以參數(shù)-Xms5m -Xmx5m -XX:+PrintGCDetails -XX:+UseSerialGC為例:

[DefNew: 1855K->1855K(1856K), 0.0000148 secs][Tenured: 2815K->4095K(4096K), 0.0134819 secs] 4671K
  • DefNew:指明了收集器類型,而且說(shuō)明了收集發(fā)生在新生代。
  • 1855K->1855K(1856K):表示,回收前 新生代占用1855K,回收后占用1855K,新生代大小1856K。
  • 0.0000148 secs: 表明新生代回收耗時(shí)。
  • Tenured:表明收集發(fā)生在老年代
  • 2815K->4095K(4096K):回收前后的值
  • 0.0134819 secs:老年代回收耗時(shí)
  • 最后的4671K指明堆的大小。

收集器參數(shù)變?yōu)?XX:+UseParNewGC,日志變?yōu)椋?/p>

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

收集器參數(shù)變?yōu)?XX:+ UseParallelGC或UseParallelOldGC,日志變?yōu)椋?/p>

 [PSYoungGen: 1024K->1022K(1536K)] [ParOldGen: 3783K->3782K(4096K)] 4807K->4804K(5632K)

CMS收集器和G1收集器會(huì)有明顯的相關(guān)字樣

GC相關(guān)的參數(shù)

  • -verbose:gc-XX:+PrintGC:打印簡(jiǎn)單的GC日志
  • -XX:+PrintGCDetails+XX:+PrintGCTimeStamps:打印詳細(xì)的GC日志
  • -Xlogger:[logpath]:指定GC日志路徑,如Xlogger:log/gc.log
  • -XX:+PrintHeapAtGC:打印推信息,獲取Heap在每次垃圾回收前后的使用狀況
  • -XX:+TraceClassLoading: 在系統(tǒng)控制臺(tái)信息中看到class加載的過(guò)程和具體的class信息,可用以分析類的加載順序以及是否可進(jìn)行精簡(jiǎn)操作。
  • -XX:+DisableExplicitGC:禁止在運(yùn)行期顯式地調(diào)用System.gc()
  • -XX:-HeapDumpOnOutOfMemoryError:默認(rèn)關(guān)閉,建議開(kāi)啟,在java.lang.OutOfMemoryError 異常出現(xiàn)時(shí),輸出一個(gè)dump.core文件,記錄當(dāng)時(shí)的堆內(nèi)存快照。
  • -XX:HeapDumpPath=./java_pid<pid>.hprof:默認(rèn)是java進(jìn)程啟動(dòng)位置,用來(lái)設(shè)置堆內(nèi)存快照的存儲(chǔ)文件路徑。

存儲(chǔ)性能優(yōu)化

  • 盡量使用SSD;
  • 定時(shí)清理數(shù)據(jù)或者按數(shù)據(jù)的性質(zhì)分開(kāi)存放;
  • 結(jié)果集處理,如:用setFetchSize控制jdbc每次從數(shù)據(jù)庫(kù)中返回多少數(shù)據(jù);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容