引言
? ?“在當(dāng)前的互聯(lián)網(wǎng)開發(fā)模式下瓤球,系統(tǒng)訪問量日漲、并發(fā)暴增敏弃、線上瓶頸等各種性能問題紛涌而至卦羡,性能優(yōu)化成為了現(xiàn)時(shí)代開發(fā)過程中炙手可熱的名詞,無論是在開發(fā)麦到、面試過程中绿饵,性能優(yōu)化都是一個(gè)常談常新的話題”。Java語言作為企業(yè)應(yīng)用中的“抗鼎者”瓶颠,Java生態(tài)中也積攢了大量寶貴的性能優(yōu)化經(jīng)驗(yàn)拟赊。
? ?在應(yīng)用系統(tǒng)中,性能優(yōu)化其實(shí)可以從各個(gè)角度出發(fā)考慮粹淋,如架構(gòu)優(yōu)化吸祟、前端調(diào)優(yōu)、中間件調(diào)優(yōu)桃移、網(wǎng)關(guān)調(diào)優(yōu)欢搜、容器調(diào)優(yōu)、JVM調(diào)優(yōu)谴轮、接口調(diào)優(yōu)炒瘟、服務(wù)器調(diào)優(yōu)、數(shù)據(jù)庫調(diào)優(yōu)等第步,從優(yōu)化類型上而言疮装,主體可以分為三類:
- ①結(jié)構(gòu)/架構(gòu)優(yōu)化:優(yōu)化應(yīng)用系統(tǒng)整體架構(gòu)做到性能提升的目的缘琅。如:讀寫分離、集群熱備廓推、分布式架構(gòu)刷袍、引入緩存/消息/搜索中間件、分庫分表樊展、中臺架構(gòu)(大數(shù)據(jù)中臺呻纹、基礎(chǔ)設(shè)施中臺)等。
- ②配置/參數(shù)優(yōu)化:調(diào)整應(yīng)用系統(tǒng)中各層面的配置文件专缠、啟動(dòng)參數(shù)達(dá)到優(yōu)化性能的目標(biāo)雷酪。如:JVM、服務(wù)器涝婉、數(shù)據(jù)庫哥力、、操作系統(tǒng)墩弯、中間件吩跋、容器、網(wǎng)關(guān)參數(shù)調(diào)整等渔工。
- ③代碼/操作優(yōu)化:開發(fā)者編寫程序時(shí)锌钮,從代碼、操作方面進(jìn)行調(diào)節(jié)引矩,達(dá)到效率更高的初衷轧粟。如:代碼中使用更優(yōu)秀的算法思想/設(shè)計(jì)模式、SQL優(yōu)化脓魏、對中間件的操作優(yōu)化等。
本章則重點(diǎn)闡述Java中通惫,JVM虛擬機(jī)相關(guān)的全面優(yōu)化茂翔,如:內(nèi)存、GC履腋、即時(shí)編譯珊燎、JVM參數(shù)配置等。
一遵湖、系統(tǒng)中性能優(yōu)化的核心思維
? ?性能調(diào)優(yōu)與上章:《線上排查問題》一樣悔政,是建立在經(jīng)驗(yàn)的基礎(chǔ)之上才能做好的,對于調(diào)優(yōu)要實(shí)事求是延旧,任何的調(diào)優(yōu)手段或技巧不要紙上談兵谋国,只有經(jīng)過實(shí)踐的才能用于生產(chǎn)環(huán)境,千萬不要將一些沒有實(shí)際依據(jù)的調(diào)優(yōu)策略用于線上環(huán)境迁沫,否則可能會(huì)導(dǎo)致原本好好的程序反而調(diào)優(yōu)調(diào)崩潰芦瘾。
1.1捌蚊、單個(gè)節(jié)點(diǎn)層面調(diào)優(yōu)的核心思想
? ?在一個(gè)程序中,所有的業(yè)務(wù)執(zhí)行實(shí)體都為線程近弟,應(yīng)用程序的性能跟線程是直接掛鉤的缅糟。而程序中的一條線程必須要經(jīng)過CPU的調(diào)度才可執(zhí)行,線程執(zhí)行時(shí)必然也會(huì)需要數(shù)據(jù)祷愉、產(chǎn)生數(shù)據(jù)窗宦,最終也會(huì)和內(nèi)存、磁盤打交道二鳄。因而單個(gè)節(jié)點(diǎn)的性能表現(xiàn)赴涵,不可避免的會(huì)跟CPU、內(nèi)存泥从、磁盤沾上關(guān)系句占。
? ?線程越多,需要的CPU調(diào)度能力也就越強(qiáng)躯嫉,需要的內(nèi)存也越大纱烘,磁盤IO速率也會(huì)要求越快。因此CPU祈餐、內(nèi)存擂啥、磁盤,這三者之間的任意之一達(dá)到了瓶頸帆阳,程序中的線程數(shù)量也會(huì)達(dá)到極限哺壶。達(dá)到極限后,系統(tǒng)的性能會(huì)成拋物線式下滑蜒谤,從而可能導(dǎo)致系統(tǒng)整體性能下降乃至癱瘓山宾。
由于如上原因,在考慮性能優(yōu)化時(shí)鳍徽,必然不能讓CPU资锰、內(nèi)存、磁盤等資源的使用率達(dá)到
95%+
阶祭,一般而言绷杜,最大利用率控制在80-85%
左右的最佳狀態(tài)。
? ?同時(shí)濒募,前面也分析過鞭盟,因?yàn)槌绦虻男阅芨€程掛鉤,所以線程的模型也是影響性能的重要因素瑰剃。目前程序設(shè)計(jì)中主要存在三種線程處理模型:BIO齿诉、NIO、AIO(NIO2)
,BIO
是Java中傳統(tǒng)的線程一對一處理模型鹃两,NIO
的最佳實(shí)踐為reactor
模型遗座,而proactor
模型又作為了NIO2/AIO
的落地者。絕大部分情況下俊扳,AIO
的性能優(yōu)于NIO
途蒋,而NIO
的性能又遠(yuǎn)超于BIO
。
所以在做性能優(yōu)化時(shí)馋记,你應(yīng)該要清楚系統(tǒng)的性能瓶頸在哪兒号坡,到底是要調(diào)哪個(gè)位置?是線程模型梯醒?或是CPU調(diào)度宽堆?還是內(nèi)存回收?亦是磁盤IO速率茸习?針對不同層面有不同的優(yōu)化方案畜隶,并非為了追求“熱詞/潮流”而盲目的調(diào)優(yōu)。
1.2号胚、優(yōu)秀且適用的系統(tǒng)架構(gòu)勝過千萬次調(diào)優(yōu)
? ?一個(gè)單體架構(gòu)(Tomcat+MySQL
)部署的系統(tǒng)遇到性能問題時(shí)籽慢,能力再強(qiáng),本事再大猫胁,任憑使出渾身解數(shù)也無法將其調(diào)到處理萬級并發(fā)的程序箱亿,正常服務(wù)器部署的一臺MySQL
服務(wù)做到極致調(diào)優(yōu)也難以在一秒內(nèi)承載5000+
的QPS
。一味的追求極致的優(yōu)化弃秆,其實(shí)也難以解決真正大流量下的并發(fā)沖擊届惋,因此一套優(yōu)秀的系統(tǒng)架構(gòu)勝過自己千萬次的調(diào)優(yōu)痕届。
? ?當(dāng)然隆豹,也并非說項(xiàng)目實(shí)現(xiàn)時(shí)财饥,越多的技術(shù)加進(jìn)來越好久锥,一套完善的分布式架構(gòu)就必然比單體架構(gòu)要好嗎?其實(shí)也不見得个盆,因?yàn)楫?dāng)引入的技術(shù)越多讨越,所需要考慮的問題也會(huì)更多远搪,耗費(fèi)的成本也會(huì)越高峡捡,一個(gè)項(xiàng)目收益60W
,結(jié)果用上最好的配置(高端的開發(fā)者+頂級的服務(wù)器+完善的分布式架構(gòu))成本耗費(fèi)200W
筑悴,這值得嗎们拙?答案顯而易見。因此阁吝,并沒有最好的技術(shù)架構(gòu)砚婆,只有最適用的架構(gòu),能從現(xiàn)有環(huán)境及實(shí)際業(yè)務(wù)出發(fā),選用最為合適的技術(shù)體系装盯,這才是我們應(yīng)該做的事情坷虑。如:
- 項(xiàng)目業(yè)務(wù)中讀寫參半,單節(jié)點(diǎn)難以承載壓力埂奈,項(xiàng)目集群迄损、雙主熱備值得參考。
- 項(xiàng)目業(yè)務(wù)中寫大于讀账磺,引入消息中間件芹敌、DB分庫、項(xiàng)目集群也可以考慮垮抗。
- 項(xiàng)目業(yè)務(wù)中讀大于寫氏捞,引入緩存/搜索中間件、動(dòng)靜分離冒版、讀寫分離是些不錯(cuò)的選擇液茎。
- .......
當(dāng)你的系統(tǒng)原有架構(gòu)遇到性能瓶頸時(shí),你甚至可以考慮進(jìn)一步做架構(gòu)優(yōu)化辞嗡,如:設(shè)計(jì)多級分布式緩存捆等、緩存中間件做集群、消息中間件做集群欲间、Java程序做集群楚里、數(shù)據(jù)庫做分庫分表、搜索中間件做集群.....猎贴,慢慢的班缎,你的系統(tǒng)會(huì)越來越龐大復(fù)雜,需要處理的問題也更為棘手她渴,但帶來的效果也顯而易見达址,隨著系統(tǒng)的結(jié)構(gòu)不斷變化,承載百萬級趁耗、千萬級沉唠、億級、乃至更大級別的流量也并非難事苛败。
? ?但只有當(dāng)你的業(yè)務(wù)流量/訪問壓力在選用其他架構(gòu)無法承載時(shí)满葛,你才應(yīng)該考慮更為龐大的架構(gòu)。當(dāng)然罢屈,如果項(xiàng)目在起步初期就有預(yù)估會(huì)承載巨大的流量壓力嘀韧,那么提前考慮也很在理,采用分布式/微服務(wù)架構(gòu)也并非失策缠捌,因?yàn)閷Ρ绕渌軜?gòu)體系而言锄贷,微服務(wù)架構(gòu)的拓展性更為靈活。但也需要記住:分布式/微服務(wù)體系是很好谊却,但它不一定適用于你的項(xiàng)目柔昼。
1.3、預(yù)防大于一切炎辨,調(diào)優(yōu)并非“臨時(shí)抱佛腳”
? ?當(dāng)問題出現(xiàn)時(shí)再想辦法解決捕透,這種策略永遠(yuǎn)都屬于下下策,防范于未然才是最佳方案蹦魔,提前防范問題出現(xiàn)主要可分為兩個(gè)階段:
- ①項(xiàng)目初期預(yù)測未來的流量壓力激率,提前根據(jù)業(yè)務(wù)設(shè)計(jì)出合適的架構(gòu),確保上線后可以承載業(yè)務(wù)的正常增長勿决。
- ②項(xiàng)目上線后乒躺,配備完善的監(jiān)控系統(tǒng),在性能瓶頸來臨前設(shè)好警報(bào)線低缩,確保能夠在真正的性能瓶頸到來之前解決問題嘉冒。
對于項(xiàng)目初期的架構(gòu)思考,值得牢記的一點(diǎn)是:不要“卡點(diǎn)”設(shè)計(jì)咆繁,也不能過度設(shè)計(jì)造成性能過剩讳推,舉例:
項(xiàng)目上線后的正常情況下,流量大概在“一木桶”左右玩般,結(jié)果你設(shè)計(jì)時(shí)直接整出個(gè)“池塘”級別的結(jié)構(gòu)出來了银觅,這顯然是不合理的,畢竟架構(gòu)體系越龐大坏为,項(xiàng)目的成本也自然就越高究驴。
當(dāng)然,也不能說正常情況下壓力在“一木桶”左右匀伏,就只設(shè)計(jì)出一套僅能夠承載“一木桶”流量的結(jié)構(gòu)洒忧,這種“卡點(diǎn)”設(shè)計(jì)的策略也是不可取的,因?yàn)槟阈枰m當(dāng)考慮業(yè)務(wù)增長帶來的風(fēng)險(xiǎn)够颠,如果“卡點(diǎn)”設(shè)計(jì)熙侍,那么很容易讓項(xiàng)目上線后,短期內(nèi)就遭遇性能瓶頸履磨。
因此蛉抓,如果項(xiàng)目正常的訪問壓力大概在“桶”級別,那將結(jié)構(gòu)設(shè)計(jì)到“缸”級別是合理的剃诅,這樣即不必?fù)?dān)心過度設(shè)計(jì)帶來的性能過剩巷送,導(dǎo)致成本增高;也無需考慮卡點(diǎn)設(shè)計(jì)造成的:項(xiàng)目短期遭遇性能瓶頸综苔。
但設(shè)計(jì)時(shí)的這個(gè)度惩系,必須由你自己根據(jù)項(xiàng)目的業(yè)務(wù)場景和環(huán)境去思量,不存在前篇一律的方法可教如筛。
有人曾說過:“如果你可以根據(jù)業(yè)務(wù)情景設(shè)計(jì)出一套能確保業(yè)務(wù)增長堡牡,且在線上能穩(wěn)定運(yùn)行三年時(shí)間以上的結(jié)構(gòu),那你就是位業(yè)內(nèi)的頂尖架構(gòu)”杨刨,但老話說的好:“計(jì)劃永遠(yuǎn)趕不上變化”晤柄,就算思考到業(yè)務(wù)的每個(gè)細(xì)節(jié),也不可能設(shè)計(jì)出一套一勞永逸的結(jié)構(gòu)出現(xiàn)妖胀,我們永遠(yuǎn)無法判斷意外和明天哪個(gè)先來芥颈。因而,項(xiàng)目上線后赚抡,配備完善的監(jiān)控警報(bào)系統(tǒng)也是必不可少的爬坑。不過值得注意的是:
監(jiān)控系統(tǒng)的作用并不是用來提醒你項(xiàng)目“嗝屁”了的,而是用來提醒你:線上部署的應(yīng)用系統(tǒng)可能會(huì)“嗝屁”或快“嗝屁”了涂臣,畢竟當(dāng)項(xiàng)目災(zāi)難已經(jīng)發(fā)生時(shí)再給警報(bào)盾计,那到時(shí)候的情況就是:“亡羊補(bǔ)牢,為時(shí)已晚”赁遗。
通常情況下署辉,在監(jiān)控系統(tǒng)上面設(shè)置的性能閾值都會(huì)比最大極限值要低5~15%
,如:最大極限值是85%
岩四,那設(shè)置告警值一般是75%
左右就會(huì)告警哭尝,不會(huì)真達(dá)到85%
才告警,只有這樣做才能留有足夠的時(shí)間讓運(yùn)維和開發(fā)人員介入排查剖煌。當(dāng)系統(tǒng)發(fā)出可能“嗝屁”的警告時(shí)材鹦,開發(fā)和運(yùn)維人員就應(yīng)當(dāng)立即排查相關(guān)的故障隱患,然后再通過不斷的修改和優(yōu)化末捣,提前將可能會(huì)出現(xiàn)的性能瓶頸解決侠姑,這才是性能調(diào)優(yōu)的正確方案。
因此箩做,最終結(jié)論為:絕不能等到系統(tǒng)奔潰才去優(yōu)化莽红,預(yù)防勝于一切。
1.4邦邦、無需追求完美安吁,理性權(quán)衡利弊
? ?“追求極致,做到完美”這點(diǎn)是大部分開發(fā)者的通病燃辖,很多人會(huì)因?yàn)檫@個(gè)思想導(dǎo)致自己在面臨一些問題時(shí)束手無策鬼店,比如舉個(gè)例子:
業(yè)務(wù):
MacBookPro
一元購活動(dòng),預(yù)計(jì)訪問壓力:10000QPS
黔龟。
環(huán)境:單臺機(jī)器只能承載2000QPS
妇智,目前機(jī)房中還剩余兩臺空閑服務(wù)器滥玷。
狀況:此時(shí)就算將空閑的兩臺機(jī)器加上去,也無法頂住目前的訪問壓力巍棱。
此時(shí)你會(huì)怎么做惑畴?很多人都會(huì)茫然,這看起來好像是沒辦法的事情呀航徙,似乎只能等死了.....
但事實(shí)真的如此嗎如贷?并非如此,其實(shí)這種情況也有多種解決方案到踏,如:
- ①停掉系統(tǒng)中部分非核心的業(yè)務(wù)杠袱,將服務(wù)器資源暫時(shí)讓給該業(yè)務(wù)。
- ②拋棄掉部分用戶的請求窝稿,只接受處理部分用戶的請求楣富。
- ③........
這些方案是不是可以解決上面的哪個(gè)問題呢?答案是肯定的伴榔。但完美主義者會(huì)認(rèn)為:
? ?系統(tǒng)中的服務(wù)不能停啊菩彬,得保持正常服務(wù)啊。
? ?用戶的請求怎么能拋潮梯,用戶的訪問必須得響應(yīng)啊骗灶。
但事實(shí)告訴你的是:類似于京東、淘寶秉馏、12306等這些國內(nèi)的頂級大廠耙旦,也照樣是這么干的。好比阿里萝究,在雙十一的時(shí)候都會(huì)抽調(diào)很多冷門業(yè)務(wù)的服務(wù)器資源給淘寶使用免都,也包括你在參與這些電商平臺的搶購或秒殺類活動(dòng)時(shí),你是否遇到過如下情況:
- 服務(wù)器繁忙帆竹,請稍后重試......
- 服務(wù)器已滿绕娘,排隊(duì)中.....
- 前方擁堵,排隊(duì)中栽连,當(dāng)前第
x
位.....
如果當(dāng)你遇到了這些情況险领,答案顯而易見,你的請求壓根就沒有到后端秒紧,在前端就給你pass
了绢陌,然后給你返回了一個(gè)字符串,讓你傻傻的等待熔恢。
? ?這個(gè)例子要告訴大家的是:在處理棘手問題或優(yōu)化性能時(shí)脐湾,無需刻意追求完美,理性權(quán)衡利弊后叙淌,適當(dāng)?shù)淖龀鲆恍Q斷秤掌,拋棄掉一部分不重要的愁铺,起碼比整個(gè)系統(tǒng)掛掉要好,何況之后照樣也可以恢復(fù)闻鉴。
1.5帜讲、性能調(diào)優(yōu)的通核心步驟
? ?性能優(yōu)化永遠(yuǎn)是建立在性能瓶頸之上的,如果你的系統(tǒng)沒有出現(xiàn)瓶頸椒拗,那則無需調(diào)優(yōu),調(diào)優(yōu)之前需要牢記的一點(diǎn)是:不要為了調(diào)優(yōu)而調(diào)優(yōu)获黔,而是需要調(diào)優(yōu)時(shí)才調(diào)蚀苛。
? ?而發(fā)現(xiàn)性能瓶頸的方式有兩種,一種是你的應(yīng)用中具備完善的監(jiān)控系統(tǒng)玷氏,能夠提前感知性能瓶頸的出現(xiàn)堵未。另一種則是:應(yīng)用中沒有搭載監(jiān)控系統(tǒng),性能瓶頸已經(jīng)發(fā)生盏触,從而導(dǎo)致應(yīng)用頻繁宕機(jī)渗蟹。大型的系統(tǒng)一般都會(huì)搭載完善的監(jiān)控系統(tǒng),但大多數(shù)中小型項(xiàng)目卻不具備該條件赞辩,因此雌芽,大部分中小型項(xiàng)目發(fā)現(xiàn)性能瓶頸時(shí),大多數(shù)情況下已經(jīng)“嗝屁”了辨嗽。
? ?通常而言世落,性能優(yōu)化的步驟可分為如下幾步:
- ①發(fā)現(xiàn)性能瓶頸:如有監(jiān)控系統(tǒng),那它會(huì)主動(dòng)發(fā)出警報(bào)糟需;如若沒有屉佳,那出現(xiàn)瓶頸時(shí)應(yīng)用肯定會(huì)出問題,如:無響應(yīng)洲押、響應(yīng)緩慢武花、頻繁宕機(jī)等。
- ②排查瓶頸原因:排查瓶頸是由于故障問題導(dǎo)致的杈帐,還是真的存在性能瓶頸体箕。
- ③定位瓶頸位置:往往一個(gè)系統(tǒng)都會(huì)由多個(gè)層面協(xié)同工作,然后對外提供服務(wù)挑童,當(dāng)發(fā)現(xiàn)性能瓶頸時(shí)干旁,應(yīng)當(dāng)確定瓶頸的范圍,如:網(wǎng)絡(luò)帶寬瓶頸炮沐、Java應(yīng)用瓶頸争群、數(shù)據(jù)庫瓶頸等。
- ④解決性能瓶頸:定位到具體的瓶頸后對癥下藥大年,從結(jié)構(gòu)换薄、配置玉雾、操作等方面出發(fā),著手解決瓶頸問題轻要。
本章則重點(diǎn)是闡述Java虛擬機(jī)-JVM相關(guān)的調(diào)優(yōu)操作复旬,但需要先提前說明的是:
單層面的性能調(diào)優(yōu)其實(shí)只能當(dāng)成錦上添花的作用,但絕對不能成為系統(tǒng)性能高/低冲泥、響應(yīng)快/慢驹碍、吞吐量大/小的決定性要素。應(yīng)用系統(tǒng)的性能本身就還算可以凡恍,那么調(diào)優(yōu)的作用是讓其性能更佳志秃。但如若項(xiàng)目結(jié)構(gòu)本身就存在問題,那么能夠帶來的性能提升也是有限的嚼酝,如果你想讓你的項(xiàng)目快到飛起浮还,那么還需要從多個(gè)層面共同著手才能達(dá)到目的。
二闽巩、JVM垃圾收集相關(guān)調(diào)優(yōu)策略
? ?在JVM垃圾收集相關(guān)的調(diào)優(yōu)實(shí)踐中钧舌,通常都是以最優(yōu)吞吐量和最短停頓時(shí)間來評價(jià)JVM的性能:吞吐量越高代表性能越好、暫停時(shí)間越短也代表越好涎跨。那么如何做到這兩點(diǎn)呢洼冻?核心思想在于:
- 盡可能讓對象在新生代中分配和回收。
- 盡量避免過多對象進(jìn)入年老代隅很,縮短年老代GC時(shí)間碘赖。
- 盡量給JVM分配足夠多的內(nèi)存,減少所有區(qū)域中的GC次數(shù)外构。
? ?歸根結(jié)底普泡,本質(zhì)思想就一點(diǎn):“盡量讓Java中的對象去到它自己該去的位置”,短命的對象就老老實(shí)實(shí)的進(jìn)入新生代區(qū)域审编,大對象和長命的對象則進(jìn)入年老代空間撼班,避免JVM因?yàn)閷ο蟆皝y竄”導(dǎo)致GC頻發(fā)和GC時(shí)間變長,如:
- 本該在新生代的短命對象由于特殊原因進(jìn)了年老代垒酬,導(dǎo)致年老代GC次數(shù)變多/時(shí)間變長砰嘁。
- 本該直接分配在年老代的長命大對象,因?yàn)槟承┰蛉勘环峙湓谛律本浚瑢?dǎo)致新生代可分配空間變少矮湘,引發(fā)分配擔(dān)保機(jī)制,造成大量未達(dá)到標(biāo)準(zhǔn)的新生代對象提前進(jìn)入年老代口糕。
因此缅阳,GC調(diào)優(yōu)的目的就相當(dāng)于給JVM做“保養(yǎng)”,讓其每個(gè)區(qū)域按照設(shè)計(jì)的初衷正常工作景描。
? ?通常情況下十办,當(dāng)JVM存在性能問題時(shí)秀撇,都會(huì)牽扯到兩個(gè)概念,分配速率(Allocation Rate
)和提升速率(Promotion Rate
)向族,這也是分析性能問題時(shí)常用的兩個(gè)指標(biāo)呵燕,其中分配速率影響新生代的垃圾回收,提升速率影響年老代的垃圾回收件相。
2.1再扭、新生代-分配速率(Allocation Rate
)
? ?分配速率代表固定時(shí)間內(nèi)分配的內(nèi)存量,通常情況下以MB/S
為單位夜矗,分配速率高泛范,其實(shí)并不是什么好事,對于這點(diǎn)我們稍后再做闡述侯养。先來具體如何計(jì)算分配的速率。
2.1.1澄干、分配速率如何計(jì)算逛揩?
一般而言可以通過GC日志計(jì)算出來,比如:
0.751: [GC (Allocation Failure) [PSYoungGen: 30705K->5115K(38400K)]
30705K->12385K(125952K), 0.0187498 secs]
[Times: user=0.00 sys=0.00, real=0.02 secs]
1.514: [GC (Allocation Failure) [PSYoungGen: 38395K->5120K(71680K)]
45665K->35687K(159232K), 0.0570688 secs]
[Times: user=0.09 sys=0.00, real=0.06 secs]
3.018: [GC (Allocation Failure) [PSYoungGen: 70326K->5104K(71680K)]
108940K->105240K(172032K), 0.0866792 secs]
[Times: user=0.30 sys=0.02, real=0.09 secs]
分配速率計(jì)算公式:(本輪GC前使用容量-上輪GC后使用容量)/(本輪GC時(shí)間-上輪GC時(shí)間)
GC輪數(shù) | 時(shí)間差值 | 上輪GC后容量 | 本輪GC前容量 | 容量差值 | 分配速率 |
---|---|---|---|---|---|
第一輪 | 751ms | 0KB | 30705KB | 30705KB | ≈41MB/S |
第二輪 | 763ms | 5115KB | 38395KB | 33280KB | ≈44MB/S |
第三輪 | 1504ms | 5120KB | 70326KB | 65206KB | ≈43MB/S |
每輪均速 | NULL | NULL | NULL | NULL | ≈43MB/S |
通過GC日志中的信息可以初步計(jì)算出麸俘,該Java程序中的對象分配速率大概在43MB/S
左右辩稽。
2.1.2、分配速率對JVM的影響
? ?前面曾提及過从媚,分配速率高并不是好事逞泄,為什么這么說呢?因?yàn)镴ava程序的分配速率越高時(shí)拜效,也代表著堆中分配的對象會(huì)越多喷众,對象越多也就會(huì)讓GC的頻率更頻繁。因此紧憾,當(dāng)分配速率越高到千,會(huì)導(dǎo)致JVM的GC開銷越大,分配速率的變化會(huì)增加或降低STW的頻率赴穗,從而影響吞吐量憔四。
但高分配速率的標(biāo)準(zhǔn)是相對而言的,要根據(jù)具體的
Eden
區(qū)大小來判斷般眉,一個(gè)堆大小為32GB
的分配速率是1000MB/S
了赵,一個(gè)500MB
的堆空間分配速率為100MB/S
,前者可被稱為是高分配速率嗎甸赃?并非如此柿汛,因?yàn)榍罢叩亩延?code>32G,1000MB/S
的速率也需要一段時(shí)間才能觸發(fā)GC埠对,但后者100MB/S
的速率對于500M
的堆空間而言苛茂,則可被稱為高速率已烤,因?yàn)閷τ?code>500MB的堆空間而言,會(huì)在極短的時(shí)間內(nèi)觸發(fā)GC妓羊。因此胯究,分配速率高低是要根據(jù)實(shí)際的堆大小來判斷。
2.1.3躁绸、分配速率的四種狀況
- ①分配速率低裕循,回收速率超于分配速率,GC狀態(tài)無異常净刮,代表系統(tǒng)GC正常剥哑。
- ②分配速率高,回收速率略低于或遠(yuǎn)低于分配速率淹父,代表程序存在OOM隱患株婴。
- ③分配速率高,但回收速率勉強(qiáng)可以跟上暑认,代表系統(tǒng)處于“亞健康”狀態(tài)困介。
- ④分配速率低,GC次數(shù)頻繁蘸际,釋放空間較少座哩,可能存在內(nèi)存泄漏。
? ?其中①為正常狀況粮彤,無需做任何處理根穷,也沒必要去對于這類系統(tǒng)做刻意優(yōu)化,如果你的Java應(yīng)用的JVM處于該狀態(tài)导坟,但程序整體吞吐量依舊上不去屿良,或響應(yīng)速度緩慢,那應(yīng)該從其他層面入手解決惫周。
? ?如果Java應(yīng)用出現(xiàn)第③種情況管引,其實(shí)應(yīng)用本身是沒有任何問題的,這種情況一般是由于分配的堆空間不足闯两,分配速率過快褥伴,導(dǎo)致頻繁觸發(fā)GC回收閾值,因此造成GC負(fù)載過重漾狼,對于這類情況應(yīng)該適當(dāng)調(diào)大堆空間重慢,從而使GC頻繁下降。
? ?②逊躁、④則都是程序中存在隱患會(huì)出現(xiàn)的狀況似踱,通常情況下都是由于程序中存在不規(guī)范的代碼導(dǎo)致的。狀況②是因?yàn)榇a在堆中生成了大量對象,造成分配速率很高核芽,回收速度無法跟上分配速度囚戚,從而導(dǎo)致應(yīng)用有可能內(nèi)存溢出。
? ?狀況④則是明顯的內(nèi)存泄露問題轧简,因?yàn)镚C開銷較大驰坊,但實(shí)際回收后釋放的空間較小,代表內(nèi)存中有大量對象無法回收哮独,這可能是由于內(nèi)存泄漏導(dǎo)致的拳芙。同時(shí),也正因?yàn)镚C次數(shù)比較頻繁皮璧,所以導(dǎo)致應(yīng)用中的用戶線程暫停了工作舟扎,停止了對象分配,因而出現(xiàn)了分配速率低的“假象”悴务。
? ?對于②睹限、④狀況則需要優(yōu)化代碼,前者需要降低分配速率讯檐,后者則需要解決內(nèi)存泄漏羡疗。
2.1.4贡未、新生代空間調(diào)優(yōu)思想
? ?新生代空間的調(diào)優(yōu)核心思想就是需要降低分配速率椅亚,簡單來說就是少創(chuàng)建對象、多分配空間,以減少GC次數(shù)蕉拢,加大系統(tǒng)吞吐量。但需要值得理解的是:為新生代分配更大的堆空間诚亚,反而會(huì)使分配速率提高晕换,但新生代空間大了,觸發(fā)GC的閾值自然會(huì)增加站宗,從而能夠達(dá)到減少GC頻率的目的闸准。
2.2、年老代-提升速率(Promotion Rate
)
? ?前面分析的分配速率僅會(huì)對新生代空間造成影響梢灭,而影響年老代空間的則是另外一個(gè)指標(biāo):提升速率夷家,也就是指定時(shí)間內(nèi),新生代升入年老代空間的對象總量敏释,通常單位也為MB/S
库快。
在前面談?wù)摲峙渌俾蕰r(shí),可以根據(jù)GC日志計(jì)算新生代的分配占比钥顽,但新生代升入年老代空間的提升速率又該如何計(jì)算呢义屏?因?yàn)?code>MajorGC一般都是伴隨著
FullGC
一起發(fā)生的,所以無法根據(jù)MajorGC
計(jì)算,比較FullGC
時(shí)會(huì)回收整堆空間闽铐。
2.2.1蝶怔、提升速率如何計(jì)算?
? ?同樣計(jì)算提升速率時(shí)兄墅,依舊是通過MinorGC
日志來計(jì)算:
1.514: [GC (Allocation Failure) [PSYoungGen: 38395K->5120K(71680K)]
45665K->35687K(159232K), 0.0570688 secs]
[Times: user=0.09 sys=0.00, real=0.06 secs]
3.018: [GC (Allocation Failure) [PSYoungGen: 70326K->5104K(71680K)]
100894K->105240K(172032K), 0.0866792 secs]
[Times: user=0.30 sys=0.02, real=0.09 secs]
提升速率計(jì)算公式:((新生代回收前使用總量-新生代回收后使用總量)-(整堆回收前使用總量-整堆回收后使用總量))/(本輪GC時(shí)間-上輪GC時(shí)間)
GC輪數(shù) | 時(shí)間差值 | 新生代減少 | 整堆減少 | 提升量 | 提升速率 |
---|---|---|---|---|---|
第一輪 | 763ms | 33275KB | 9978KB | 23297KB | ≈30MB/S |
第二輪 | 1504ms | 65222KB | 3700KB | 61522KB | ≈40MB/S |
每輪均速 | NULL | NULL | NULL | NULL | ≈35MB/S |
結(jié)果如上表踢星,此刻是通過MinorGC
日志來計(jì)算的提升速率,拆解前面的計(jì)算公式可以分析出整體的計(jì)算邏輯:
- 先通過新生代回收前后的已使用容量大小察迟,計(jì)算出新生代中減少容量斩狱。
- 再通過整堆回收前后的已使用容量大小,計(jì)算出整個(gè)堆空間的減少容量扎瓶。
- 再通過新生代減少-整堆減少所踊,這樣可以大致算出新生代中提升到年老代的提升量。
- 該方式只能計(jì)算出大概的提升量概荷,因?yàn)檎褱p少會(huì)包含年老代秕岛、元空間等區(qū)域回收。
- 在通過本次GC觸發(fā)時(shí)間-上次GC觸發(fā)時(shí)間误证,得到本輪GC中程序正常執(zhí)行的時(shí)長继薛。
- 最后通過提示量除執(zhí)行時(shí)長,即可得到JVM的大概提升速率愈捅。
不過在計(jì)算提升速率的時(shí)候遏考,有個(gè)點(diǎn)需要額外注意:Java應(yīng)用啟動(dòng)后的第一條GC日志不能參與計(jì)算,因?yàn)榈谝粭lGC日志是程序啟動(dòng)后蓝谨,初次觸發(fā)GC時(shí)輸出的灌具,此時(shí)堆空間剛從“冷狀態(tài)”啟動(dòng),因此測算出的速率并非程序正常執(zhí)行時(shí)的提升速率譬巫。
2.2.2咖楣、提升速率對JVM的影響
? ?和分配速率相同,提升速率也一樣會(huì)影響GC芦昔,但它影響的是年老代空間诱贿,速率越快也就代表著提升的對象越多,年老代空間被填滿的時(shí)間會(huì)更短咕缎,MajorGC
被觸發(fā)的頻率也會(huì)越快珠十。不過通常情況下,年老代的GC一般會(huì)伴隨著FullGC
一起發(fā)生凭豪,因此焙蹭,提升速率越高會(huì)最終導(dǎo)致FullGC
頻率越快。
2.2.3墅诡、進(jìn)入年老代的三種異常情況
- ①代碼存在內(nèi)存泄漏
當(dāng)代碼中存在內(nèi)存泄漏時(shí)壳嚎,會(huì)造成堆內(nèi)存被一點(diǎn)點(diǎn)蠶食桐智,最終導(dǎo)致新生代空間沒有空閑內(nèi)存分配新對象,從而觸發(fā)JVM的空間分代擔(dān)保機(jī)制烟馅,開啟對象動(dòng)態(tài)晉升閾值判定说庭,將大量原本未達(dá)晉升標(biāo)準(zhǔn)的對象提前遷入年老代空間,以確保新生代擁有足夠的空閑內(nèi)存維護(hù)Java應(yīng)用的正常執(zhí)行郑趁。
常發(fā)性內(nèi)存泄漏刊驴、偶發(fā)性內(nèi)存泄漏、一次性內(nèi)存泄漏寡润、隱式內(nèi)存泄漏捆憎,不同性質(zhì)的內(nèi)存泄漏造成的提升速率增長也不同,后兩者引發(fā)的速率增長并不大梭纹,但前兩者躲惰,尤其是常發(fā)性內(nèi)存泄漏會(huì)帶來很大的隱患,最終必然會(huì)引發(fā)OOM变抽。
- ②頻繁的大對象分配
在分代堆中有這么一條法則:“超過指定閾值的大對象會(huì)被直接送往年老代空間”础拨,這條結(jié)論是依據(jù)對象特性而制定的,正常情況下绍载,大對象都不會(huì)是“朝生夕死”的對象诡宗,一般都能夠“活”到成功晉升。因此击儡,為了節(jié)省大對象在兩個(gè)
Survivor
區(qū)中反復(fù)挪動(dòng)帶來的開銷塔沃,JVM會(huì)將超過閾值標(biāo)準(zhǔn)的大對象直接分配到年老代。
大對象直接進(jìn)入年老代是合理的阳谍,但頻繁的大對象分配是不合理的蛀柴,會(huì)導(dǎo)致年老代被快速填滿,因而頻繁觸發(fā)FullGC
边坤。
大對象直接進(jìn)入年老代空間名扛,因此大對象分配是不參與前述的提升速率計(jì)算公式的谅年。
- ③高并發(fā)/大流量壓力
當(dāng)系統(tǒng)業(yè)務(wù)暴漲時(shí)茧痒,巨大的流量和并發(fā)沖擊會(huì)導(dǎo)致業(yè)務(wù)線程創(chuàng)建更多的新對象,因而會(huì)導(dǎo)致新生代的GC閾值被頻繁觸發(fā)融蹂,加快了新生代整體的晉升速度旺订,從而導(dǎo)致提升速率暴漲。
對于這類正常業(yè)務(wù)增長導(dǎo)致的提升速率變高超燃,這是系統(tǒng)中的常事区拳,這種情況下只需依照具體業(yè)務(wù)流量的增長,合理的調(diào)大堆空間即可意乓。
? ?其實(shí)歸根結(jié)底樱调,上述三點(diǎn)都是在圍繞著“對象被過早提升到年老代”這一核心思想展開。對于年老代而言,新生代空間中的所有對象笆凌,按部就班的活到15
歲再晉升是最佳的狀態(tài)圣猎,因?yàn)槟軌蛟谛律具^十多輪GC的對象晉升后,絕大多數(shù)情況下會(huì)再存活很長一段時(shí)間乞而。
? ?但如果是由于上述三種狀況導(dǎo)致對象過早提升到年老代空間送悔,則會(huì)帶來很大的不穩(wěn)定因素,有可能很多提早晉升的對象剛晉升爪模,沒熬過幾輪GC就“死”了欠啤,從而違背了“年老代存放長命對象”的設(shè)計(jì)初衷。同時(shí)屋灌,過早提升還會(huì)造成年老代會(huì)被快速填滿洁段,從而頻繁觸發(fā)FullGC
,最終導(dǎo)致Java應(yīng)用暫停時(shí)間過長共郭,影響系統(tǒng)整體的吞吐量眉撵。
2.2.4、年老代空間調(diào)優(yōu)思想
? ?年老代空間調(diào)優(yōu)的核心就一點(diǎn):避免或盡量減少過早提升落塑,為何不是降低提升速率呢纽疟?因?yàn)樵跇I(yè)務(wù)規(guī)模比較大的情況下,提升速率比較高也是合理的憾赁。所以在調(diào)優(yōu)年老代時(shí)污朽,只需要將過早提升的對象依舊控制在新生代即可。
過早提升的表現(xiàn)
- ①一次
FullGC
后龙考,年老代的空間占用比極速下降蟆肆。 - ②短時(shí)間內(nèi)頻繁觸發(fā)
FullGC
。 - ③提升速率接近分配速率晦款。
- ④新生代GC發(fā)生后炎功,新生代的空間占用比下降到
20%
以內(nèi)。
過早提升如何解決缓溅?
? ?處理過早提升時(shí)蛇损,需要根據(jù)具體的情況來決定采取何種措施:
- ①如果是業(yè)務(wù)或流量壓力變大導(dǎo)致的,那么增大新生代空間即可坛怪。
- ②如果是代碼中存在問題淤齐,如內(nèi)存泄漏或循環(huán)體中創(chuàng)建對象等,優(yōu)化代碼即可袜匿。
- ③如果是短命的大對象分配更啄,如大數(shù)組,則可以考慮優(yōu)化數(shù)據(jù)結(jié)構(gòu)居灯,如換成鏈表祭务。
2.3内狗、合理的堆空間該如何分配
? ?Java內(nèi)存各分區(qū)的大小對JVM的性能影響很大,不恰當(dāng)?shù)目臻g大小可能會(huì)埋下很多故障隱患义锥,同時(shí)也會(huì)直接或間接影響JVM的提升速率其屏、分配速率,所以如何將各分區(qū)調(diào)整到合適的大小就成了一個(gè)棘手的問題缨该。大部分不具備線上JVM調(diào)優(yōu)實(shí)操經(jīng)驗(yàn)的開發(fā)者都會(huì)茫然偎行,通常會(huì)認(rèn)為設(shè)定的越大越好,但答案卻并非如此贰拿。
? ?在指定各區(qū)域大小時(shí)蛤袒,可以依據(jù)“活躍數(shù)據(jù)”大小來進(jìn)行設(shè)定,“活躍數(shù)據(jù)”是指應(yīng)用程序穩(wěn)定運(yùn)行后長期存活在堆中的對象膨更,也就是FullGC
后年老代中的對象妙真。一般在計(jì)算“活躍數(shù)據(jù)大小”,都會(huì)多次采集程序穩(wěn)定執(zhí)行后的FullGC
日志荚守,通過取平均值的方式計(jì)算出堆中長期存活的年老代總量大小珍德。
? ?計(jì)算出“活躍數(shù)據(jù)大小”后,就可以根據(jù)其具體值計(jì)算出其他分區(qū)恰當(dāng)?shù)闹荡Q壤缦拢?/p>
- ①堆空間:活躍數(shù)據(jù)大小的
4~5
倍 - ②新生代:活躍數(shù)據(jù)大小的
1.5~2
倍 - ③年老代:活躍數(shù)據(jù)大小的
2.5~3
倍 - ④元空間:活躍數(shù)據(jù)大小的
1.2~1.8
倍
假設(shè)此時(shí)觀測出的“活躍數(shù)據(jù)大小”為800MB
锈候,那堆空間的各區(qū)域的大小:
- ①堆空間:
3200MB
- ②新生代:
1200MB
- ③年老代:
2000MB
當(dāng)然敞贡,這僅作為初始值參考泵琳,具體情況取決于應(yīng)用業(yè)務(wù)的特性和需求。
但需注意的是:實(shí)際過程中誊役,
-Xmx获列、-Xms
兩個(gè)參數(shù)設(shè)定的值必須一致,這樣做的好處在于可以避免動(dòng)態(tài)伸縮時(shí)帶來的性能損耗與空間震蕩蛔垢,因?yàn)楫?dāng)JVM內(nèi)存不足向OS申請內(nèi)存時(shí)都會(huì)觸發(fā)一次全局GC击孩。
2.4、GC調(diào)優(yōu)實(shí)操思路
? ?前面幾點(diǎn)所提及的都是GC調(diào)優(yōu)的一些方法論以及衡量指標(biāo)鹏漆,但在真正需要處理GC調(diào)優(yōu)時(shí)巩梢,上面幾點(diǎn)只能給你提供輔導(dǎo),并不能建立完善的調(diào)優(yōu)思路甫男,因此且改,接下來再一同論述GC調(diào)優(yōu)的具體實(shí)操思想验烧。
? ?GC調(diào)優(yōu)時(shí)板驳,一般會(huì)根據(jù)Java程序所裝配的垃圾收集器以及具體的GC日志來作為基礎(chǔ)進(jìn)行操作,但不同的垃圾回收器執(zhí)行的GC日志都是不同的碍拆,因此并沒有萬能的調(diào)優(yōu)策略可以滿足所有的性能指標(biāo)若治,GC優(yōu)化要建立在具體的業(yè)務(wù)場景及環(huán)境中慨蓝,才能達(dá)到事半功倍的效果。不過通常GC調(diào)優(yōu)核心步驟如下:
- ①明確優(yōu)化目標(biāo)
- ②實(shí)施優(yōu)化操作
- ③跟蹤優(yōu)化結(jié)果
調(diào)優(yōu)前首先需要確定的就是優(yōu)化目標(biāo)端幼,到底是需要減少GC停頓礼烈,還是增大程序吞吐等,然后再根據(jù)目標(biāo)排除GC日志婆跑,分析后根據(jù)日志中的分配速率此熬、提升速率、GC頻率滑进、GC各階段停頓時(shí)間等指標(biāo)犀忱,實(shí)行具體的優(yōu)化操作。
同時(shí)扶关,也不必奢求一次優(yōu)化到位阴汇,GC調(diào)優(yōu)通常是需要多次進(jìn)行的,一次優(yōu)化往往無法達(dá)到目標(biāo)預(yù)期节槐,需要不斷的根據(jù)優(yōu)化后的GC日志再次制定優(yōu)化策略搀庶,從而最終達(dá)到優(yōu)化目標(biāo)。
但GC調(diào)優(yōu)的根本其實(shí)是在調(diào)“對象”铜异,如果程序本身代碼就存在問題哥倔,好比代碼中存在頻繁創(chuàng)建對象的邏輯,就算你調(diào)出花來也無濟(jì)于事揍庄,必須還得從根源上解決問題未斑,這種情況下應(yīng)當(dāng)采用jmap
工具分析堆使用情況,查看對象分布币绩,從而反向定位代碼中的問題并加以解決蜡秽。
2.5、GC優(yōu)化總結(jié)
? ?凡是涉及性能調(diào)優(yōu)的內(nèi)容缆镣,幾乎都必須建立在監(jiān)控系統(tǒng)之上芽突,不一定要全面,但至少能讓調(diào)優(yōu)前有指標(biāo)數(shù)據(jù)可參考董瞻。對于監(jiān)控系統(tǒng)中寞蚌,JVM-GC這塊建議統(tǒng)計(jì)的信息:
- ①流量方面:流量峰值、流量均值钠糊、用活時(shí)間段等挟秤。
- ②對象方面:分配速率、每個(gè)請求的分配均值/峰值抄伍、提升速率艘刚、每次提升總量均值等。
- ③GC方面:
MinorGC截珍、FullGC
停頓時(shí)長攀甚、GC觸發(fā)間隔箩朴、GC回收總量等。 - ..........
GC調(diào)優(yōu)時(shí)的收益排序:改善代碼 > 裝配合適的GC回收器 > 重新設(shè)置內(nèi)存比例/大小 > 調(diào)整JVM參數(shù)秋度。
但需重點(diǎn)注意的是:上述的GC調(diào)優(yōu)理論都是基于
G1
之前的分代垃圾收集器而言的炸庞,G1
之后的不分代收集器,如:ZGC荚斯、ShenandoahGC
等壓根沒必要刻意優(yōu)化埠居,自身的機(jī)制本就足夠優(yōu)異,而且后續(xù)的不分代收集器對外暴露的可操作參數(shù)也并不多事期。
三拐格、阿里在線排除工具 - Arthas
? ?Arthas(阿爾薩斯)是阿里開源的一款Java在線診斷工具,官網(wǎng)原話:當(dāng)你遇到以下類似問題而束手無策時(shí)刑赶,Arthas可以幫助你解決:
- 這個(gè)類從哪個(gè) jar 包加載的捏浊?為什么會(huì)報(bào)各種類相關(guān)的 Exception?
- 我改的代碼為什么沒有執(zhí)行到撞叨?難道是我沒 commit金踪?分支搞錯(cuò)了?
- 遇到問題無法在線上 debug牵敷,難道只能通過加日志再重新發(fā)布嗎胡岔?
- 線上遇到某個(gè)用戶的數(shù)據(jù)處理有問題,但線上同樣無法 debug枷餐,線下無法重現(xiàn)靶瘸!
- 是否有一個(gè)全局視角來查看系統(tǒng)的運(yùn)行狀況?
- 有什么辦法可以監(jiān)控到JVM的實(shí)時(shí)運(yùn)行狀態(tài)毛肋?
- 怎么快速定位應(yīng)用的熱點(diǎn)怨咪,生成火焰圖?
- 怎樣直接從JVM內(nèi)查找某個(gè)類的實(shí)例润匙?
Arthas
支持JDK6+
诗眨,支持Linux/Mac/Winodws
,采用命令行交互模式孕讳,同時(shí)提供豐富的Tab
自動(dòng)補(bǔ)全功能匠楚,進(jìn)一步方便進(jìn)行問題的定位和診斷。
3.1厂财、Arthas快速上手
? ?對于Arthas工具如果不會(huì)使用芋簿,其實(shí)阿里提供的在線的Terminal
學(xué)習(xí)方式(傳送門),可以幫助大家快速上手璃饱,下面在本篇中也快速概述一下与斤。
依照官方的案例演示,先下載并啟動(dòng)提供好的Java案例:
$ wget https://arthas.aliyun.com/math-game.jar
$ java -jar math-game.jar
再啟動(dòng)一個(gè)新的Terminal
窗口,下載并啟動(dòng)Arthas
工具:
$ wget https://arthas.aliyun.com/arthas-boot.jar
$ java -jar arthas-boot.jar
緊接著Arthas
會(huì)將本機(jī)中所有的Java進(jìn)程查詢出來幽告,類似于jps/ps
的作用:
[INFO] arthas-boot version: 3.5.5
[INFO] Found existing java process.......
* [1]: 161 math-game.jar
如果你的機(jī)器中啟動(dòng)了多個(gè)Java應(yīng)用梅鹦,此時(shí)會(huì)查詢出來一個(gè)應(yīng)用列表裆甩,我們可以根據(jù)前面的序號選擇自己要操作的Java應(yīng)用冗锁,如上情況中,再輸入1
即可:
$ 1
最終嗤栓,Arthas
成功啟動(dòng)冻河,接下來再通過Arthas
提供的指令進(jìn)行操作即可:
3.2、Arthas命令詳解
? ?Arthas
從最初的發(fā)布開始茉帅,隨著后續(xù)社區(qū)的活躍性增強(qiáng)及用戶群體的不斷壯大叨叙,指令也越發(fā)完善與豐富,至目前為止提供了基礎(chǔ)命令堪澎、JVM命令擂错、class命令以及字節(jié)碼增強(qiáng)命令等幾大類。
3.2.1樱蛤、基礎(chǔ)命令
-
help
:查看Arthas
命令幫助信息钮呀。 -
cls
:清空當(dāng)前屏幕中的所有信息,類似于clear
命令昨凡。 -
session
:查看當(dāng)前會(huì)話的信息爽醋。 -
reset
:重置所有增強(qiáng)類,還原Arthas
增強(qiáng)過的所有類(stop
時(shí)生效)便脊。 -
version
:顯示當(dāng)前的Arthas
版本信息蚂四。 -
history
:輸出歷史執(zhí)行過的所有命令。 -
quit
:退出當(dāng)前的Arthas
會(huì)話哪痰,其他會(huì)話不受影響遂赠。 -
shutdown
:關(guān)閉所有Arthas
會(huì)話后,退出Arthas
晌杰。 -
stop
:強(qiáng)制關(guān)閉Arthas
并中斷所有會(huì)話解愤。 -
keymap
:輸出Arthas
中所有默認(rèn)的以及自定義的快捷鍵。 -
options
:查看或設(shè)置Arthas
的全局開關(guān)乎莉。 -
pwd
:返回當(dāng)前的工作目錄位置送讲,同Linux的pwd
命令。
3.2.2惋啃、類命令
-
sc
:查看JVM已加載的類信息哼鬓,可選項(xiàng)如下:-
class-pattern
:類名表達(dá)式匹配(必填),如sc java.lang.String
边灭。 -
-E
:開啟正則表達(dá)式匹配异希,默認(rèn)為通配符匹配。 -
-c
:指定class
的類加載器的哈希碼绒瘦。 -
-d
:顯示當(dāng)前類的詳細(xì)信息称簿,包含來源扣癣、聲明、類加載相關(guān)等信息憨降。 -
-f
:輸出當(dāng)前類的屬性成員信息父虑,與-d
一同使用。 -
-x
:指定輸出靜態(tài)變量時(shí)屬性的遍歷深度授药,默認(rèn)為0
士嚎。 -
-n
:具有詳細(xì)信息的匹配類的最大數(shù)量(默認(rèn)為100)。
-
-
sm
:查看已加載類的方法信息悔叽,可選項(xiàng)如下:-
class-pattern
:類名表達(dá)式匹配(必填)莱衩,如sm java.lang.String
。 -
-E
:開啟正則表達(dá)式匹配娇澎,默認(rèn)為通配符匹配笨蚁。 -
-d
:查看方法的詳細(xì)信息,配合方法名使用趟庄,如sm -d java.lang.String toString
括细。 -
-c
:同sc -c
作用相同。 -
-n
:同sc -h
作用相同岔激。
-
-
jad
:反編譯指定已加載類的源碼勒极,可選項(xiàng)如下:-
-c、-E
都與前面的作用相同虑鼎,舉幾個(gè)案例演示用法辱匿。 -
jad --source-only java.lang.String
:只顯示反編譯后的Java源碼。 -
jad java.lang.String
:反編譯指定類炫彩。 -
jad java.lang.String toString
:反編譯指定類的某個(gè)方法匾七。
-
-
mc
:內(nèi)存編譯器,編譯.java
源文件為.class
類文件江兢,可選項(xiàng)如下:-
-c
:指定類加載器(以哈希碼的方式指定)昨忆。 -
-d
:指定編譯后的類文件輸出位置。
-
-
redefine
:加載外部的.class
文件杉允,重新加載JVM已加載的類邑贴。- 推薦使用
retransform
代替redefine
。
- 推薦使用
-
retransform
:作用與redefine
相同叔磷,熱部署的作用拢驾,用于線上替換類方法。- 注意點(diǎn):
- ①重新替換JVM中被加載的類時(shí)改基,不能新增方法或?qū)傩浴?/li>
- ②正在執(zhí)行的方法不能替換繁疤。
- 注意點(diǎn):
-
dump
:導(dǎo)出已加載類的字節(jié)碼數(shù)據(jù)到指定目錄,可選項(xiàng)如下:-
-c、-E
作用與之前的相同稠腊。 -
-d
:指定輸出的路徑躁染,如dump -d /usr/data/byteCode java.lang.String
。
-
-
classloader
:查類加載器的繼承樹架忌,urls吞彤,類加載信息,可選項(xiàng)如下:-
-a
:顯示所有類加載器加載的所有類鳖昌。 -
-c
:查看指定的類加載器的加載路徑备畦,如classloader -c 14ae5a5
低飒。 -
-l
:統(tǒng)計(jì)每個(gè)類加載器的加載信息许昨。 -
-r
:查找某個(gè)的資源路徑,配合-c
使用褥赊,如classloader -c 33909752 -r java/lang/String.class
糕档。 -
-t
:以樹結(jié)構(gòu)列出每個(gè)類加載器之間的父子關(guān)系。 -
-u
:顯示類加載器的url統(tǒng)計(jì)信息拌喉,如加載總數(shù)速那、父子關(guān)系、加載范圍等尿背。 -
-i
:查看每種類加載器的實(shí)例數(shù)量及其加載總量端仰。
-
3.2.3、JVM命令
-
dashboard
:資源監(jiān)控儀表盤田藐,包含線程荔烧、內(nèi)存、GC汽久、運(yùn)行環(huán)境等信息鹤竭,可選項(xiàng)如下:-
-i
:刷新實(shí)時(shí)數(shù)據(jù)的間隔時(shí)間,默認(rèn)為5000ms
景醇。 -
-n
:刷新實(shí)時(shí)數(shù)據(jù)的次數(shù)臀稚,默認(rèn)為一直持續(xù)刷新,按ctrl+c
退出三痰。
-
-
thread
:查看當(dāng)前線程的堆棧信息吧寺,可選項(xiàng)如下:-
-n
:顯示最活躍的n
條線程信息,如thread -n 5
散劫。 -
-i
:指定活躍性統(tǒng)計(jì)的采樣間隔時(shí)間稚机,如thread -i 5000
。 -
-b
:自動(dòng)檢測出應(yīng)用中當(dāng)前阻塞其他線程的線程舷丹。 -
--state
:查詢目前程序中處于指定狀態(tài)的線程抒钱,如thread --state BLOCKED
。 -
id
:查看某個(gè)線程的詳細(xì)信息,如thread 21
谋币。
-
-
jvm
:查看JVM信息仗扬,包含線程/內(nèi)存/OS/內(nèi)存結(jié)構(gòu)/編譯/類加載/運(yùn)行環(huán)境等信息。 -
sysprop
:查看或修改當(dāng)前JVM的系統(tǒng)屬性蕾额,如sysprop java.home
早芭。 -
sysenv
:,查看當(dāng)前JVM的環(huán)境參數(shù)诅蝶。 -
vmoption
:查看或修改JVM的運(yùn)行時(shí)參數(shù)退个,如:-
vmoption PrintGC
:查看PrintGC
是否開啟。 -
vmoption PrintGC true
:更改PrintGC
參數(shù)调炬。
-
-
logger
:查看logger信息语盈,更新logger level郎哭。 -
getstatic
:查看類的靜態(tài)屬性绊茧,用法:getstatic class_nmae field_name
。 -
ognl
:執(zhí)行ognl表達(dá)式窄陡,使用方式可參考:官方指南棘钞、特殊用法缠借。 -
heapdump
:類似于jmap
工具的堆dump
功能,使用方式:-
heapdump /usr/data/dump/heap.hprof
:導(dǎo)出堆快照到指定文件宜猜。 -
heapdump --live /usr/data/dump/heap.hprof
:只導(dǎo)出存活對象的快照泼返。
-
-
mbean
:查看Mbean
的信息,詳情參考:官方文檔姨拥。 -
memory
:查看JVM的內(nèi)存劃分绅喉、內(nèi)存結(jié)構(gòu)以及占用率。
3.2.4垫毙、字節(jié)碼增強(qiáng)命令
-
tt
:記錄指定方法每次執(zhí)行的數(shù)據(jù)霹疫,并能在不同的時(shí)間下調(diào)用觀測,可選項(xiàng)如下:-
<class_pattern> <method_pattern>
:指定要觀測的類名+方法名综芥。 -
-t
:記錄下方法每次執(zhí)行的情況丽蝎,如tt -t demo.MathGame primeFactors
。 -
-i <index>
:查看某條執(zhí)行記錄的執(zhí)行詳情膀藐,如tt -i 1000
屠阻。 -
-d <index>
:刪除某條執(zhí)行記錄,配合-i
使用额各,tt- d -i 1000
国觉。 -
-n
:設(shè)置執(zhí)行次數(shù),如tt -t -n 10 demo.MathGame primeFactors
虾啦。 -
-l
:顯示目前已存在的所有執(zhí)行記錄麻诀。 -
-p
:重新執(zhí)行某條執(zhí)行記錄痕寓,配合-i
使用,如tt -i 1001 -p
蝇闭。 -
-s
:通過OGNL
表達(dá)式進(jìn)行查找呻率。 -
-M
:指定接收結(jié)果的字節(jié)上限,默認(rèn)為1KB
呻引。 -
---replay-times
:配合-p
使用礼仗,指定重新執(zhí)行N
次。 -
--replay-interval
:執(zhí)行多次時(shí)逻悠,每次執(zhí)行時(shí)的間隔時(shí)間元践。 - 重新執(zhí)行
3
次某記錄,每次間隔500ms
:tt -i 1001 -p --replay-times 3 --replay-interval 500
童谒。
-
-
watch
:觀測指定方法的執(zhí)行情況单旁,可選項(xiàng)如下:-
-b
:在方法調(diào)用之前觀測。 -
-s
:在方法成功執(zhí)行后觀測惠啄。 -
-e
:在方法異常執(zhí)行后觀測慎恒。 -
-f
:在方法結(jié)束后進(jìn)行觀測(默認(rèn))任内。 -
-n
:指定觀測的次數(shù)撵渡。 - 使用示例:
watch -s -n 10 demo.MathGame primeFactors
-
-
monitor
:對指定的方法執(zhí)行進(jìn)行監(jiān)控,可選項(xiàng)如下:-
-c
:指定監(jiān)控的周期死嗦,默認(rèn)為60s
趋距。 -
-n
:指定監(jiān)控的周期次數(shù)。 - 使用示例:
monitor -c 10 -n 3 demo.MathGame primeFactors
-
-
stack
:輸出當(dāng)前方法被調(diào)用的調(diào)用路徑越除。 -
trace
:方法內(nèi)部調(diào)用路徑节腐,并輸出方法路徑上的每個(gè)節(jié)點(diǎn)上耗時(shí),可選項(xiàng)如下:-
-i
:跳過JVM的本地方法摘盆。 -
-n
:和之前的-n
同義翼雀。
-
3.2.5、Arthas的OGNL表達(dá)式
? ?Arthas中的很多進(jìn)階操作都需要依賴于OGNL
表達(dá)式進(jìn)行編寫孩擂,因此想要玩轉(zhuǎn)Arthas狼渊,自然需要對于OGNL
也具備一定的基本功,接下來演示一些常規(guī)操作类垦,詳細(xì)的使用方式可參考:官方指南狈邑、特殊用法。
①蚤认、調(diào)用靜態(tài)屬性
ognl '@類的全限定名@靜態(tài)屬性名'
示例:
[arthas@80573]$ ognl '@demo.MathGame@random'
②米苹、調(diào)用靜態(tài)方法
ognl '@類的全限定名@靜態(tài)方法名("參數(shù)")'
示例1:調(diào)用入?yún)榛緮?shù)據(jù)類型和集合的方法:
[arthas@80573]$ ognl '@demo.MathGame@print(100,{1,2,3,4})' -x 1
null
示例2:調(diào)用入?yún)閷ο箢愋偷姆椒ǎ?/p>
[arthas@80573]$ ognl '#obj=new java.lang.Object(),@xxx.xxx@xxx(#obj)' -x 1
示例3:調(diào)用入?yún)?code>Map類型的方法:
[arthas@80573]$ ognl '#map={"k1":"v1","k2":"v2"},@xxx.xxx@xxx(#map)' -x 1
示例4:將一個(gè)方法的執(zhí)行結(jié)果作為另一個(gè)方法的入?yún)ⅲ?/p>
[arthas@80573]$ ognl '#result=@xx.xx@A(),@xx.xx@xx(#result)' -x 1
③、調(diào)用構(gòu)造方法
ognl 'new 類的全限定名()'
示例1:調(diào)用無參創(chuàng)建對象
[arthas@80573]$ ognl 'new java.lang.Object()'
示例2:調(diào)用有參創(chuàng)建對象
[arthas@80573]$ ognl 'new xxx.xx.xxx("xx",x,{1,2,3})'
示例3:調(diào)用存在對象引用類型的構(gòu)造函數(shù)創(chuàng)建對象
[arthas@80573]$ ognl '#obj=new new java.lang.Object(),new xxx.xx.xxx(#obj)'
④砰琢、讀取不同類型的值
示例1:讀取引用對象類型的屬性值
[arthas@80573]$ ognl '@類全限定名@方法名("參數(shù)").屬性名稱'
示例2:讀取List
類型的指定元素
[arthas@80573]$ ognl '@類全限定名@方法名("參數(shù)")[下標(biāo)]'
示例3:讀取Map
類型的指定元素
[arthas@80573]$ ognl '@類全限定名@方法名("參數(shù)")["key"]'
⑤.........
詳細(xì)的OGNL
語法可參考:官方指南蘸嘶,在線上排查時(shí)往往會(huì)結(jié)合tt良瞧、watch、monitor训唱、stack莺褒、trace
等多個(gè)命令共同使用。
3.3雪情、Arthas線上常用場景
? ?Arthas中集成了大部分JDK工具的功能實(shí)現(xiàn)遵岩,因此,在線上情況時(shí)巡通,可以通過它快速的幫助我們解決問題尘执,如CPU占用過高、線程阻塞宴凉、死鎖誊锭、代碼動(dòng)態(tài)修改、方法執(zhí)行緩慢弥锄、排查404
等丧靡。
3.3.1、排查CPU占用過高問題
- ①使用
thread -n 10
命令查看CPU占用資源最高的10條線程籽暇。 - ②使用
thread
命令查看前幾條線程的詳細(xì)執(zhí)行信息温治,定位到具體的方法。 - ③使用
monitor
命令對前面定位到的方法進(jìn)行監(jiān)控戒悠,查看方法的調(diào)用次數(shù)與耗時(shí)熬荆。 - ④分析
monitor
命令查詢出的結(jié)果,定位問題根源绸狐,確定是由于調(diào)用過于頻繁導(dǎo)致的卤恳,還是內(nèi)部代碼邏輯問題。 - ⑤使用
jad
命令反編譯class
文件寒矿,根據(jù)前面分析的原因排查代碼并改善突琳。
3.3.2、排查線程阻塞問題
- ①使用
thread
查看所有線程信息符相,再篩選所有阻塞狀態(tài)的線程拆融。 - ②根據(jù)線程名稱定位具體的業(yè)務(wù)模塊,再選中該業(yè)務(wù)中的一條線程查看堆棧信息主巍。
- ③根據(jù)線程堆棧信息定位導(dǎo)致阻塞的具體方法冠息,再利用
stack
命令查看方法堆棧信息。 - ④利用
jad
工具反編譯源碼孕索,分析業(yè)務(wù)邏輯代碼并改善逛艰。
3.3.3、排查死鎖問題
- ①利用
Arthas
來檢測死鎖特別簡單搞旭,只需要執(zhí)行一行命令thread -b
即可散怖。
3.3.4菇绵、排查方法執(zhí)行過慢問題
- ①通過
trace
命令排查方法執(zhí)行速度,trace xx類 xx方法 '#cost>50ms'
镇眷,觀測執(zhí)行時(shí)間大于50ms
的該方法的調(diào)用信息咬最。 - ②可以結(jié)合正則表達(dá)式,同時(shí)排查多個(gè)類欠动、多個(gè)方法永乌,
trace -E ClassA|ClassB method1|method2|method3
。
3.3.5具伍、動(dòng)態(tài)修改線上代碼
有些項(xiàng)目編譯可能需要兩小時(shí)翅雏,好容易編譯完成上線之后,發(fā)現(xiàn)代碼有一處小地方存在邏輯錯(cuò)誤需要更改人芽,此時(shí)難度需要重新將其下線望几,重新更改后打包部署嗎?有了Arthas
之后的你完全不需要這樣干萤厅。
- ①通過
jad
將要修改的類反編譯為.java
文件橄抹,輸出到指定目錄。 - ②本地糾正
.java
文件后惕味,通過mc
命令重新編譯.java
文件楼誓。 - ③通過
redefine
或retransform
命令將剛編譯的.class
文件再次加載到JVM中。
這個(gè)功能是Arthas非常實(shí)用的一個(gè)功能赦拘,往往在線上環(huán)境被用于代碼糾錯(cuò)慌随、日志級別修改、Java配置文件修改等場景躺同。
3.3.6、...........
? ?顯然丸逸,Arthas
還有更多的應(yīng)用場景等待你去探索蹋艺,根據(jù)不同的業(yè)務(wù)場景以及遇到的不同問題,利用Arthas
都可以實(shí)現(xiàn)很好的排查與解決黄刚,上述中僅列出一些常見的應(yīng)用場景捎谨。
四、不同場景下的最佳配置推薦
? ?線上JVM的最佳參數(shù)配置往往要根據(jù)實(shí)際的業(yè)務(wù)場景以及運(yùn)行環(huán)境進(jìn)行思量憔维,首先需要弄明白業(yè)務(wù)是追求響應(yīng)速度還是吞吐量涛救,再者需要結(jié)合所部署的硬件配置及服務(wù)器環(huán)境綜合考慮,下面提供一些配置參數(shù)給予大家用作參考业扒。
4.1检吆、運(yùn)行時(shí)數(shù)據(jù)區(qū)
4.1.1、堆空間
? ?之前曾提及到程储,運(yùn)行時(shí)數(shù)據(jù)區(qū)最佳的空間大小蹭沛,以“活躍數(shù)據(jù)大小”進(jìn)行作為基礎(chǔ)參考臂寝,然后進(jìn)行設(shè)置:
無論你的項(xiàng)目是追求響應(yīng)速度,亦或是吞吐量摊灭,都可根據(jù)“活躍數(shù)據(jù)”計(jì)算的大小作為基礎(chǔ)進(jìn)行調(diào)整咆贬,依照“活躍數(shù)據(jù)”計(jì)算出的大小也恰巧能夠符合
Sun
公司官方給出的推薦,如:
新生代空間的最佳占比應(yīng)當(dāng)在堆總大小的
3/8
帚呼,換算成百分比為37.5%
掏缎。
通過上圖中根據(jù)“活躍數(shù)據(jù)”獲取的各分區(qū)大小進(jìn)行計(jì)算:
1200MB(Eden)/3200MB(Heap)=0.375(37.5%)
,和官方的推薦完全一致煤杀。
那么實(shí)際項(xiàng)目上線時(shí)御毅,“活躍數(shù)據(jù)大小”如何獲取呢?可以在測試階段進(jìn)行壓測怜珍,然后通過GC
日志進(jìn)行計(jì)算端蛆。不過基于“活躍數(shù)據(jù)”計(jì)算出的大小也可以根據(jù)業(yè)務(wù)進(jìn)行調(diào)整。
- ①對象存活較高的業(yè)務(wù)酥泛,
Survivor
區(qū)與Eden
區(qū)比值建議為2:4
今豆,即-XX:SurvivorRatio=4
。 - ②對象晉升年齡閾值建議:
- 對象存活率較低的業(yè)務(wù):保留默認(rèn)值柔袁,即
15
呆躲。 - 對象存活率較高的業(yè)務(wù):建議調(diào)小,如
-XX:MaxTenuringThreshold=7
捶索,可以減少大量存活對象在幸存區(qū)反復(fù)橫跳帶來的性能開銷插掂。
- 對象存活率較低的業(yè)務(wù):保留默認(rèn)值柔袁,即
- ③JIT編譯的熱點(diǎn)代碼緩存區(qū)至少
64M
,即-XX:ReservedCodeCacheSize=64m
腥例。 - ④TLAB線程私有區(qū)域可以調(diào)整為
Eden
區(qū)的1/10
辅甥,即-XX:TLABWasteTargetPercent=10
。 - ⑤記得打開
OOM
時(shí)Dump
堆的參數(shù)燎竖,以及執(zhí)行腳本可以指定為重啟應(yīng)用璃弄。
1.8及以上版本的JDK大多數(shù)情況下,只需要調(diào)整好每個(gè)分區(qū)的大小即可构回,其他的優(yōu)化參數(shù)夏块,大多數(shù)JVM都會(huì)默認(rèn)開啟。
-Xms纤掸、-Xmx
兩參數(shù)的值需保持一致脐供,防止由于內(nèi)存動(dòng)態(tài)伸縮時(shí)造成抖動(dòng)影響性能。
4.2借跪、元空間
? ?元空間的大小建議:一般在“活躍數(shù)據(jù)”的1.2
倍左右足夠政己,如果程序內(nèi)使用大量動(dòng)態(tài)代理,可以嘗試加大到1.5垦梆、1.8
倍匹颤。
4.3仅孩、棧空間
? ?HotSpot中印蓖,Java虛擬機(jī)棧和本地方法棧合二為一了辽慕,因此這里的棧空間涵蓋了這兩個(gè)概念赦肃。
? ?JDK1.5之前默認(rèn)棧大小為256K
溅蛉,1.5之后默認(rèn)為1M
大小,對于該值的調(diào)整要基于業(yè)務(wù)來決定他宛,如果業(yè)務(wù)執(zhí)行時(shí)船侧,方法調(diào)用鏈不會(huì)太長,可以適當(dāng)縮小到512k
厅各,即-Xss512K
镜撩,這樣做的好處在于:在物理內(nèi)存相同的情況下,該值越小队塘,程序中就能產(chǎn)生更多的線程袁梗,從而能夠擁有更多的線程處理客戶端到來的請求。
但操作系統(tǒng)不可能允許一個(gè)進(jìn)程無限制的創(chuàng)建線程憔古,因此單個(gè)進(jìn)程中的線程數(shù)量一般最多控制
3000~5000
最佳遮怜。
4.2、GC垃圾收集
? ?GC方面也是JVM調(diào)優(yōu)中“操作性”最大的部分鸿市,因此锯梁,這部分在JVM調(diào)優(yōu)額外重要。
4.2.1焰情、選擇垃圾收集器
? ?選用合適的垃圾收集器往往能夠讓你的應(yīng)用性能提升一大截陌凳,但合適的收集器也需要根據(jù)運(yùn)行環(huán)境及業(yè)務(wù)場景去選擇,那如何選擇最合適的收集器呢烙样?
- ①冯遂、將堆空間調(diào)整到合適的大小后,優(yōu)先讓JVM自行根據(jù)配置選擇谒获。
- ②、如果內(nèi)存小于
100MB
或部署在單核/雙核機(jī)器壁却,使用串行收集器批狱。 - ③、JDK8及以前追求低延遲(響應(yīng)速度)選
ParNew+CMS
展东,追求高吞吐則選PS+PO
赔硫。 - ④、后續(xù)新版本的JDK中盐肃,
8GB
以上可以考慮選用G1
爪膊,上百GB
規(guī)娜ㄎ颍可采用ZGC
。
4.2.2推盛、ParNew+CMS組合參數(shù)推薦
- 使用
ParNew+CMS
組合:-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
峦阁。 - ①并行收集GC線程數(shù)建議為CPU核數(shù),即
-XX:ParallelCMSThreads=CPU*core
耘成。 - ②內(nèi)存碎片整理方面(
MSC
工作):-
-XX:+UseCMSCompactAtFullCollection
:內(nèi)存碎片化嚴(yán)重時(shí)開啟MSC
整理榔昔。 - 建議將每次
FullGC
后的內(nèi)存整理改為2-3
輪觸發(fā)一次,即-XX:CMSFullGCsBeforeCompaction
瘪菌。
-
- ③因?yàn)槭亲非箜憫?yīng)速度的組合撒会,因此目標(biāo)停頓時(shí)間可以適當(dāng)偏小一些,即
-XX:MaxGCPauseMillis
师妙。 - ④激進(jìn)優(yōu)化策略:
-
-XX:+CMSParallellnitialMarkEnabled
:在初始階段采用多線程執(zhí)行诵肛。 -
-XX:+CMSParallelRemarkEnabled
:在重新標(biāo)記階段采用多線程執(zhí)行。 -
-XX:+CMSScavengeBeforeRemark
:在重新標(biāo)記階段前觸發(fā)一次新生代GC默穴。
-
4.2.3怔檩、ParallelScavenge+ParallelOld組合參數(shù)推薦
- 使用
PS+PO
組合:-XX:+UseParallelGC -XX:+UseParallelOldGC
。 - ①并行收集GC線程數(shù)建議為CPU核數(shù)壁顶,即
-XX:ParallelGCThreads=CPU*core
珠洗。 - ②因?yàn)槭亲非笸掏碌慕M合,因此吞吐比盡量可以調(diào)高若专,即
-XX:GCTimeRatio
许蓖,如若無經(jīng)驗(yàn)沒法預(yù)估準(zhǔn)確值,那則可以開啟JVM的自適應(yīng)調(diào)整策略:-XX:+UseAdaptiveSizePolicy
调衰。
4.2.4膊爪、G1整堆收集器參數(shù)推薦
- 使用
G1
收集器:-XX:+UseG1GC
。 - ①不要強(qiáng)制使用
-Xmn
參數(shù)設(shè)置年輕代的大小嚎莉,因?yàn)镚1是通過動(dòng)態(tài)調(diào)整年輕代大小達(dá)到目標(biāo)暫停時(shí)間的目的米酬。 - ②如果分配的對象平均體積過大,可以適當(dāng)調(diào)大每個(gè)分區(qū)的
Size
趋箩,但必須要為2
的次冪赃额,即通過-XX:G1HeapRegionSize
調(diào)整,正常情況下盡量不要手動(dòng)調(diào)整叫确。 - ③盡量可以將并發(fā)線程數(shù)調(diào)整的大一些跳芳,即
-XX:ConcGCThreads
,一般推薦為CPU核數(shù)+1~2
竹勉。 - ④手動(dòng)指定觸發(fā)混合GC的閾值飞盆,關(guān)閉
IHOP
適應(yīng)分析,消除自適應(yīng)計(jì)算的耗時(shí),-XX:InitiatingHeapOccupancyPercent=45 -XX:-G1UseAdaptiveIHOP
吓歇。 - ⑤混合GC時(shí)間過長可微調(diào)該三個(gè)參數(shù):
-XX:G1MixedGCCountTarget=8 -XX:G1MixedGCLiveThresholdPercent=88 -XX:G1HeapWastePercent=5
孽水。
4.3、性能激進(jìn)優(yōu)化策略
? ?在JDK1.7及其之后的版本中城看,JVM推出了很多激進(jìn)優(yōu)化的策略女气,但在1.8及其之后的環(huán)境中,大部分的參數(shù)都是默認(rèn)開啟的析命,因此我們沒有必要顯式再次開啟主卫。但其實(shí)JVM中的一些激進(jìn)優(yōu)化參數(shù)默認(rèn)也并未打開,如果你的程序堆空間足夠大鹃愤,也可以嘗試開啟后優(yōu)化程序性能簇搅。
- ①
-XX:ParGCCardsPerStrideChunk=4096
:CMS激進(jìn)優(yōu)化策略,增大GC線程掃描卡表的范圍软吐,默認(rèn)為256
瘩将,三個(gè)最佳值為32768、4K凹耙、8K
姿现。 - ②
-XX:+AlwaysPreTouch
:開啟物理內(nèi)存分配替換虛擬內(nèi)存分配,優(yōu)化分配率肖抱。 - ③
-XX:+UseLargePages
:啟用內(nèi)存大頁面分配技術(shù)备典。 - ④
-XX:-UseBiasedLocking
:關(guān)閉偏向鎖,在并發(fā)較高的系統(tǒng)中關(guān)閉反而可以提升性能意述。 - ⑤
-XX:AutoBoxCacheMax=20000
:加大IntrgerCache
的緩存提佣。 - ⑥
-XX:-UseCounterDecay
:關(guān)閉JIT即時(shí)編譯器的熱度衰減機(jī)制(會(huì)消耗一定內(nèi)存)。 - ⑦
-XX:-TieredCompilation
:關(guān)閉C1
靜態(tài)編譯器編譯荤崇,直接使用C2
編譯拌屏。 - ⑧
-XX:MaxDirectMemorySize
:直接內(nèi)存大小如果確認(rèn)用的比較少,可以調(diào)小术荤,如果用的比較多倚喂,可以適當(dāng)調(diào)大。
4.4瓣戚、不同的啟動(dòng)方式參數(shù)設(shè)置方式
-
Idea/Ecalipse
:在運(yùn)行時(shí)的選項(xiàng)卡中配置端圈,如IDEA的Configurations... -> VM Options
中。 -
Tomcat
:bin
目錄下的catalina.sh
文件中的JAVA_OPTS
的值上寫JVM參數(shù)即可子库。 -
jar
包方式啟動(dòng)直接將VM參數(shù)跟在后面即可枫笛。
五、總結(jié)
? ?對于性能優(yōu)化這個(gè)內(nèi)容而言刚照,沒有絕對正確或最佳的參數(shù),也包括本章的內(nèi)容你可以適當(dāng)參考但不能照搬于生產(chǎn)環(huán)境喧兄,安全第一无畔,項(xiàng)目能夠穩(wěn)定執(zhí)行是根本啊楚,性能優(yōu)化永遠(yuǎn)要建立在應(yīng)用健康運(yùn)轉(zhuǎn)但遭遇瓶頸的基礎(chǔ)上,不要隨便調(diào)優(yōu)浑彰,更不要刻意調(diào)優(yōu)恭理。
同時(shí),對于JDK不同版本中的默認(rèn)值郭变,如果你不清楚其具體作用颜价,那建議保留默認(rèn)值,畢竟JDK默認(rèn)將其設(shè)為此值總有它的理由诉濒,默認(rèn)值至少能夠滿足絕大部分的項(xiàng)目需求周伦。因此,如若你沒有豐富的激進(jìn)優(yōu)化經(jīng)驗(yàn)未荒,再次重申:不要隨意更改一些性能參數(shù)的默認(rèn)值专挪。