1.前言
由于項(xiàng)目的人員變動往核,接手了現(xiàn)在的項(xiàng)目荒典,項(xiàng)目前期的部署和試點(diǎn)運(yùn)營都沒有出現(xiàn)什么重大問題遗座,就在年初的項(xiàng)目全部鋪開的時(shí)候,由于并發(fā)量上來之后出現(xiàn)了頻繁的服務(wù)重啟的現(xiàn)象扼仲。后面經(jīng)過排查發(fā)現(xiàn)是內(nèi)存發(fā)生溢出導(dǎo)致的服務(wù)隔段時(shí)間就會重啟远寸。
在講內(nèi)存溢出之前先講回顧一些Java內(nèi)存結(jié)構(gòu):
1)程序計(jì)數(shù)器:當(dāng)前字節(jié)碼所執(zhí)行的字節(jié)碼的行號指示器;
2)Java虛擬機(jī)棧:線程私有屠凶,用于存儲局部變量驰后、操作棧、動態(tài)鏈接矗愧、方法出口等信息灶芝;
3)本地方法棧:與虛擬機(jī)棧的區(qū)別就是虛擬機(jī)棧運(yùn)行的是Java方法,本地方法棧運(yùn)行的是本地方法唉韭;
4)Java堆:存儲Java對象實(shí)例笙以;
5)方法區(qū):線程共享活烙,存儲已經(jīng)被虛擬機(jī)加載的類信息割岛、常量籽慢、靜態(tài)變量、及時(shí)編譯器編譯后的代碼等數(shù)據(jù)住诸;
6)運(yùn)行常量池:方法區(qū)的一部分驾胆,用于存放編譯期生成的各種字面量和符號引用涣澡。
7)直接內(nèi)存:直接內(nèi)存的分配不會受到Java堆的限制,在Java1.4之后引入的一種基于通道和緩沖的方式丧诺,它可以使用Native方法分配堆外內(nèi)存入桂。
現(xiàn)場問題1
問題現(xiàn)象:
現(xiàn)場正常運(yùn)行一段時(shí)間之后服務(wù)就會發(fā)生重啟,并且發(fā)生的時(shí)候總是在訪問高峰期驳阎,一開始懷疑是內(nèi)存設(shè)置太锌钩睢(事實(shí)證明內(nèi)存也確實(shí)設(shè)置的小了),在調(diào)整內(nèi)存之后還是會發(fā)生服務(wù)重啟的現(xiàn)象搞隐。
排查思路:
這個(gè)時(shí)候意識到可能是內(nèi)存發(fā)生了溢出,于是增加gc日志(在虛擬機(jī)啟動參數(shù)中添加-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:home/ssd/log/gc.log -XX:GCLogFileSize=1024M參數(shù))远搪,運(yùn)行一段時(shí)間服務(wù)發(fā)生重啟劣纲,查看gc回收日志。gc日志在發(fā)生服務(wù)重啟的時(shí)候回收也是正常的谁鳍,設(shè)置的內(nèi)存并沒有消耗完全癞季。為了快速出現(xiàn)內(nèi)存溢出的現(xiàn)象,我們把內(nèi)存調(diào)整為1.5g倘潜,采用默認(rèn)的參數(shù)設(shè)置绷柒。
如圖是發(fā)生內(nèi)存溢出gc的狀態(tài)圖:
Gc回收狀態(tài)圖,可以看到圈紅的地方是老年代的占用涮因,oc是分配的內(nèi)存废睦,ou是使用的內(nèi)存,還遠(yuǎn)沒有達(dá)到內(nèi)存的的設(shè)置閾值养泡。也可以查看更加詳細(xì)的gc信息嗜湃,由于內(nèi)存并未使用完全,因此回收細(xì)節(jié)就不再關(guān)注了澜掩。查看了gc日志购披,斷定服務(wù)重啟問題并不是堆內(nèi)存不足導(dǎo)致的。
為了驗(yàn)證我的猜想肩榕,我快速重啟服務(wù)之后監(jiān)控內(nèi)存增長并定時(shí)dump內(nèi)存快照刚陡,將內(nèi)存dump下來之后(jmap -dump:format=b,file=/home/ssd/log/Scheduler/java.dump pid)使用mat分析,發(fā)現(xiàn)內(nèi)存占用只用了50MB多株汉,這個(gè)這個(gè)時(shí)候懷疑是堆外內(nèi)存發(fā)生了溢出筐乳,于是就對堆外內(nèi)存進(jìn)行監(jiān)控。如圖為服務(wù)重啟前的內(nèi)存快照乔妈,堆內(nèi)存設(shè)置的1.5g哥童,服務(wù)重啟內(nèi)存的占用不到60MB:
到此為止已經(jīng)確認(rèn)是堆外內(nèi)存造成的,開始監(jiān)控堆外內(nèi)存占用褒翰。首先對vm參數(shù)添加-XX:NativeMemoryTracking=detail贮懈,表示詳細(xì)輸出堆外內(nèi)存占用匀泊,輸出指令jcmd ProsessPID VM.native_memory summary scale=MB《淠悖可以使用cron定時(shí)任務(wù)將輸出的結(jié)果輸出到對應(yīng)的文件中各聘,我是使用的Java定時(shí)任務(wù)將結(jié)果輸出到指定文件中分析,當(dāng)然也可以肉眼觀察變化抡医,使用Runtime調(diào)用服務(wù)指令輸出信息到文件躲因。如圖為其中一個(gè)時(shí)刻的堆外內(nèi)存占用圖,很明顯看到Thread占用異常忌傻,線程數(shù)占用到達(dá)了18610個(gè)大脉,并且還在不斷增長。
發(fā)現(xiàn)線程數(shù)量異常水孩,因此判斷內(nèi)存溢出極有可能是線程數(shù)增加導(dǎo)致內(nèi)存溢出镰矿,下一步就是找出產(chǎn)生異常線程的原因,通過堆棧信息發(fā)現(xiàn)在內(nèi)存溢出之前有大量的線程狀態(tài)都是wating狀態(tài)俘种,并且等待的都是同一把鎖秤标,迅速判斷這些線程都是什么線程,最后分析發(fā)現(xiàn)都是同一類線程宙刘,這些線程都有一個(gè)特點(diǎn):
這些線程等待的都是同一把鎖苍姜、而且都是同一個(gè)線程池產(chǎn)生的線程(這里還是吐槽一下之前的同事,為啥線程不加名字悬包。衙猪。。哈哈布近,誰讓他們增加了我的排查難度屈嗤。)。根據(jù)堆棧信息找到了鎖的位置吊输,發(fā)現(xiàn)是心跳處理線程饶号。線程不斷增長的原因是服務(wù)之間存在超時(shí)處理,由于我們服務(wù)是一個(gè)管理節(jié)點(diǎn)季蚂,有服務(wù)注冊中心功能茫船,其它服務(wù)注冊進(jìn)來的心跳超時(shí)時(shí)間是2s,注冊服務(wù)在2s之內(nèi)沒有收到心跳信息就會再次發(fā)出心跳請求扭屁,由于業(yè)務(wù)原因心跳必須一直保持算谈,如此不斷反復(fù)就導(dǎo)致了線程數(shù)量的增長。而在高峰期的時(shí)候由于訪問量增大料滥,心跳處理業(yè)務(wù)增加然眼,對于鎖的占用時(shí)間拉長,導(dǎo)致超時(shí)心跳增加葵腹,如此惡性循環(huán)高每。
首先解決心跳線程無限增長問題屿岂,心跳是通過thrift傳輸?shù)模覀冞@邊是服務(wù)端鲸匿,我們限制了服務(wù)端得到線程池大小爷怀,保證線程數(shù)量在可控范圍內(nèi);其次解決鎖的問題带欢,解決方案就是去鎖操作运授,鎖的對象是其他服務(wù)的運(yùn)行信息,采用的list結(jié)構(gòu)乔煞,后來決定采用map結(jié)構(gòu)吁朦,在不修改數(shù)據(jù)的情況下直接從內(nèi)存中獲取對象信息;最后就是解決耗時(shí)多的問題渡贾,這里沒有更好的辦法逗宜,就只能是每個(gè)處理邏輯的地方都加打印,找出耗時(shí)的地方剥啤,然后一個(gè)一個(gè)地優(yōu)化锦溪,優(yōu)化之后一個(gè)心跳的處理耗時(shí)減少到50ms以內(nèi)不脯,大大小于2s的超時(shí)設(shè)置府怯。
在解決這些問題值之后最內(nèi)存的大小也做了一些調(diào)優(yōu),服務(wù)運(yùn)行到現(xiàn)在就再也沒有發(fā)生重啟的問題了防楷。
現(xiàn)場問題2
這個(gè)現(xiàn)場問題比較簡單牺丙,只是堆內(nèi)存溢出,完全就是內(nèi)存不夠用導(dǎo)致的复局,在這里也總結(jié)一下排查思路冲簿。
這個(gè)集群的規(guī)模是比較小的規(guī)模,服務(wù)的內(nèi)存設(shè)置是1g亿昏,由于我們的業(yè)務(wù)模式的原因?qū)е旅看卧L問的數(shù)據(jù)的大小未做限制峦剔,為了保證處理速度我們會對消息進(jìn)行緩存,在處理的時(shí)候全部都是內(nèi)存處理角钩,原來是按照5k報(bào)文和20000條消息的緩存設(shè)置的內(nèi)存大小吝沫。一開始用戶也是按照這種限制來操作的,后來用戶對消息進(jìn)行打包處理递礼,每個(gè)請求由原來的1個(gè)報(bào)文合并到200個(gè)報(bào)文大小惨险,直接導(dǎo)致原來設(shè)置的內(nèi)存不夠用,導(dǎo)致的內(nèi)存不足溢出脊髓。
下面來看下排查過程辫愉,線上環(huán)境一直都沒有出現(xiàn)過問題,突然間就出現(xiàn)了內(nèi)存溢出的現(xiàn)象将硝,而且是頻繁的發(fā)生恭朗,幾乎是1分鐘內(nèi)就會發(fā)生一次屏镊,現(xiàn)場業(yè)務(wù)全部暫停,現(xiàn)場技術(shù)支持表示沒有任何改動冀墨。在接到這個(gè)問題之后第一時(shí)間就是觀察線上環(huán)境闸衫,使用jstat –gc pid 5000也是就是每5s輸出一次內(nèi)存回收信息,如圖1所示:
oc表示老年代的內(nèi)存大小诽嘉,ou表示老年代使用的內(nèi)存大小蔚出,可以看出老年代已經(jīng)滿了,此時(shí)可以確認(rèn)內(nèi)存不足導(dǎo)致虫腋,因此將內(nèi)存dump下來進(jìn)行分析骄酗,查看是什么占用了大量的內(nèi)存。如圖2所示悦冀,使用mat進(jìn)行分析:
經(jīng)過分析a占用了大部分內(nèi)存趋翻,而這部分正是我們緩存的消息數(shù)據(jù)。正好接近10000條消息盒蟆,因此我們懷疑是消息的數(shù)據(jù)太大導(dǎo)致的內(nèi)存不足踏烙。
為了驗(yàn)證我的猜想我在代碼中增加了對象大小的輸出RamUsageEstimator.sizeOf(),替換上去運(yùn)行一段時(shí)間之后發(fā)現(xiàn)有大量91032字節(jié)的對象历等,此時(shí)就可以完全判斷是由于對象太大導(dǎo)致的內(nèi)存溢出讨惩,在不影響運(yùn)行規(guī)格的情況下我們限制了緩存的大小為5000條,并且對報(bào)文大小進(jìn)行了控制寒屯,到此問題就算是完全解決了荐捻。
Java內(nèi)存思考
經(jīng)過上面的兩個(gè)問題,我重新思考了一下程序的內(nèi)存設(shè)置和一些邊界問題寡夹。
這里將Java內(nèi)存分為堆內(nèi)存(heap)和堆外內(nèi)存兩個(gè)部分处面。堆內(nèi)存是在虛擬機(jī)啟動的時(shí)候設(shè)置的虛擬機(jī)參數(shù),因此這部分內(nèi)存我們是很容器控制的菩掏,那么堆外內(nèi)存對于我們來說就相對更加隱蔽一些魂角,如果不是一些內(nèi)存限制嚴(yán)格或者堆外內(nèi)存溢出的情況發(fā)生,很難注意到這部分內(nèi)存智绸。那么什么是堆外內(nèi)存呢野揪?
與堆內(nèi)存對應(yīng),堆外內(nèi)存就是內(nèi)存對象分配在Java虛擬機(jī)的堆以外的內(nèi)存传于,這些內(nèi)存是直接從操作系統(tǒng)申請的囱挑。如我們經(jīng)常使用的java.nio.DirectByteBuffer類就是使用堆外內(nèi)存管理的,一些通信框架的內(nèi)存如netty也存在的堆外內(nèi)存沼溜;垃圾回收使用的一部分內(nèi)存也屬于堆外內(nèi)存平挑;線程占用的內(nèi)存;即時(shí)編譯器編譯之后的字節(jié)碼等都屬于堆外內(nèi)存管理。對于堆外內(nèi)存的監(jiān)控可以使用vm參數(shù)通熄,-XX:NativeMemoryTracking=detail唆涝,
輸入指令:
jcmd ProsessPID VM.native_memory summary scale=MB即可。輸出的數(shù)據(jù)如下圖:
可以看到整個(gè)memory主要包含了Java Heap唇辨、Class廊酣、Thread、Code赏枚、GC亡驰、Compiler、Internal饿幅、Other凡辱、Symbol、Native Memory Tracking栗恩、Arena Chunk這幾部分透乾;
其中reserved表示應(yīng)用可用的內(nèi)存大小,committed表示應(yīng)用正在使用的內(nèi)存大小
--Java Heap部分表示heap內(nèi)存目前占用了1536MB磕秤;
--Class部分表示已經(jīng)加載的classes個(gè)數(shù)為7472乳乌,其metadata占用了70MB;
--Thread部分表示目前有137個(gè)線程市咆,占用了137MB汉操;
--Code部分表示JIT生成的或者緩存的instructions占用了42MB;
--GC部分表示目前已經(jīng)占用了83MB的內(nèi)存空間用于幫助GC床绪;
--Internal部分表示命令行解析客情、JVMTI等占用了116MB其弊;
--Symbol部分表示諸如string table及constant pool等symbol占用了9MB癞己;
--Native Memory Tracking部分表示該功能自身占用了5MB;
Arena Chunk部分表示arena chunk占用了63MB,一個(gè)arena表示使用malloc分配的一個(gè)memory chunk梭伐,這些chunks可以被其他subsystems做為臨時(shí)內(nèi)存使用痹雅,比如pre-thread的內(nèi)存分配,它的內(nèi)存釋放是成bulk的糊识。commit是實(shí)際使用的內(nèi)存大小绩社。
堆內(nèi)存溢出問題比較容易排查,直接查看堆快照赂苗,找出占用內(nèi)存對象愉耙,很容易就可以找出溢出的原因;堆外內(nèi)存溢出問題比較難排查拌滋。
一個(gè)Java對象到底占用多大內(nèi)存
上面講到一個(gè)內(nèi)存計(jì)算組件RamUsageEstimator.sizeOf()朴沿,通過計(jì)算可以輸出單個(gè)對象占用的內(nèi)存大小,也可以采用openjdk的jol或者Instrumentation,那么一個(gè)Java對象占用的內(nèi)存大小是多少呢赌渣?Java對象內(nèi)存構(gòu)成是有三部分組成:
1)對象頭
Java對象頭由兩部分組成:一部分是Java對象運(yùn)行的數(shù)據(jù)(mark word)魏铅,比如hashCode、gc分帶年齡坚芜、鎖狀態(tài)標(biāo)志等览芳,這部分占用的內(nèi)存在32位虛擬機(jī)和64位虛擬機(jī)的大小分別是4B和8B;另一部分是Java對象指針(kclass)鸿竖,Java虛擬機(jī)通過這個(gè)指針判斷這個(gè)對象是哪個(gè)類的實(shí)例沧竟,如果是數(shù)組,對象頭中還必須有一塊表示數(shù)組大小的數(shù)據(jù)缚忧,因?yàn)镴ava虛擬機(jī)可以直接訪問對象元數(shù)據(jù)指導(dǎo)對象的大小屯仗,但是不能訪問數(shù)組的元數(shù)據(jù)來計(jì)算數(shù)組的大小。這部分大小在32位虛擬機(jī)和64位虛擬機(jī)的大小分別是4B和8B搔谴。
另外64位虛擬機(jī)中存在指針壓縮的問題魁袜,在閱讀elasticsearch官方文檔的時(shí)候有提到一個(gè)32g的概念,意思就是Java虛擬機(jī)的堆內(nèi)存申請?jiān)?2g以內(nèi)可以啟動指針壓縮達(dá)到節(jié)省空間的效果敦第,如果超過32g以上的指針壓縮就會失效峰弹,實(shí)際占用的內(nèi)存就會增多,也就是說超過32g的內(nèi)存不一定會比32g內(nèi)存存儲更多的對象芜果。
關(guān)于指針壓縮可以通過-XX:+UseCompressedOops來支持鞠呈,如果是打開的你們指針就會壓縮。
Java指針是用來存放內(nèi)存地址的右钾,在32位機(jī)器上內(nèi)存是通過一個(gè)32位二進(jìn)制數(shù)來存儲內(nèi)存地址蚁吝,操作系統(tǒng)中1個(gè)內(nèi)存單位長度是1byte=8bit,所以一個(gè)指針占用的內(nèi)存大小就是4個(gè)字節(jié)舀射,那么64位的二進(jìn)制數(shù)就是8個(gè)字節(jié)的大小窘茁。至于為什么是32g,主要原因是指針存儲的不再是內(nèi)存地址而是內(nèi)存地址的偏移量脆烟,所有在32g之前都是可以用32位指針表示的山林。
那么在使用指針壓縮的情況下,64位的對象頭就變成了12字節(jié)=Mark Work(8B) + kclass(4B).
2)實(shí)例數(shù)據(jù):
Java對象實(shí)例數(shù)據(jù)中有兩種:一種是8種基本類型邢羔;一種實(shí)例數(shù)據(jù)也是對象驼抹;
這里的實(shí)例變量要與棧中的實(shí)例變量進(jìn)行區(qū)分,Java棧里面保存的是局部變量表拜鹤、動態(tài)鏈接框冀、操作數(shù)棧、方法返回地址敏簿、附加信息明也,其中的局部變量表存儲的就是局部變量,而如果基本類型是對象 的實(shí)例變量則直接存儲到對象中,也就是堆內(nèi)存中诡右,當(dāng)然靜態(tài)變量final修飾的變量會存儲在方法區(qū)安岂。需要注意的是引用類型的大小是4字節(jié)。
3)對齊填充:
Java規(guī)定內(nèi)存對象的其實(shí)地址必須是8的倍數(shù)帆吻,因此對象的大小必須是8的倍數(shù)域那,舉個(gè)例子上面講的對象頭,通過指針壓縮之后的大小是12B猜煮,按照J(rèn)ava的規(guī)范次员,實(shí)際占用的內(nèi)存就是16B,不夠的部分就是進(jìn)行填充王带。數(shù)組由于存在一個(gè)數(shù)組大小的計(jì)算淑蔚,比普通對象多出4B,數(shù)組的對象頭為16B愕撰。
4)內(nèi)存計(jì)算實(shí)例
public class ClassDemo {
private boolean bo;
private char ch;
private int in;
private float fl;
private long lo;
private double dou;
public ClassDemo(boolean bo,char ch,int in,float fl,long lo,double dou) {
this.bo = bo;
this.ch = ch;
this.in = in;
this.fl = fl;
this.lo = lo;
this.dou = dou;
}
}
內(nèi)存計(jì)算:1B(boolean)+2B(char)+4B(int)+4B(float)+8B(double)+8B(long)+12B(對象頭)=39B刹衫,再加上對齊的1B為40B,時(shí)間監(jiān)控結(jié)果也是符合我們的計(jì)算的搞挣。
如下結(jié)果:
com.hikvision.cache.ClassDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 db ec 2d (00000001 11011011 11101100 00101101) (770497281)
4 4 (object header) 18 00 00 00 (00011000 00000000 00000000 00000000) (24)
8 4 (object header) 18 02 a7 16 (00011000 00000010 10100111 00010110) (380043800)
12 4 int ClassDemo.in 2
16 8 long ClassDemo.lo 3
24 8 double ClassDemo.dou 3.0
32 4 float ClassDemo.fl 3.0
36 2 char ClassDemo.ch c
38 1 boolean ClassDemo.bo false
39 1 (loss due to the next object alignment)
Instance size: 40 bytes
內(nèi)存溢出關(guān)注點(diǎn)主要有以下幾種:
1)堆內(nèi)存
可以通過-Xmx和-Xms參數(shù)控制带迟,如果堆內(nèi)存中沒有完成實(shí)例分配,并且堆內(nèi)存也無法擴(kuò)展囱桨,將會跑出OutOfMemoryError仓犬;
2)java虛擬機(jī)棧
棧內(nèi)存溢出有兩種情況:如果線程的棧深度大于虛擬機(jī)允許的深度,將拋出SrackOverFlowError異常舍肠;如果虛擬機(jī)可以動態(tài)擴(kuò)展搀继,當(dāng)擴(kuò)展無法盛情難到足夠的內(nèi)存是就會拋出StackOverflowError異常。
棧深度——虛擬機(jī)棧最多存儲的變量和計(jì)算結(jié)果值占用的深度大小翠语。
3)本地方法
本地方法會拋出StackOverflowError和OutOfMemoryError異常叽躯。
4)方法區(qū)
當(dāng)方法區(qū)無法分配內(nèi)存時(shí),將拋出OutOfMemoryError異常啡专。
5)Direct Memory
可以通過-XX:MaxDirectMemorySize調(diào)整大小险毁,內(nèi)存不足時(shí)拋出OutOfMemoryError或者OutOfMemoryError:Direct buffer memory
6)Socket 緩存區(qū)
java創(chuàng)建socket默認(rèn)的緩沖區(qū)也占用一定內(nèi)存(recieve和send分別默認(rèn)37k和25k)制圈,當(dāng)連接數(shù)多時(shí)無法分配會拋出IOException:Too many open files異常们童。
上面就是在發(fā)生兩次內(nèi)存溢出之后的總結(jié),后面也對邊界的一些問題進(jìn)行了修改以及對之前部分代碼進(jìn)行重構(gòu)鲸鹦,接口限流慧库,服務(wù)降級,線程池優(yōu)化等馋嗜。
關(guān)于實(shí)際項(xiàng)目接口和內(nèi)存的一些優(yōu)化和測試
1.1 接口性能測試
針對項(xiàng)目中的實(shí)時(shí)比對接口的性能較差的問題進(jìn)行了優(yōu)化齐板,接口通過批量的方式進(jìn)行處理,處理之后也是按照批量入庫的方式,在相同條件下的測試接口顯示甘磨,優(yōu)化后的接口耗時(shí)平均比優(yōu)化之后的1/4的耗時(shí)橡羞,性能提升大概是在75%左右。以上的測試結(jié)果的在正向測試得出的济舆。在模擬的環(huán)境下得出的結(jié)果卿泽,在實(shí)際項(xiàng)目中還有待驗(yàn)證,例如在cpu不足的情況下的耗時(shí)問題以及在內(nèi)存不足的情況下的耗時(shí)問題滋觉,以及在現(xiàn)場的環(huán)境下的耗時(shí)問題签夭。
下面是按照220個(gè)任務(wù)打包的測試數(shù)據(jù),數(shù)據(jù)使用從0開始持續(xù)派發(fā)任務(wù)到達(dá)數(shù)據(jù)庫限制閾值為止椎侠,表1 是原始的數(shù)據(jù)第租,圖1是兩份數(shù)據(jù)的折線圖,實(shí)際的比例大概是在1/4;性能提升較為明顯:
優(yōu)化之前:
提交測試用例總的次數(shù):1232次我纪;總耗時(shí):1661407慎宾;平均耗時(shí):1348.5ms。
優(yōu)化之后:
提交測試用例總的次數(shù):1232次浅悉;總的耗時(shí):422646璧诵;平均耗時(shí):343ms。
表1優(yōu)化前后數(shù)據(jù)統(tǒng)計(jì)
如圖1位優(yōu)化前后面積圖仇冯,橫坐標(biāo)表示用例之宿,縱坐標(biāo)表示耗時(shí),單位ms苛坚。
具體修改點(diǎn)是將報(bào)文組裝進(jìn)行合并比被,在入庫的時(shí)候有原來的單個(gè)入庫改為批量入庫的方式。
1.2 內(nèi)存測試
1.2.1 內(nèi)存占用測試
內(nèi)存測試主要關(guān)注的是兩個(gè)方面泼舱,一個(gè)是內(nèi)存的限制大小和內(nèi)存占用的情況等缀;以上測試都是正向測試,采用的是模擬節(jié)點(diǎn)消費(fèi)任務(wù)娇昙,在調(diào)度占用內(nèi)存中尺迂,占用最大的還是任務(wù)節(jié)點(diǎn)的占用,因此冒掌,首先的測試維度就是報(bào)文大小對內(nèi)存占用的影響噪裕。
表2是在保留20000條任務(wù)的內(nèi)存占用情況:
圖1是內(nèi)存占用的曲線圖,在實(shí)際的內(nèi)存占用中Java內(nèi)存的對象轉(zhuǎn)換和內(nèi)存對齊等原因會有一定的范圍波動股毫,總的來說有一種線性關(guān)系膳音,也就是說內(nèi)存占用跟任務(wù)的報(bào)文大小有直接關(guān)系。報(bào)文對應(yīng)內(nèi)存折線圖:固定變量:內(nèi)存保留20000條數(shù)據(jù)铃诬,內(nèi)存設(shè)置大小1.5G祭陷;近視關(guān)系如下:
y=38x+39
x:報(bào)文大胁粤荨;
y:占用內(nèi)存
圖2 報(bào)文大小和內(nèi)存占用的關(guān)系
隊(duì)列大小和內(nèi)存的關(guān)系也是一種線性關(guān)系兵志,表3和圖3位隊(duì)列大小對內(nèi)存占用的影響醇蝴。
表3 隊(duì)列大小對內(nèi)存內(nèi)存的影響
圖3 任務(wù)隊(duì)列大小對內(nèi)存占用影響
隊(duì)列大小對內(nèi)存的影響:固定變量,1.5內(nèi)存想罕,10k報(bào)文大小;
近視關(guān)系:
y=0.021z-6
z:任務(wù)條數(shù)
y:內(nèi)存占用大小
實(shí)際測試中還測試了其它維度得到數(shù)據(jù)哑蔫,具體詳細(xì)數(shù)據(jù)可以查看測試文檔《內(nèi)存測試》。
以上內(nèi)存都是針對對內(nèi)存大小進(jìn)行測試弧呐,測試過程中也針對總的內(nèi)存進(jìn)行了監(jiān)控闸迷,具體數(shù)據(jù)在《內(nèi)存測試》中都有,可以自行觀察俘枫,總的來說這部分內(nèi)存占用變化不大腥沽,和內(nèi)存設(shè)置、線程數(shù)等有關(guān)鸠蚪。
在8g內(nèi)存情況下今阳,項(xiàng)目分配的內(nèi)存為4g,依照數(shù)據(jù)推算出的調(diào)度的內(nèi)存設(shè)置大小如下表:
堆內(nèi)存設(shè)置:1.5g茅信;隊(duì)列大小控制5000條盾舌;接口限制5并發(fā)(20個(gè)任務(wù)打包tps超過10000);
1.2.2 內(nèi)存回收測試
在內(nèi)存設(shè)置的情況下內(nèi)存回收頻率比較高蘸鲸,跟提交的任務(wù)的并發(fā)有關(guān)系妖谴;如圖5tps越大回收頻率越高。具體數(shù)據(jù)請參考《內(nèi)存測試》
圖4 內(nèi)存每s回收次數(shù)和tps之間的關(guān)系圖
參考:
《深入理解Java虛擬機(jī):Java高級特性與實(shí)戰(zhàn)》
《Java虛擬機(jī)實(shí)戰(zhàn)》