寶雞品牌網(wǎng)站建設百度關鍵字搜索排名
常用內(nèi)存選項
-Xmx: 最大堆大小
-Xms:最小堆大小
-Xss :線程堆棧大小,默認1M
生產(chǎn)環(huán)境最好保持 Xms = Xmx
java內(nèi)存研究
內(nèi)存布局
可見:
- 堆大小 = 新生代 + 老年代,新生代=E+From Survivor+To Survivor。新生代和老年代的比例通過-XX:NewRatio=2選項指定,新生代內(nèi)部E/S0/S1的比例用-XX:SurvivorRatio=8選項指定。
- -Xmx和-Xms設置的是堆大小,即設置的是新生代和老年代
- M(即永久代)不算在堆里面。永久代存放的是類的元數(shù)據(jù)信息(比如類名、屬性、方法、訪問限制等)。動態(tài)代理生成的類定義也會存放在永久代,所以如果動態(tài)生成的類太多,永久代空間就會不夠,這一點需要注意(參看這里)
對于某個服務,我們通過
-Xmx256m -Xms256m
設置堆大小為256M,但服務跑起來后,通過top命令查看,它占了500M的物理內(nèi)存。不要奇怪,剩下的200多M是被方法區(qū)、線程堆棧等占用了。
E/O的比例
新生代和老年代的比例默認是1:2,通過-XX:NewRatio=2選項指定,我們可通過jstat -gc命令查看結(jié)果來印證:
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
16384.0 16384.0 3776.0 0.0 54272.0 28511.2 175104.0 100739.4 73816.0 70718.7 9344.0 8572.7 408 4.010 7 1.666 5.676
EC是Eden區(qū)大小,則新生代總大小為S0+S1+E=87040。
OC為老年代大小:175104,我們看到,剛好1:2的比例。
新生代到老年代的轉(zhuǎn)化
新生代是 GC 收集垃圾的頻繁區(qū)域。
當對象在 Eden ( 包括一個 Survivor 區(qū)域,這里假設是 from 區(qū)域 ) 出生后,在經(jīng)過一次 Minor GC 后,如果對象還存活,并且能夠被另外一塊 Survivor 區(qū)域所容納
( 上面已經(jīng)假設為 from 區(qū)域,這里應為 to 區(qū)域,即 to 區(qū)域有足夠的內(nèi)存空間來存儲 Eden 和 from 區(qū)域中存活的對象 ),則使用復制算法將這些仍然還存活的對象復制到另外一塊 Survivor 區(qū)域 ( 即 to 區(qū)域 ) 中,然后清理所使用過的 Eden 以及 Survivor 區(qū)域 ( 即 from 區(qū)域 ),并且將這些對象的年齡設置為1,以后對象在 Survivor 區(qū)每熬過一次 Minor GC,就將對象的年齡 + 1,當對象的年齡達到某個值時 ( 默認是 15 歲,可以通過參數(shù) -XX:MaxTenuringThreshold 來設定 ),這些對象就會成為老年代。
但這也不是一定的,對于一些較大的對象 ( 即需要分配一塊較大的連續(xù)內(nèi)存空間 ) 則是直接進入到老年代。
From Survivor區(qū)域與To Survivor區(qū)域是交替切換空間,在同一時間內(nèi)兩者中只有一個不為空。jstat -gc結(jié)果里的S0和S1就是這2個survivor區(qū)。
方法區(qū)
方法區(qū)存放類定義和元信息,像字符串池和類的靜態(tài)成員不在方法區(qū)里,而是放在堆上。
Major GC 和 Full GC 的區(qū)別
Full GC:收集young gen、old gen、perm gen
Major GC:有時又叫 old gc ,只收集老年代( old gen )
Minor GC:只收集新生代(young gen)。
生產(chǎn)問題案例
JVM堆使用率居高不下
現(xiàn)象:JVM堆使用率過很久才能降下來。
原因是:程序內(nèi)未做分批處理,一次性分配了大片連續(xù)內(nèi)存(常見于ArrayList中),導致新生代區(qū)域不夠分,所以分到了老年代。老年代只能在執(zhí)行頻度更低、執(zhí)行速度更慢的major gc里清理,而不會在頻繁執(zhí)行、速度更快的minor gc里清理,所以導致JVM堆內(nèi)存占用要過一段較長的時間才能看到下降。實際中遇到過2萬條記錄在8G內(nèi)存的容器里就占用了2G堆內(nèi)存的情況。
線程間歇性發(fā)呆
現(xiàn)象:一個函數(shù)內(nèi)的兩個步驟間并無耗時操作,但線程卻仿佛發(fā)呆了幾秒不做事
可能的原因:
- 服務內(nèi)的數(shù)據(jù)庫、redis連接池耗盡,線程掛起等可用的連接;
- cpu和內(nèi)存占用率很高
- 日志打印過多,寫日志操作阻塞了線程,比如在一個2C接口里去打印異常堆棧
定位手段:
既然是線程阻塞,就用jstack命令dump出線程堆棧,查看可疑的阻塞。這跟早期C++里用gdb的thread apply all bt 命令查看堆棧一樣。