品牌建設(shè)的四條主線seo頁面優(yōu)化技術(shù)
java進(jìn)程內(nèi)存
JVM內(nèi)存分布圖:
【java進(jìn)程內(nèi)存】=【堆外內(nèi)存】 + 【jvm堆內(nèi)存】
【堆外內(nèi)存】= 【Metaspace】+ 【Direct Memory】+【JNI Memory】+【code_cache】+ …
堆外內(nèi)存泄漏的排查在于【本地內(nèi)存(Native Memory)】=【Direct Memory】+【JNI Memory】
一般堆內(nèi)存比較好理解,而對(duì)于堆外內(nèi)存,了解比較少。
什么是堆外內(nèi)存
Non-Heap Space 翻譯為非堆內(nèi)存,也被稱為Off-Heap(堆外內(nèi)存),大家習(xí)慣于叫這部分內(nèi)存為堆外內(nèi)存。查看了很多國(guó)內(nèi)外文章,對(duì)于這塊內(nèi)存,沒有很統(tǒng)一的定義。
較可信的是分為下面兩種定義:
- 廣義上的Non-Heap
除開Heap以外的所有內(nèi)存,包括MetaSpace、NativeMemory(JNI Memory、Direct Memory等)、Stack、Code Cache等。
下面講解的Non-Heap是針對(duì)于廣義的定義。 - 狹義上的Non-Heap
只包含Metaspace、code_cache。
注意:
監(jiān)控系統(tǒng)里會(huì)有Non-Heap的監(jiān)控,例如SkyWalking、Arthas的Non-Heap指標(biāo),都是通過JDK自帶的MemoryMXBean方法獲取的。
所以一般監(jiān)控系統(tǒng)采集的Non-Heap只是Heap以外的一部分內(nèi)存!還需要留意NativeMemory等等內(nèi)存。
對(duì)應(yīng)的代碼:
@Override
public long getNonHeapMemoryMax() {return memoryMXBean.getNonHeapMemoryUsage().getMax();
}@Override
public long getNonHeapMemoryUsed() {return memoryMXBean.getNonHeapMemoryUsage().getUsed();
}
JNI Memory內(nèi)存
JNI (Java Native Interface) memory是指Java應(yīng)用程序與本地代碼交互時(shí)使用的內(nèi)存。Java Native Interface (JNI) 是 Java 與本地(如 C 或 C++)代碼進(jìn)行交互的橋梁。出了問題所以需要使用C、C++的思路去解決。
ptmalloc2在高并發(fā)分配內(nèi)存時(shí),會(huì)存在較多內(nèi)存碎片無法釋放的情況,碎片積壓到一定程度甚至?xí)?dǎo)致進(jìn)程內(nèi)存不夠用,最終堆外內(nèi)存泄漏。因此MySQL、TFS、Tair、Redis這些中間件部署時(shí),指定jemlloc內(nèi)存分配器替代ptmalloc2可更好地管理內(nèi)存。
內(nèi)存排查工具
java內(nèi)存查看相關(guān)工具
實(shí)例:
- 確定到底是哪個(gè)進(jìn)程占用內(nèi)存高
ps aux --sort=-%mem
- 通過pmap命令可以看到最真實(shí)的JVM heap的物理內(nèi)存的占有量
pmap -X 27045 | head -2; pmap -X 27045 | awk ‘NR>2’ | sort -nr -k6 | head -10
堆外內(nèi)存相關(guān)工具
不同的內(nèi)存區(qū)域可以使用不同的命令進(jìn)行排查,同時(shí)也留意合理設(shè)置對(duì)應(yīng)內(nèi)存區(qū)域的參數(shù)。
JVM內(nèi)存使用量過大問題排查思路
參考: 【JVM內(nèi)存】系統(tǒng)性排查JVM內(nèi)存問題的思路
JVM參數(shù)設(shè)置說明
- -Xms4g
初始堆大小 默認(rèn)物理內(nèi)存的1/64(<1GB) 默認(rèn)(MinHeapFreeRatio參數(shù)可以調(diào)整)空余堆內(nèi)存小于40%時(shí),JVM就會(huì)增大堆直到-Xmx的最大限制。初始和最大最好設(shè)置成一樣,避免堆內(nèi)存在應(yīng)用運(yùn)行過程中自動(dòng)擴(kuò)容而影響服務(wù)穩(wěn)定性
- -Xmx4g
最大堆大小 默認(rèn)物理內(nèi)存的1/4(<1GB) 默認(rèn)(MaxHeapFreeRatio參數(shù)可以調(diào)整)空余堆內(nèi)存大于70%時(shí),JVM會(huì)減少堆直到 -Xms的最小限制。初始和最大最好設(shè)置成一樣,避免堆內(nèi)存在應(yīng)用運(yùn)行過程中自動(dòng)擴(kuò)容而影響服務(wù)穩(wěn)定性
另外一般的應(yīng)用,XMX可以設(shè)置為物理內(nèi)存的1/2到2/3,較充分地去利用內(nèi)存。
需要較多地使用Heap外內(nèi)存應(yīng)用,物理內(nèi)存不要超過1/2,例如ElasticSearch、RocketMQ-broker、Kafka等中間件,需要大量讀寫文件,操作系統(tǒng)需要大量的Page Cache,才能有足夠的緩存提高性能,所以JVM Heap不要過大,以預(yù)留給非Heap的其他內(nèi)存。
- -Xmn
年輕代大小(1.4or lator) 整個(gè)堆大小=年輕代大小 + 年老代大小 + 持久代大小. 增大年輕代后,將會(huì)減小年老代大小.此值對(duì)系統(tǒng)性能影響較大,Sun官方推薦配置為整個(gè)堆的3/8。盡量設(shè)置小一點(diǎn)
- -Xss128k
每個(gè)線程的堆棧大小 JDK5.0以后每個(gè)線程堆棧大小為1M,以前每個(gè)線程堆棧大小為256K.更具應(yīng)用的線程所需內(nèi)存大小進(jìn)行 調(diào)整.在相同物理內(nèi)存下,減小這個(gè)值能生成更多的線程.但是操作系統(tǒng)對(duì)一個(gè)進(jìn)程內(nèi)的線程數(shù)還是有限制的,不能無限生成,經(jīng)驗(yàn)值在3000~5000左右。
一般小的應(yīng)用, 如果棧不是很深, 應(yīng)該是128k夠用的 大的應(yīng)用建議使用256k。這個(gè)選項(xiàng)對(duì)性能影響比較大,需要嚴(yán)格的測(cè)試。
- -XX:SurvivorRatio=8
Eden區(qū)與Survivor區(qū)的大小比值 Eden區(qū)與Survivor區(qū)的大小比值 設(shè)置為8,則兩個(gè)Survivor區(qū)與一個(gè)Eden區(qū)的比值為2:8,一個(gè)Survivor區(qū)占整個(gè)年輕代的1/10
- -XX:AlwaysPreTouch
(1) 如果未配置了-XX:AlwaysPreTouch,則實(shí)際是使用的是虛擬內(nèi)存,給了一張空頭支票,只在首次訪問時(shí),例如存放一批新的Java對(duì)象數(shù)據(jù),但原來申請(qǐng)的內(nèi)存不夠用了,需要新的內(nèi)存來,這時(shí)才需要分配物理內(nèi)存,也就是通過缺頁異常進(jìn)入內(nèi)核中,再由內(nèi)核來分配內(nèi)存,再交給JVM進(jìn)程使用。一般情況,不會(huì)配置-XX:AlwaysPreTouch。
(2) 如果配置了-XX:AlwaysPreTouch,則JVM啟動(dòng)時(shí),則不僅分配Xms的大小的虛擬內(nèi)存,還會(huì)使用物理內(nèi)存、填充整個(gè)堆。
提前申請(qǐng)好物理內(nèi)存,減少程序運(yùn)行過程中發(fā)生的物理內(nèi)存分配帶來的延遲,可以提升性能。例如部署elasticsearch節(jié)點(diǎn)時(shí),可以指定該參數(shù),提升性能。
- -XX:+UseG1GC
選擇使用G1垃圾收集器,G1日志解析參考:G1日志解析
- -XX:MetaspaceSize=128M
元數(shù)據(jù)空間的擴(kuò)容臨界值,元數(shù)據(jù)空間的內(nèi)存commited指超過256M,將會(huì)發(fā)生mixed GC和擴(kuò)容
- -XX:MaxMetaspaceSize=256M
一般大小256M足夠,因?yàn)槟J(rèn)值無限大,如果出現(xiàn)頻繁加載class等情況,容易打滿系統(tǒng)內(nèi)存。報(bào)錯(cuò):java.lang.OutOfMemoryError: Metaspace。
- -XX:MaxDirectMemorySize=1G
默認(rèn)值等于-Xmx,這個(gè)參數(shù)直接干預(yù)sun.nio.MaxDirectMemorySize這個(gè)屬性,會(huì)限制nio可用的最大直接內(nèi)存為1G,最好配置直接內(nèi)存,因?yàn)槎淹鈨?nèi)存一旦泄漏可能會(huì)打滿整個(gè)系統(tǒng)內(nèi)存,并且很難排查。Netty等框架會(huì)用到DirectMemory,且一般設(shè)置1G足夠。報(bào)錯(cuò):java.lang.OutOfMemoryError: Direct buffer memory。
- -XX:InitialBootClassLoaderMetaspaceSize=256M
給每個(gè)classloader分配的元數(shù)據(jù)空間初始值是256M
- -XX:+PrintGCDetails
打印GC日志
- -XX:+PrintGCDateStamps
打印GC日志附帶時(shí)間戳
- -XX:+PrintHeapAtGC
GC之前和GC之后的堆的內(nèi)存使用情況要打印出來
- -Xloggc:/data/logs/java/gc.log
GC日志的存儲(chǔ)路徑(前綴)
- -XX:+UseGCLogFileRotation
GC日志文件開啟滾動(dòng)存儲(chǔ)
-XX:NumberOfGCLogFiles=5
GC日志文件最多保留5個(gè)
- -XX:GCLogFileSize=30M
每個(gè)GC日志文件最大30M,超過30M,生成新的文件
- -XX:+HeapDumpOnOutOfMemoryError
當(dāng)發(fā)生內(nèi)存溢出時(shí)dump堆轉(zhuǎn)儲(chǔ)文件
- -XX:HeapDumpPath=/data/logs/java/heap_dump_%p.hprof
堆轉(zhuǎn)儲(chǔ)文件的存放地址
JVM方法區(qū)(元空間)大小設(shè)置
-XX:MetaspaceSize設(shè)置元空間初始大小
-XX:MaxMetaspaceSize最大可分配大小
解釋JDK8及以后:可以使用-XX:MetaspaceSize和-XX:MaxMetaspaceSize設(shè)置元空間初始大小以及最大可分配大小。
例子:設(shè)置初始大小是100M,最大可分配空間也是100M。-XX:MetaspaceSize=100m -XX:MaxMetaspaceSize=100m。
如果不指定元空間的大小,默認(rèn)情況下,元空間最大的大小是系統(tǒng)內(nèi)存的大小,元空間一直擴(kuò)大,虛擬機(jī)可能會(huì)消耗完所有的可用系統(tǒng)內(nèi)存。如果元空間內(nèi)存不夠用,就會(huì)報(bào)OOM。默認(rèn)情況下,對(duì)應(yīng)一個(gè)64位的服務(wù)端JVM來說,其默認(rèn)的-XX:MetaspaceSize值為21MB,這就是初始的高水位線,一旦元空間的大小觸及這個(gè)高水位線,就會(huì)觸發(fā)Full GC并會(huì)卸載沒有用的類,然后高水位線的值將會(huì)被重置。如果初始化的高水位線設(shè)置過低,會(huì)頻繁的觸發(fā)Full GC,高水位線會(huì)被多次調(diào)整。所以為了避免頻繁GC以及調(diào)整高水位線,建議將-XX:MetaspaceSize設(shè)置為較高的值, -XX:MaxMetaspaceSize,一般大小256M足夠,因?yàn)槟J(rèn)值無限大,如果出現(xiàn)頻繁加載class等情況,容易打滿系統(tǒng)內(nèi)存,出現(xiàn)系統(tǒng)風(fēng)險(xiǎn)。
Heap Dump文件生成
heap dump進(jìn)行分析工具
事先需要準(zhǔn)備軟下軟件包:
- JDK 11+(目前MAT最新版本1.13.0要求使用)
- Memory Analyzer Tool (MAT)
MAT官方下載頁面:Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation
軟件包準(zhǔn)備就緒后,解壓JDK和MAT到任意目錄,無需其他安裝操作。
使用命令可以立刻獲取某個(gè)Java進(jìn)程的heap dump。
使用jmap命令:
jmap -dump:live,format=b,file=/path/to/heapdump.hprof [pid]
使用jcmd命令:
jcmd [pid] GC.heap_dump /path/to/heapdump.hprof
其中為需要分析的Java進(jìn)程的pid。/path/to/heapdump.hprof為生成的heap dump文件所在的路徑。
在特定時(shí)間點(diǎn)生成
考慮到如下場(chǎng)景:我們需要獲取Java程序出現(xiàn)異常的時(shí)候(例如OOM)的heap dump。這種場(chǎng)景下我們無法使用上面講解的命令,Java進(jìn)程出現(xiàn)問題的時(shí)候可能已經(jīng)被系統(tǒng)kill掉。因此我們需要配置JVM,讓他能夠在特定時(shí)間點(diǎn)自動(dòng)生成heap dump。
下面列出生成heap dump的時(shí)間點(diǎn)和對(duì)應(yīng)的JVM參數(shù)。
在OOM的時(shí)候生成heap dump:
-XX:+HeapDumpOnOutOfMemoryError
在full GC前生成heap dump:
-XX:+HeapDumpBeforeFullGC
在full GC后生成heap dump:
-XX:+HeapDumpAfterFullGC
在按下ctrl+break的時(shí)候生成heap dump:
-XX:+HeapDumpOnCtrlBreak
注意: 指定heap dump文件生成的路徑需要配置-XX:HeapDumpPath=/path/to/heapdump.hprof。
Heap Dump分析
對(duì)于不是很大的heap dump文件(不大于MAT分析機(jī)器內(nèi)存的1.2倍),我們可以將heap dump文件壓縮后下載到本地,使用MAT圖形界面方式分析。操作方法比較直觀(File -> Open Heap Dump…),這里不再贅述。
生產(chǎn)環(huán)境中可能遇到特別大的heap dump。比如我們要分析一個(gè)30G的heap dump。將其在服務(wù)器上壓縮之后,空間占用仍有大約10G,下載到本地耗時(shí)很長(zhǎng)。本地開發(fā)機(jī)器一般也很少見32G內(nèi)存機(jī)器,分析的時(shí)候會(huì)出現(xiàn)內(nèi)存溢出問題。這種情況下我們需要使用MAT提供的命令行功能,在服務(wù)器上進(jìn)行分析并輸出分析報(bào)告。這些分析報(bào)告是靜態(tài)的web頁面,只有幾百KB大小。需要在本地做的僅僅是將這些報(bào)告下載下來用瀏覽器打開,這樣完美避免了本地開發(fā)環(huán)境配置不足的問題。
在服務(wù)器上使用MAT命令行分析的方法很簡(jiǎn)單。在MAT安裝目錄中執(zhí)行:
./ParseHeapDump.sh xxx.hprof org.eclipse.mat.api:suspects org.eclipse.mat.api:overview org.eclipse.mat.api:top_components
其中xxx.hprof在實(shí)際使用的時(shí)候需要替換為hprof文件的路徑。運(yùn)行完畢后在hprof文件所在目錄會(huì)生成一系列的index/threads文件和3個(gè)壓縮文件。這3個(gè)壓縮文件是我們重點(diǎn)關(guān)注的分析報(bào)告,分別為:
- xxx_Leak_Suspects.zip:報(bào)告包含懷疑造成內(nèi)存泄漏的地方,報(bào)告中包含了class層級(jí)圖。對(duì)于OOM的場(chǎng)景能夠很容易的定位到是哪個(gè)對(duì)象占用了大量?jī)?nèi)存不釋放。
- xxx_System_Overview.zip:包含heap dump基本信息,dump進(jìn)程JVM的相關(guān)配置和線程信息等。
- xxx_Top_Components.zip:查看占用空間最大的幾個(gè)object/class/classloader/package等。報(bào)告以餅圖和表格的形式展示。通過這個(gè)報(bào)告可以定位出Java程序運(yùn)行時(shí)哪些對(duì)象占用內(nèi)存較多,對(duì)問題排查和程序優(yōu)化很有幫助。
這三個(gè)報(bào)告是分析問題的關(guān)鍵。我們通過報(bào)告找出內(nèi)存占用過大的對(duì)象,然后結(jié)合日志和項(xiàng)目源代碼分析程序邏輯,逐步定位出問題。
jvm配置實(shí)例
- demo1
export JAVA_OPTS="-Xmx2048m -Xms2048m -Xss128k -XX:SurvivorRatio=8 -XX:MetaspaceSize=256m \
-XX:MaxDirectMemorySize=1G \
-XX:+UseG1GC -XX:MaxGCPauseMillis=300 \
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/export/data/heapdump.hprof \
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC \
-Xloggc:/export/Data/gc-%t.log -XX:+UseGCLogFileRotation -XX:GCLogFileSize=100M -XX:NumberOfGCLogFiles=20"echo "環(huán)境變量:"
echo "JAVA_OPTS:$JAVA_OPTS"
echo "JAVA_OPTS_EXT:$JAVA_OPTS_EXT"echo "應(yīng)用啟動(dòng)命令:"
echo "java $JAVA_OPTS $JAVA_OPTS_EXT -jar $MAIN_JAR >/dev/null 2>&1"java ${PFINDER_AGENT:-} $JAVA_OPTS $JAVA_OPTS_EXT -jar $MAIN_JAR >/dev/null 2>&1