歡迎關(guān)注公眾號(hào)OpenCoder,來和我做朋友吧~??????
案例背景:
實(shí)際開發(fā)中有很多類似的這樣的應(yīng)用場(chǎng)景汤徽,比如每秒多少個(gè)請(qǐng)求桥言,每次請(qǐng)求分配多少對(duì)象等,我們的目的就是通過工具分析我們系統(tǒng)在實(shí)際運(yùn)行過程中是否頻繁觸發(fā)GC以及對(duì)象是否頻繁進(jìn)入老年代引發(fā)Full GC厌漂,哪些對(duì)象存在影響性能以及沒有及時(shí)回收的問題。
我們以一個(gè)線上的BI系統(tǒng)來進(jìn)行講解斟珊,整個(gè)的流程運(yùn)行如下:
針對(duì)上訴系統(tǒng)在商家不多的情況下苇倡,也就是幾分鐘卡頓10ms,對(duì)于用戶端的感受來講幾乎沒有影響囤踩,但是假設(shè)如果我們的商家突然暴增旨椒,同時(shí)訪問量能達(dá)到幾千,我們的機(jī)器可能每秒請(qǐng)求量就會(huì)達(dá)到幾百個(gè)比如500堵漱,那么這時(shí)每秒的數(shù)據(jù)加載就有50MB综慎,那么針對(duì)只有1G的Eden區(qū)域多久就會(huì)發(fā)生一次YongGc呢?自己系統(tǒng)多久會(huì)觸發(fā)一次Full GC呢勤庐?
以下代碼示惊,我們模擬的就是系統(tǒng)正常運(yùn)行,每秒鐘50個(gè)請(qǐng)求愉镰,每個(gè)請(qǐng)求加載100KB數(shù)據(jù)的方式不停運(yùn)行米罚,由于是死循環(huán),我們不停止程序也不會(huì)停止:
/**
* @Description: 案例實(shí)戰(zhàn)-通過jps丈探、jstat录择、jmap、jhat工具進(jìn)行聯(lián)調(diào)優(yōu)化
JVM參數(shù): -XX:NewSize=100m -XX:MaxNewSize=100m -XX:InitialHeapSize=200m -XX:MaxHeapSize=200m -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=3m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
*
*/
public class JVMTest {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(30000);
while(true){
loadData();
}
}
public static final int _1KB = 1024;
/**
* 模擬每秒50個(gè)請(qǐng)求碗降,每次請(qǐng)求分配100kb的數(shù)組
* @throws InterruptedException
*/
private static void loadData() throws InterruptedException {
byte[] data = null;
for (int i = 0; i < 50; i++) {
data = new byte[100 * _1KB ];
}
data = null;
Thread.sleep(1000);
}
}
我們給堆內(nèi)存設(shè)置為200M隘竭,新生代100M,Eden區(qū)占80M讼渊,老年代占100M進(jìn)行模擬GC情況动看。
步驟分析:
- 在Main方法中首先第一行代碼先執(zhí)行睡眠30S,目的在于方便我們程序啟動(dòng)后精偿,通過jps命令找到我們當(dāng)前程序進(jìn)程ID弧圆,然后結(jié)合jstat來觀察程序運(yùn)行狀態(tài)
- 接著無限循環(huán)開始加載數(shù)據(jù),在loadData()方法中每隔1秒向內(nèi)存中申請(qǐng)分配100*50 = 5MB對(duì)象
- 通過jstat命令打印觀察數(shù)據(jù)的變化
數(shù)據(jù)分析:
- 查找到進(jìn)程id
- 跟蹤進(jìn)程id查看內(nèi)存數(shù)據(jù)變化:
命令:jstat -gc 19492 1000 1000 表示每隔1秒鐘打印1次統(tǒng)計(jì)信息笔咽,連續(xù)打印1000次:
- 通過幾十秒的運(yùn)行后搔预,我們結(jié)合上圖也能明顯觀察出內(nèi)存的一個(gè)變化情況
- 首先我們先看 EU的變化,EU代表的是Eden區(qū)內(nèi)存的使用情況:
- 最開始EU只使用了6M左右大小叶组,并且持續(xù)了一段時(shí)間拯田,代表這段時(shí)間其實(shí)就是我們的睡眠時(shí)間
- 后續(xù)開始增長(zhǎng)并且每秒都有變化,代表我們的loadData()方法開始執(zhí)行甩十,每秒增長(zhǎng)差不多5M左右大小船庇,跟我們的代碼一致
- 當(dāng)EU的占用已達(dá)到81303KB的時(shí)候吭产,再分配5M對(duì)象很明顯此時(shí)就無法分配了,這時(shí)就會(huì)觸發(fā)一次 Minor GC鸭轮!
- 注意:我們發(fā)現(xiàn)EU的大小一下降低到了4471KB差不多4M的內(nèi)存臣淤,這也說明了一次Minor GC回收了大部分對(duì)象
小結(jié):通過以上程序的運(yùn)行以及分析我們知道了,該程序每秒對(duì)象增速在5MB左右窃爷,大概在10幾秒左右會(huì)觸發(fā)一次Mionr GC邑蒋,并且通過 YGCT我們也能知道,一次Minor GC的耗時(shí)也就在8ms按厘,速度非骋降酰快!一次回收差不多接近80MB對(duì)象逮京,如果我們的Eden區(qū)是800MB內(nèi)存卿堂,那一次回收預(yù)估也就在80ms,對(duì)于系統(tǒng)而已幾乎沒有卡頓
-
我們繼續(xù)觀察數(shù)據(jù)懒棉,看看每次Mionr GC過后的存活對(duì)象有多少草描?
S1U代表的就是Survivor1區(qū)使用的內(nèi)存大小,我們發(fā)現(xiàn)觸發(fā)Minor GC后 S1U就有值了策严,大小為:1131KB, 代表從Eden區(qū)中存活的對(duì)象有1131kb對(duì)象內(nèi)移入了S1區(qū)陶珠,接近1MB的對(duì)象對(duì)于10MB的S1區(qū)來說還是很輕松的。
我們繼續(xù)觀察后續(xù)GC存活的對(duì)象有多少享钞?
通過后續(xù)的數(shù)據(jù)我們可以發(fā)現(xiàn),第二次GC后剩余存活1387KB诀蓉,第三次GC過后剩余存活1475KB栗竖,增長(zhǎng)量幾乎在幾十KB范圍很小,而且后續(xù)的GC耗時(shí)更短渠啤,我們幾乎可以斷定該系統(tǒng)運(yùn)行非常良好狐肢!幾乎不會(huì)發(fā)生Full GC。