JVM?調(diào)優(yōu)概述

JVM 調(diào)優(yōu)概述

性能定義

吞吐量 - 指不考慮 GC 引起的停頓時(shí)間或內(nèi)存消耗登馒,垃圾收集器能支撐應(yīng)用達(dá)到的最高性能指標(biāo)。延遲 - 其度量標(biāo)準(zhǔn)是縮短由于垃圾啊收集引起的停頓時(shí)間或者完全消除因垃圾收集所引起的停頓咆槽,避免應(yīng)用運(yùn)行時(shí)發(fā)生抖動(dòng)陈轿。內(nèi)存占用 - 垃圾收集器流暢運(yùn)行所需要的內(nèi)存數(shù)量。

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

GC 優(yōu)化的兩個(gè)目標(biāo):

將進(jìn)入老年代的對(duì)象數(shù)量降到最低減少 Full GC 的執(zhí)行時(shí)間

GC 優(yōu)化的基本原則是:將不同的 GC 參數(shù)應(yīng)用到兩個(gè)及以上的服務(wù)器上然后比較它們的性能秦忿,然后將那些被證明可以提高性能或減少 GC 執(zhí)行時(shí)間的參數(shù)應(yīng)用于最終的工作服務(wù)器上麦射。

將進(jìn)入老年代的對(duì)象數(shù)量降到最低

除了可以在 JDK7 及更高版本中使用的 G1 收集器以外,其他分代 GC 都是由 Oracle JVM 提供的灯谣。關(guān)于分代 GC潜秋,就是對(duì)象在 Eden 區(qū)被創(chuàng)建,隨后被轉(zhuǎn)移到 Survivor 區(qū)胎许,在此之后剩余的對(duì)象會(huì)被轉(zhuǎn)入老年代峻呛。也有一些對(duì)象由于占用內(nèi)存過(guò)大,在 Eden 區(qū)被創(chuàng)建后會(huì)直接被傳入老年代辜窑。老年代 GC 相對(duì)來(lái)說(shuō)會(huì)比新生代 GC 更耗時(shí)钩述,因此,減少進(jìn)入老年代的對(duì)象數(shù)量可以顯著降低 Full GC 的頻率穆碎。你可能會(huì)以為減少進(jìn)入老年代的對(duì)象數(shù)量意味著把它們留在新生代牙勘,事實(shí)正好相反,新生代內(nèi)存的大小是可以調(diào)節(jié)的惨远。

降低 Full GC 的時(shí)間

Full GC 的執(zhí)行時(shí)間比 Minor GC 要長(zhǎng)很多谜悟,因此话肖,如果在 Full GC 上花費(fèi)過(guò)多的時(shí)間(超過(guò) 1s),將可能出現(xiàn)超時(shí)錯(cuò)誤葡幸。


如果通過(guò)減小老年代內(nèi)存來(lái)減少 Full GC 時(shí)間最筒,可能會(huì)引起 OutOfMemoryError 或者導(dǎo)致 Full GC 的頻率升高。另外蔚叨,如果通過(guò)增加老年代內(nèi)存來(lái)降低 Full GC 的頻率床蜘,F(xiàn)ull GC 的時(shí)間可能因此增加。

因此蔑水,你需要把老年代的大小設(shè)置成一個(gè)“合適”的值邢锯。


GC 優(yōu)化需要考慮的 JVM 參數(shù)


類型參數(shù)描述堆內(nèi)存大小-Xms啟動(dòng) JVM 時(shí)堆內(nèi)存的大小-Xmx堆內(nèi)存最大限制新生代空間大小-XX:NewRatio新生代和老年代的內(nèi)存比-XX:NewSize新生代內(nèi)存大小-XX:SurvivorRatioEden 區(qū)和 Survivor 區(qū)的內(nèi)存比


GC 優(yōu)化時(shí)最常用的參數(shù)是-Xms,-Xmx和-XX:NewRatio。-Xms和-Xmx參數(shù)通常是必須的搀别,所以NewRatio的值將對(duì) GC 性能產(chǎn)生重要的影響丹擎。


有些人可能會(huì)問(wèn)如何設(shè)置永久代內(nèi)存大小,你可以用-XX:PermSize和-XX:MaxPermSize參數(shù)來(lái)進(jìn)行設(shè)置歇父,但是要記住蒂培,只有當(dāng)出現(xiàn)OutOfMemoryError錯(cuò)誤時(shí)你才需要去設(shè)置永久代內(nèi)存。


GC 優(yōu)化的過(guò)程


GC 優(yōu)化的過(guò)程和大多數(shù)常見(jiàn)的提升性能的過(guò)程相似榜苫,下面是筆者使用的流程:


1.監(jiān)控 GC 狀態(tài)


你需要監(jiān)控 GC 從而檢查系統(tǒng)中運(yùn)行的 GC 的各種狀態(tài)护戳。


2.分析監(jiān)控結(jié)果后決定是否需要優(yōu)化 GC


在檢查 GC 狀態(tài)后,你需要分析監(jiān)控結(jié)構(gòu)并決定是否需要進(jìn)行 GC 優(yōu)化垂睬。如果分析結(jié)果顯示運(yùn)行 GC 的時(shí)間只有 0.1-0.3 秒媳荒,那么就不需要把時(shí)間浪費(fèi)在 GC 優(yōu)化上,但如果運(yùn)行 GC 的時(shí)間達(dá)到 1-3 秒驹饺,甚至大于 10 秒钳枕,那么 GC 優(yōu)化將是很有必要的。


但是逻淌,如果你已經(jīng)分配了大約 10GB 內(nèi)存給 Java么伯,并且這些內(nèi)存無(wú)法省下,那么就無(wú)法進(jìn)行 GC 優(yōu)化了卡儒。在進(jìn)行 GC 優(yōu)化之前田柔,你需要考慮為什么你需要分配這么大的內(nèi)存空間,如果你分配了 1GB 或 2GB 大小的內(nèi)存并且出現(xiàn)了OutOfMemoryError骨望,那你就應(yīng)該執(zhí)行**堆快照(heap dump)**來(lái)消除導(dǎo)致異常的原因硬爆。


注意:

**堆快照(heap dump)**是一個(gè)用來(lái)檢查 Java 內(nèi)存中的對(duì)象和數(shù)據(jù)的內(nèi)存文件。該文件可以通過(guò)執(zhí)行 JDK 中的jmap命令來(lái)創(chuàng)建擎鸠。在創(chuàng)建文件的過(guò)程中缀磕,所有 Java 程序都將暫停,因此,不要在系統(tǒng)執(zhí)行過(guò)程中創(chuàng)建該文件袜蚕。

你可以在互聯(lián)網(wǎng)上搜索 heap dump 的詳細(xì)說(shuō)明糟把。

3.設(shè)置 GC 類型/內(nèi)存大小


如果你決定要進(jìn)行 GC 優(yōu)化,那么你需要選擇一個(gè) GC 類型并且為它設(shè)置內(nèi)存大小牲剃。此時(shí)如果你有多個(gè)服務(wù)器遣疯,請(qǐng)如上文提到的那樣,在每臺(tái)機(jī)器上設(shè)置不同的 GC 參數(shù)并分析它們的區(qū)別凿傅。


4.分析結(jié)果


在設(shè)置完 GC 參數(shù)后就可以開(kāi)始收集數(shù)據(jù)缠犀,請(qǐng)?jiān)谑占辽?24 小時(shí)后再進(jìn)行結(jié)果分析。如果你足夠幸運(yùn)聪舒,你可能會(huì)找到系統(tǒng)的最佳 GC 參數(shù)辨液。如若不然,你還需要分析輸出日志并檢查分配的內(nèi)存箱残,然后需要通過(guò)不斷調(diào)整 GC 類型/內(nèi)存大小來(lái)找到系統(tǒng)的最佳參數(shù)滔迈。


5.如果結(jié)果令人滿意,將參數(shù)應(yīng)用到所有服務(wù)器上并結(jié)束 GC 優(yōu)化


如果 GC 優(yōu)化的結(jié)果令人滿意疚宇,就可以將參數(shù)應(yīng)用到所有服務(wù)器上亡鼠,并停止 GC 優(yōu)化。


在下面的章節(jié)中敷待,你將會(huì)看到上述每一步所做的具體工作。


命令


jmap


jmap 即 JVM Memory Map仁热。


jmap 用于生成 heap dump 文件榜揖。


如果不使用這個(gè)命令,還可以使用 -XX:+HeapDumpOnOutOfMemoryError 參數(shù)來(lái)讓虛擬機(jī)出現(xiàn) OOM 的時(shí)候抗蠢,自動(dòng)生成 dump 文件举哟。


jmap 不僅能生成 dump 文件,還可以查詢 finalize 執(zhí)行隊(duì)列迅矛、Java 堆和永久代的詳細(xì)信息妨猩,如當(dāng)前使用率、當(dāng)前使用的是哪種收集器等秽褒。


命令格式:


jmap [option] LVMID


option 參數(shù):


dump - 生成堆轉(zhuǎn)儲(chǔ)快照f(shuō)inalizerinfo - 顯示在 F-Queue 隊(duì)列等待 Finalizer 線程執(zhí)行 finalizer 方法的對(duì)象heap - 顯示 Java 堆詳細(xì)信息histo - 顯示堆中對(duì)象的統(tǒng)計(jì)信息permstat - to print permanent generation statisticsF - 當(dāng)-dump 沒(méi)有響應(yīng)時(shí)壶硅,強(qiáng)制生成 dump 快照

示例:jmap -dump PID 生成堆快照


dump 堆到文件,format 指定輸出格式销斟,live 指明是活著的對(duì)象庐椒,file 指定文件名


$ jmap -dump:live,format=b,file=dump.hprof 28920


Dumping heap to /home/xxx/dump.hprof ...


Heap dump file created


dump.hprof 這個(gè)后綴是為了后續(xù)可以直接用 MAT(Memory Anlysis Tool)打開(kāi)。


示例:jmap -heap 查看指定進(jìn)程的堆信息


注意:使用 CMS GC 情況下蚂踊,jmap -heap 的執(zhí)行有可能會(huì)導(dǎo)致 java 進(jìn)程掛起约谈。


jmap -heap PID


[root@chances bin]# ./jmap -heap 12379


Attaching to process ID 12379, please wait...


Debugger attached successfully.


Server compiler detected.


JVM version is 17.0-b16


using thread-local object allocation.


Parallel GC with 6 thread(s)


Heap Configuration:


MinHeapFreeRatio = 40


MaxHeapFreeRatio = 70


MaxHeapSize = 83886080 (80.0MB)


NewSize = 1310720 (1.25MB)


MaxNewSize = 17592186044415 MB


OldSize = 5439488 (5.1875MB)


NewRatio = 2


SurvivorRatio = 8


PermSize = 20971520 (20.0MB)


MaxPermSize = 88080384 (84.0MB)


Heap Usage:


PS Young Generation


Eden Space:


capacity = 9306112 (8.875MB)


used = 5375360 (5.1263427734375MB)


free = 3930752 (3.7486572265625MB)


57.761608714788736% used


From Space:


capacity = 9306112 (8.875MB)


used = 3425240 (3.2665634155273438MB)


free = 5880872 (5.608436584472656MB)


36.80634834397007% used


To Space:


capacity = 9306112 (8.875MB)


used = 0 (0.0MB)


free = 9306112 (8.875MB)


0.0% used


PS Old Generation


capacity = 55967744 (53.375MB)


used = 48354640 (46.11457824707031MB)


free = 7613104 (7.2604217529296875MB)


86.39733629427693% used


PS Perm Generation


capacity = 62062592 (59.1875MB)


used = 60243112 (57.452308654785156MB)


free = 1819480 (1.7351913452148438MB)


97.06831451706046% used


jstack


jstack 用于生成 java 虛擬機(jī)當(dāng)前時(shí)刻的線程快照。


線程快照是當(dāng)前 java 虛擬機(jī)內(nèi)每一條線程正在執(zhí)行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現(xiàn)長(zhǎng)時(shí)間停頓的原因棱诱,如線程間死鎖泼橘、死循環(huán)、請(qǐng)求外部資源導(dǎo)致的長(zhǎng)時(shí)間等待等迈勋。


線程出現(xiàn)停頓的時(shí)候通過(guò) jstack 來(lái)查看各個(gè)線程的調(diào)用堆棧侥加,就可以知道沒(méi)有響應(yīng)的線程到底在后臺(tái)做什么事情,或者等待什么資源粪躬。 如果 java 程序崩潰生成 core 文件担败,jstack 工具可以用來(lái)獲得 core 文件的 java stack 和 native stack 的信息,從而可以輕松地知道 java 程序是如何崩潰和在程序何處發(fā)生問(wèn)題镰官。另外提前,jstack 工具還可以附屬到正在運(yùn)行的 java 程序中,看到當(dāng)時(shí)運(yùn)行的 java 程序的 java stack 和 native stack 的信息, 如果現(xiàn)在運(yùn)行的 java 程序呈現(xiàn) hung 的狀態(tài)泳唠,jstack 是非常有用的狈网。


命令格式:


jstack [option] LVMID


option 參數(shù):


-F - 當(dāng)正常輸出請(qǐng)求不被響應(yīng)時(shí),強(qiáng)制輸出線程堆棧-l - 除堆棧外笨腥,顯示關(guān)于鎖的附加信息-m - 如果調(diào)用到本地方法的話拓哺,可以顯示 C/C++的堆棧

jps


jps(JVM Process Status Tool),顯示指定系統(tǒng)內(nèi)所有的 HotSpot 虛擬機(jī)進(jìn)程脖母。


命令格式:


jps [options] [hostid]


option 參數(shù):


-l - 輸出主類全名或 jar 路徑-q - 只輸出 LVMID-m - 輸出 JVM 啟動(dòng)時(shí)傳遞給 main()的參數(shù)-v - 輸出 JVM 啟動(dòng)時(shí)顯示指定的 JVM 參數(shù)

其中[option]士鸥、[hostid]參數(shù)也可以不寫(xiě)。


$ jps -l -m


28920 org.apache.catalina.startup.Bootstrap start


11589 org.apache.catalina.startup.Bootstrap start


25816 sun.tools.jps.Jps -l -m


jstat


jstat(JVM statistics Monitoring)谆级,是用于監(jiān)視虛擬機(jī)運(yùn)行時(shí)狀態(tài)信息的命令烤礁,它可以顯示出虛擬機(jī)進(jìn)程中的類裝載齿尽、內(nèi)存轧膘、垃圾收集、JIT 編譯等運(yùn)行數(shù)據(jù)掩浙。


命令格式:


jstat [option] LVMID [interval] [count]


參數(shù):


[option] - 操作參數(shù)LVMID - 本地虛擬機(jī)進(jìn)程 ID[interval] - 連續(xù)輸出的時(shí)間間隔[count] - 連續(xù)輸出的次數(shù)

jhat


jhat(JVM Heap Analysis Tool)舆绎,是與 jmap 搭配使用鲤脏,用來(lái)分析 jmap 生成的 dump,jhat 內(nèi)置了一個(gè)微型的 HTTP/HTML 服務(wù)器吕朵,生成 dump 的分析結(jié)果后猎醇,可以在瀏覽器中查看。


注意:一般不會(huì)直接在服務(wù)器上進(jìn)行分析边锁,因?yàn)?jhat 是一個(gè)耗時(shí)并且耗費(fèi)硬件資源的過(guò)程姑食,一般把服務(wù)器生成的 dump 文件復(fù)制到本地或其他機(jī)器上進(jìn)行分析。


命令格式:


jhat [dumpfile]


jinfo


jinfo(JVM Configuration info)茅坛,用于實(shí)時(shí)查看和調(diào)整虛擬機(jī)運(yùn)行參數(shù)音半。


之前的 jps -v 口令只能查看到顯示指定的參數(shù)则拷,如果想要查看未被顯示指定的參數(shù)的值就要使用 jinfo 口令


命令格式:


jinfo [option] [args] LVMID


option 參數(shù):


-flag : 輸出指定 args 參數(shù)的值-flags : 不需要 args 參數(shù),輸出所有 JVM 參數(shù)的值-sysprops : 輸出系統(tǒng)屬性曹鸠,等同于 System.getProperties()

HotSpot VM 參數(shù)


詳細(xì)參數(shù)說(shuō)明請(qǐng)參考官方文檔:Java HotSpot VM Options煌茬,這里僅列舉常用參數(shù)。

JVM 內(nèi)存配置


配置描述-Xms堆空間初始值彻桃。-Xmx堆空間最大值坛善。-XX:NewSize新生代空間初始值。-XX:MaxNewSize新生代空間最大值邻眷。-Xmn新生代空間大小眠屎。-XX:PermSize永久代空間的初始值。-XX:MaxPermSize永久代空間的最大值肆饶。


GC 類型配置


配置描述-XX:+UseSerialGC串行垃圾回收器-XX:+UseParallelGC并行垃圾回收器-XX:+UseParNewGC使用 ParNew + Serial Old 垃圾回收器組合-XX:+UseConcMarkSweepGC并發(fā)標(biāo)記掃描垃圾回收器-XX:ParallelCMSThreads=并發(fā)標(biāo)記掃描垃圾回收器 = 為使用的線程數(shù)量-XX:+UseG1GCG1 垃圾回收器


輔助配置


配置描述-XX:+PrintGCDetails打印 GC 日志-Xloggc:<filename>指定 GC 日志文件名-XX:+HeapDumpOnOutOfMemoryError內(nèi)存溢出時(shí)輸出堆快照文件


典型配置


堆大小設(shè)置


年輕代的設(shè)置很關(guān)鍵改衩。


JVM 中最大堆大小有三方面限制:


相關(guān)操作系統(tǒng)的數(shù)據(jù)模型(32-bt 還是 64-bit)限制;系統(tǒng)的可用虛擬內(nèi)存限制驯镊;系統(tǒng)的可用物理內(nèi)存限制葫督。

整個(gè)堆大小 = 年輕代大小 + 年老代大小 + 持久代大小


持久代一般固定大小為 64m。使用 -XX:PermSize 設(shè)置板惑。官方推薦年輕代占整個(gè)堆的 3/8橄镜。使用 -Xmn 設(shè)置。

回收器選擇


JVM 給了三種選擇:串行收集器冯乘、并行收集器洽胶、并發(fā)收集器。


JVM 實(shí)戰(zhàn)


分析 GC 日志


獲取 GC 日志


獲取 GC 日志有兩種方式:


使用命令動(dòng)態(tài)查看在容器中設(shè)置相關(guān)參數(shù)打印 GC 日志

jstat -gc 統(tǒng)計(jì)垃圾回收堆的行為:


jstat -gc 1262


S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT


26112.0 24064.0 6562.5 0.0 564224.0 76274.5 434176.0 388518.3 524288.0 42724.7 320 6.417 1 0.398 6.815


也可以設(shè)置間隔固定時(shí)間來(lái)打油:


$ jstat -gc 1262 2000 20


這個(gè)命令意思就是每隔 2000ms 輸出 1262 的 gc 情況妖异,一共輸出 20 次


Tomcat 設(shè)置示例:


JAVA_OPTS="-server -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m -XX:SurvivorRatio=4


-verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log


-Djava.awt.headless=true


-XX:+PrintGCTimeStamps -XX:+PrintGCDetails


-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000


-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15"


-Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m Xms,即為 jvm 啟動(dòng)時(shí)得 JVM 初始堆大小,Xmx 為 jvm 的最大堆大小领追,xmn 為新生代的大小,permsize 為永久代的初始大小响逢,MaxPermSize 為永久代的最大空間绒窑。-XX:SurvivorRatio=4 SurvivorRatio 為新生代空間中的 Eden 區(qū)和救助空間 Survivor 區(qū)的大小比值,默認(rèn)是 8舔亭,則兩個(gè) Survivor 區(qū)與一個(gè) Eden 區(qū)的比值為 2:8,一個(gè) Survivor 區(qū)占整個(gè)年輕代的 1/10些膨。調(diào)小這個(gè)參數(shù)將增大 survivor 區(qū),讓對(duì)象盡量在 survitor 區(qū)呆長(zhǎng)一點(diǎn)钦铺,減少進(jìn)入年老代的對(duì)象订雾。去掉救助空間的想法是讓大部分不能馬上回收的數(shù)據(jù)盡快進(jìn)入年老代,加快年老代的回收頻率矛洞,減少年老代暴漲的可能性洼哎,這個(gè)是通過(guò)將-XX:SurvivorRatio 設(shè)置成比較大的值(比如 65536)來(lái)做到烫映。-verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log 將虛擬機(jī)每次垃圾回收的信息寫(xiě)到日志文件中,文件名由 file 指定噩峦,文件格式是平文件锭沟,內(nèi)容和-verbose:gc 輸出內(nèi)容相同。-Djava.awt.headless=true Headless 模式是系統(tǒng)的一種配置模式识补。在該模式下族淮,系統(tǒng)缺少了顯示設(shè)備、鍵盤(pán)或鼠標(biāo)凭涂。-XX:+PrintGCTimeStamps -XX:+PrintGCDetails 設(shè)置 gc 日志的格式-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000 指定 rmi 調(diào)用時(shí) gc 的時(shí)間間隔-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15 采用并發(fā) gc 方式祝辣,經(jīng)過(guò) 15 次 minor gc 后進(jìn)入年老代

如何分析 GC 日志


Young GC 回收日志:


2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs]


Full GC 回收日志:


2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]


通過(guò)上面日志分析得出,PSYoungGen切油、ParOldGen蝙斜、PSPermGen 屬于 Parallel 收集器。其中 PSYoungGen 表示 gc 回收前后年輕代的內(nèi)存變化白翻;ParOldGen 表示 gc 回收前后老年代的內(nèi)存變化乍炉;PSPermGen 表示 gc 回收前后永久區(qū)的內(nèi)存變化。young gc 主要是針對(duì)年輕代進(jìn)行內(nèi)存回收比較頻繁滤馍,耗時(shí)短岛琼;full gc 會(huì)對(duì)整個(gè)堆內(nèi)存進(jìn)行回城,耗時(shí)長(zhǎng)巢株,因此一般盡量減少 full gc 的次數(shù)


通過(guò)兩張圖非常明顯看出 gc 日志構(gòu)成:




OutOfMemory(OOM)分析


OutOfMemory 槐瑞,即內(nèi)存溢出,是一個(gè)常見(jiàn)的 JVM 問(wèn)題阁苞。那么分析 OOM 的思路是什么呢困檩?


首先,要知道有三種 OutOfMemoryError:


OutOfMemoryError:Java heap space - 堆空間溢出OutOfMemoryError:PermGen space - 方法區(qū)和運(yùn)行時(shí)常量池溢出OutOfMemoryError:unable to create new native thread - 線程過(guò)多

OutOfMemoryError:PermGen space


OutOfMemoryError:PermGen space 表示方法區(qū)和運(yùn)行時(shí)常量池溢出那槽。


原因:


Perm 區(qū)主要用于存放 Class 和 Meta 信息的悼沿,Class 在被 Loader 時(shí)就會(huì)被放到 PermGen space,這個(gè)區(qū)域稱為年老代骚灸。GC 在主程序運(yùn)行期間不會(huì)對(duì)年老區(qū)進(jìn)行清理糟趾,默認(rèn)是 64M 大小。


當(dāng)程序程序中使用了大量的 jar 或 class甚牲,使 java 虛擬機(jī)裝載類的空間不夠义郑,超過(guò) 64M 就會(huì)報(bào)這部分內(nèi)存溢出了,需要加大內(nèi)存分配丈钙,一般 128m 足夠非驮。


解決方案:


(1)擴(kuò)大永久代空間


JDK7 以前使用 -XX:PermSize 和 -XX:MaxPermSize 來(lái)控制永久代大小。JDK8 以后把原本放在永久代的字符串常量池移出, 放在 Java 堆中(元空間 Metaspace)中雏赦,元數(shù)據(jù)并不在虛擬機(jī)中劫笙,使用的是本地的內(nèi)存芙扎。使用 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 控制元空間大小。

注意:-XX:PermSize 一般設(shè)為 64M

(2)清理應(yīng)用程序中 WEB-INF/lib 下的 jar邀摆,用不上的 jar 刪除掉纵顾,多個(gè)應(yīng)用公共的 jar 移動(dòng)到 Tomcat 的 lib 目錄,減少重復(fù)加載栋盹。


OutOfMemoryError:Java heap space


OutOfMemoryError:Java heap space 表示堆空間溢出施逾。


原因:JVM 分配給堆內(nèi)存的空間已經(jīng)用滿了。


問(wèn)題定位


(1)使用 jmap 或 -XX:+HeapDumpOnOutOfMemoryError 獲取堆快照例获。 (2)使用內(nèi)存分析工具(visualvm汉额、mat、jProfile 等)對(duì)堆快照文件進(jìn)行分析榨汤。 (3)根據(jù)分析圖蠕搜,重點(diǎn)是確認(rèn)內(nèi)存中的對(duì)象是否是必要的,分清究竟是是內(nèi)存泄漏(Memory Leak)還是內(nèi)存溢出(Memory Overflow)收壕。


內(nèi)存泄露


內(nèi)存泄漏是指由于疏忽或錯(cuò)誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存的情況妓灌。


內(nèi)存泄漏并非指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后蜜宪,由于設(shè)計(jì)錯(cuò)誤虫埂,失去了對(duì)該段內(nèi)存的控制,因而造成了內(nèi)存的浪費(fèi)圃验。


內(nèi)存泄漏隨著被執(zhí)行的次數(shù)越多-最終會(huì)導(dǎo)致內(nèi)存溢出掉伏。


而因程序死循環(huán)導(dǎo)致的不斷創(chuàng)建對(duì)象-只要被執(zhí)行到就會(huì)產(chǎn)生內(nèi)存溢出。


內(nèi)存泄漏常見(jiàn)幾個(gè)情況:


靜態(tài)集合類聲明為靜態(tài)(static)的 HashMap澳窑、Vector 等集合通俗來(lái)講 A 中有 B斧散,當(dāng)前只把 B 設(shè)置為空,A 沒(méi)有設(shè)置為空摊聋,回收時(shí) B 無(wú)法回收-因被 A 引用鸡捐。

監(jiān)聽(tīng)器監(jiān)聽(tīng)器被注冊(cè)后釋放對(duì)象時(shí)沒(méi)有刪除監(jiān)聽(tīng)器

物理連接DataSource.getConnection()建立鏈接,必須通過(guò) close()關(guān)閉鏈接

內(nèi)部類和外部模塊等的引用發(fā)現(xiàn)它的方式同內(nèi)存溢出麻裁,可再加個(gè)實(shí)時(shí)觀察jstat -gcutil 7362 2500 70

重點(diǎn)關(guān)注:


FGC — 從應(yīng)用程序啟動(dòng)到采樣時(shí)發(fā)生 Full GC 的次數(shù)闯参。FGCT — 從應(yīng)用程序啟動(dòng)到采樣時(shí) Full GC 所用的時(shí)間(單位秒)。FGC 次數(shù)越多悲立,F(xiàn)GCT 所需時(shí)間越多-可非常有可能存在內(nèi)存泄漏。

解決方案


(1)檢查程序新博,看是否有死循環(huán)或不必要地重復(fù)創(chuàng)建大量對(duì)象薪夕。有則改之。


下面是一個(gè)重復(fù)創(chuàng)建內(nèi)存的示例:


public class OOM {


public static void main(String[] args) {


Integer sum1=300000;


Integer sum2=400000;


OOM oom = new OOM();


System.out.println("往ArrayList中加入30w內(nèi)容");


oom.javaHeapSpace(sum1);


oom.memoryTotal();


System.out.println("往ArrayList中加入40w內(nèi)容");


oom.javaHeapSpace(sum2);


oom.memoryTotal();


}


public void javaHeapSpace(Integer sum){


Random random = new Random();


ArrayList openList = new ArrayList();


for(int i=0;i<sum;i++){


String charOrNum = String.valueOf(random.nextInt(10));


openList.add(charOrNum);


}


}


public void memoryTotal(){


Runtime run = Runtime.getRuntime();


long max = run.maxMemory();


long total = run.totalMemory();


long free = run.freeMemory();


long usable = max - total + free;


System.out.println("最大內(nèi)存 = " + max);


System.out.println("已分配內(nèi)存 = " + total);


System.out.println("已分配內(nèi)存中的剩余空間 = " + free);


System.out.println("最大可用內(nèi)存 = " + usable);


}


}


執(zhí)行結(jié)果:


往ArrayList中加入30w內(nèi)容


最大內(nèi)存 = 20447232


已分配內(nèi)存 = 20447232


已分配內(nèi)存中的剩余空間 = 4032576


最大可用內(nèi)存 = 4032576


往ArrayList中加入40w內(nèi)容


Exception in thread "main" java.lang.OutOfMemoryError: Java heap space


at java.util.Arrays.copyOf(Arrays.java:2245)


at java.util.Arrays.copyOf(Arrays.java:2219)


at java.util.ArrayList.grow(ArrayList.java:242)


at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)


at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)


at java.util.ArrayList.add(ArrayList.java:440)


at pers.qingqian.study.seven.OOM.javaHeapSpace(OOM.java:36)


at pers.qingqian.study.seven.OOM.main(OOM.java:26)


(2)擴(kuò)大堆內(nèi)存空間


使用 -Xms 和 -Xmx 來(lái)控制堆內(nèi)存空間大小赫悄。


OutOfMemoryError: GC overhead limit exceeded


原因:JDK6 新增錯(cuò)誤類型原献,當(dāng) GC 為釋放很小空間占用大量時(shí)間時(shí)拋出馏慨;一般是因?yàn)槎烟。瑢?dǎo)致異常的原因姑隅,沒(méi)有足夠的內(nèi)存写隶。


解決方案:


查看系統(tǒng)是否有使用大內(nèi)存的代碼或死循環(huán); 通過(guò)添加 JVM 配置讲仰,來(lái)限制使用內(nèi)存:


<jvm-arg>-XX:-UseGCOverheadLimit</jvm-arg>


OutOfMemoryError:unable to create new native thread


原因:線程過(guò)多


那么能創(chuàng)建多少線程呢慕趴?這里有一個(gè)公式:


(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads


MaxProcessMemory 指的是一個(gè)進(jìn)程的最大內(nèi)存


JVMMemory JVM內(nèi)存


ReservedOsMemory 保留的操作系統(tǒng)內(nèi)存


ThreadStackSize 線程棧的大小


當(dāng)發(fā)起一個(gè)線程的創(chuàng)建時(shí),虛擬機(jī)會(huì)在 JVM 內(nèi)存創(chuàng)建一個(gè) Thread 對(duì)象同時(shí)創(chuàng)建一個(gè)操作系統(tǒng)線程鄙陡,而這個(gè)系統(tǒng)線程的內(nèi)存用的不是 JVMMemory冕房,而是系統(tǒng)中剩下的內(nèi)存: (MaxProcessMemory - JVMMemory - ReservedOsMemory) 結(jié)論:你給 JVM 內(nèi)存越多,那么你能用來(lái)創(chuàng)建的系統(tǒng)線程的內(nèi)存就會(huì)越少趁矾,越容易發(fā)生 java.lang.OutOfMemoryError: unable to create new native thread耙册。


CPU 過(guò)高


定位步驟:


(1)執(zhí)行 top -c 命令,找到 cpu 最高的進(jìn)程的 id


(2)jstack PID 導(dǎo)出 Java 應(yīng)用程序的線程堆棧信息毫捣。


示例:


jstack 6795


"Low Memory Detector" daemon prio=10 tid=0x081465f8 nid=0x7 runnable [0x00000000..0x00000000]


"CompilerThread0" daemon prio=10 tid=0x08143c58 nid=0x6 waiting on condition [0x00000000..0xfb5fd798]


"Signal Dispatcher" daemon prio=10 tid=0x08142f08 nid=0x5 waiting on condition [0x00000000..0x00000000]


"Finalizer" daemon prio=10 tid=0x08137ca0 nid=0x4 in Object.wait() [0xfbeed000..0xfbeeddb8]


at java.lang.Object.wait(Native Method)


- waiting on <0xef600848> (a java.lang.ref.ReferenceQueue$Lock)


at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:116)


- locked <0xef600848> (a java.lang.ref.ReferenceQueue$Lock)


at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:132)


at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)


"Reference Handler" daemon prio=10 tid=0x081370f0 nid=0x3 in Object.wait() [0xfbf4a000..0xfbf4aa38]


at java.lang.Object.wait(Native Method)


- waiting on <0xef600758> (a java.lang.ref.Reference$Lock)


at java.lang.Object.wait(Object.java:474)


at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)


- locked <0xef600758> (a java.lang.ref.Reference$Lock)


"VM Thread" prio=10 tid=0x08134878 nid=0x2 runnable


"VM Periodic Task Thread" prio=10 tid=0x08147768 nid=0x8 waiting on condition


在打印的堆棧日志文件中详拙,tid 和 nid 的含義:


nid : 對(duì)應(yīng)的 Linux 操作系統(tǒng)下的 tid 線程號(hào),也就是前面轉(zhuǎn)化的 16 進(jìn)制數(shù)字


tid: 這個(gè)應(yīng)該是 jvm 的 jmm 內(nèi)存規(guī)范中的唯一地址定位


在 CPU 過(guò)高的情況下蔓同,查找響應(yīng)的線程饶辙,一般定位都是用 nid 來(lái)定位的。而如果發(fā)生死鎖之類的問(wèn)題牌柄,一般用 tid 來(lái)定位畸悬。


(3)定位 CPU 高的線程打印其 nid


查看線程下具體進(jìn)程信息的命令如下:


top -H -p 6735


top - 14:20:09 up 611 days, 2:56, 1 user, load average: 13.19, 7.76, 7.82


Threads: 6991 total, 17 running, 6974 sleeping, 0 stopped, 0 zombie


%Cpu(s): 90.4 us, 2.1 sy, 0.0 ni, 7.0 id, 0.0 wa, 0.0 hi, 0.4 si, 0.0 st


KiB Mem: 32783044 total, 32505008 used, 278036 free, 120304 buffers


KiB Swap: 0 total, 0 used, 0 free. 4497428 cached Mem


PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND


6800 root 20 0 27.299g 0.021t 7172 S 54.7 70.1 187:55.61 java


6803 root 20 0 27.299g 0.021t 7172 S 54.4 70.1 187:52.59 java


6798 root 20 0 27.299g 0.021t 7172 S 53.7 70.1 187:55.08 java


6801 root 20 0 27.299g 0.021t 7172 S 53.7 70.1 187:55.25 java


6797 root 20 0 27.299g 0.021t 7172 S 53.1 70.1 187:52.78 java


6804 root 20 0 27.299g 0.021t 7172 S 53.1 70.1 187:55.76 java


6802 root 20 0 27.299g 0.021t 7172 S 52.1 70.1 187:54.79 java


6799 root 20 0 27.299g 0.021t 7172 S 51.8 70.1 187:53.36 java


6807 root 20 0 27.299g 0.021t 7172 S 13.6 70.1 48:58.60 java


11014 root 20 0 27.299g 0.021t 7172 R 8.4 70.1 8:00.32 java


10642 root 20 0 27.299g 0.021t 7172 R 6.5 70.1 6:32.06 java


6808 root 20 0 27.299g 0.021t 7172 S 6.1 70.1 159:08.40 java


11315 root 20 0 27.299g 0.021t 7172 S 3.9 70.1 5:54.10 java


12545 root 20 0 27.299g 0.021t 7172 S 3.9 70.1 6:55.48 java


23353 root 20 0 27.299g 0.021t 7172 S 3.9 70.1 2:20.55 java


24868 root 20 0 27.299g 0.021t 7172 S 3.9 70.1 2:12.46 java


9146 root 20 0 27.299g 0.021t 7172 S 3.6 70.1 7:42.72 java


由此可以看出占用 CPU 較高的線程,但是這些還不高珊佣,無(wú)法直接定位到具體的類蹋宦。nid 是 16 進(jìn)制的,所以我們要獲取線程的 16 進(jìn)制 ID:


printf "%x\n" 6800


輸出結(jié)果:45cd


然后根據(jù)輸出結(jié)果到 jstack 打印的堆棧日志中查定位:


"catalina-exec-5692" daemon prio=10 tid=0x00007f3b05013800 nid=0x45cd waiting on condition [0x00007f3ae08e3000]


java.lang.Thread.State: TIMED_WAITING (parking)


at sun.misc.Unsafe.park(Native Method)


- parking to wait for <0x00000006a7800598> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)


at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:226)


at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2082)


at java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:467)


at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:86)


at org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:32)


at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)


at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)


at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)


at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)


at java.lang.Thread.run(Thread.java:745)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咒锻,一起剝皮案震驚了整個(gè)濱河市冷冗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惑艇,老刑警劉巖蒿辙,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異滨巴,居然都是意外死亡思灌,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)恭取,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)泰偿,“玉大人,你說(shuō)我怎么就攤上這事蜈垮『孽耍” “怎么了裕照?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)调塌。 經(jīng)常有香客問(wèn)我晋南,道長(zhǎng),這世上最難降的妖魔是什么羔砾? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任负间,我火速辦了婚禮,結(jié)果婚禮上蜒茄,老公的妹妹穿的比我還像新娘唉擂。我一直安慰自己,他們只是感情好檀葛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布玩祟。 她就那樣靜靜地躺著,像睡著了一般屿聋。 火紅的嫁衣襯著肌膚如雪空扎。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天润讥,我揣著相機(jī)與錄音转锈,去河邊找鬼。 笑死楚殿,一個(gè)胖子當(dāng)著我的面吹牛撮慨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播脆粥,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼砌溺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了变隔?” 一聲冷哼從身側(cè)響起规伐,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎匣缘,沒(méi)想到半個(gè)月后猖闪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肌厨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年培慌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柑爸。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡检柬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情何址,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布进胯,位于F島的核電站用爪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏胁镐。R本人自食惡果不足惜偎血,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盯漂。 院中可真熱鬧颇玷,春花似錦、人聲如沸就缆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)竭宰。三九已至空郊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間切揭,已是汗流浹背狞甚。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留廓旬,地道東北人哼审。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像孕豹,于是被迫代替她去往敵國(guó)和親涩盾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容