Java內(nèi)存問(wèn)題排查和解決
內(nèi)存都有啥
編譯后地址是邏輯地址蔚万,需要經(jīng)過(guò)編譯映射到物理內(nèi)存
MMU負(fù)責(zé)地址的轉(zhuǎn)換
可用內(nèi)存 = 物理內(nèi)存 + 虛擬內(nèi)存(swap)
RES 實(shí)際內(nèi)存占用
可用內(nèi)存 = free + buffers + cached
linux使用時(shí),迅速占滿(mǎn)內(nèi)存
堆:JVM堆中的數(shù)據(jù)是共享的已卷,是占用內(nèi)存最大的一塊區(qū)域
虛擬機(jī)棧:Java虛擬機(jī)棧闺魏,是基于線(xiàn)程的,用來(lái)服務(wù)字節(jié)碼指令的運(yùn)行
程序計(jì)數(shù)器:當(dāng)前線(xiàn)程所執(zhí)行的字節(jié)碼的行號(hào)指示器
元空間:方法區(qū)的位置,非堆
本地內(nèi)存:其他的內(nèi)存占用空間
Java內(nèi)存管理基本概念:
內(nèi)存:
- Java內(nèi)存
- 操作系統(tǒng)
Java內(nèi)存:
- Java堆內(nèi)存
- 元空間(堆外)
Java堆內(nèi)存
- JVM分配的Java內(nèi)存對(duì)象
- 通常使用 -Xmx -Xms 控制大小
元空間
- Metaspace默認(rèn)無(wú)上限
- 原方法區(qū)在這里
內(nèi)存劃分:
- JVM進(jìn)程內(nèi)存 = 堆內(nèi)內(nèi)存 + 堆外內(nèi)存
- 堆外內(nèi)存 = 元空間 + CodeCache + 本地內(nèi)存
- 堆外內(nèi)存和操作系統(tǒng)剩余內(nèi)存是此消彼長(zhǎng)的關(guān)系
- 可分配的內(nèi)存大小 = 物理內(nèi)存 - SWAP
32位內(nèi)存限制4GB截亦,目前ZGC支持16TB內(nèi)存
控制參數(shù):
- 堆 -Xms -XMx
- 元空間 -XX:MaxMetaspaceSize -XX:MetaspaceSize
- 棧: -Xss
- 直接內(nèi)存: -XX:MaxDirectMemorySize
- 其他堆外內(nèi)存無(wú)法控制
垃圾回收:
- 自動(dòng)垃圾回收:JVM自動(dòng)檢測(cè)和釋放不再使用的內(nèi)存
- Java運(yùn)行時(shí)JVM會(huì)有線(xiàn)程執(zhí)行GC却桶,不需要程序員顯示釋放對(duì)象
- GC發(fā)生的時(shí)機(jī)由復(fù)雜的策略判斷,自動(dòng)觸發(fā),不受外部控制
- 不同的垃圾回收算法亏钩,甚至不同的JVM版本,回收策略都不一樣
內(nèi)存問(wèn)題兩種形式:
- 內(nèi)存溢出(OutOfMemoryError)OOM:
- 堆是最常見(jiàn)的情況
- 堆外內(nèi)存排查困難
- 內(nèi)存泄漏(MemoryLeak)ML:
- 分配的內(nèi)存沒(méi)有得到釋放
- 內(nèi)存一直在增長(zhǎng),有OOM風(fēng)險(xiǎn)
- GC回收時(shí)回收不掉
- 能夠回收但很快又占滿(mǎn)
內(nèi)存問(wèn)題影響:
- 發(fā)生OOM Error,應(yīng)用停止
- 頻繁GC沦偎,GC時(shí)間長(zhǎng)谈火,GC線(xiàn)程時(shí)間片占用高
- 服務(wù)卡頓,請(qǐng)求響應(yīng)時(shí)間邊長(zhǎng)
排查困難:
- 問(wèn)題時(shí)間跨度大
- 問(wèn)題解決耗費(fèi)精力
- 現(xiàn)場(chǎng)保護(hù)意識(shí)不足
簡(jiǎn)單問(wèn)題場(chǎng)景:
- 物理內(nèi)存不足
- 主機(jī)物理內(nèi)存非常少
- 主機(jī)上應(yīng)用進(jìn)程非常多
- 給應(yīng)用JVM分配的內(nèi)存小
- 錯(cuò)誤的引用方法荒揣,發(fā)生了內(nèi)存泄漏,沒(méi)有及時(shí)切斷與GC roots的關(guān)系
- 并發(fā)量大,計(jì)算需要內(nèi)存大
- 沒(méi)有控制取數(shù)范圍
- 加載了非常多的Jar包
- 對(duì)堆外內(nèi)存無(wú)限制的使用
垃圾回收器:
- CMS:將在Java14正式移除
- G1:主流應(yīng)用的垃圾回收器
- ZGC 大容量(16TB),低延遲(10ms)的垃圾回收器
可達(dá)性分析法:
- Reference Chain
- 可達(dá)性分析法
- GC過(guò)程:找到活躍的對(duì)象,然后清理其他的
引用級(jí)別:
- 強(qiáng)引用:屬于最普通最強(qiáng)硬的一種存在,只有在和GC Roots斷絕關(guān)系時(shí),才會(huì)被消滅掉
- 軟引用:只有在內(nèi)存不足時(shí),系統(tǒng)則會(huì)回收軟引用獨(dú)享
- 弱引用:當(dāng)JVM進(jìn)行垃圾回收時(shí),無(wú)論內(nèi)存是否充足,都會(huì)回收被弱引用關(guān)聯(lián)的對(duì)象
- 虛引用:虛引用主要用來(lái)跟蹤對(duì)象被垃圾回收的活動(dòng)
對(duì)象何時(shí)提升(Promotion)
- 常規(guī)提升 對(duì)象夠老
- 分配擔(dān)保 Survivor空間不夠戳杀,老年代擔(dān)保
- 大對(duì)象直接在老年代分配
- 動(dòng)態(tài)對(duì)象年齡判定
瞬時(shí)態(tài)和歷史態(tài)
- 瞬時(shí)態(tài):
- 指當(dāng)時(shí)發(fā)生的快照類(lèi)型的元素
- 體積大
- 歷史態(tài)
- 指按照頻率抓取的
- 有固定監(jiān)控項(xiàng)的資源變動(dòng)
預(yù)防措施:
- 減少創(chuàng)建大對(duì)象的頻率:比如byte數(shù)組的傳遞
- 不要緩存太多的堆內(nèi)存數(shù)據(jù):使用guava的weak引用模式
- 查詢(xún)的范圍一定要可控:如分庫(kù)分表中間件;ES等有同樣問(wèn)題
- 用完的資源一定要close掉:可以使用新的try-with-resources語(yǔ)法
- 少用intern:字符串太長(zhǎng),且無(wú)法復(fù)用,就會(huì)造成內(nèi)存泄漏
- 合理的Session超時(shí)時(shí)間
- 少用第三方本地代碼,使用Java方案替代
- 合理的池大小
- XML(SAX/DOM)、JSON解析時(shí)要注意對(duì)象大小
總結(jié):
- 問(wèn)題發(fā)現(xiàn):
- 確保加入了日志和自動(dòng)轉(zhuǎn)儲(chǔ)參數(shù)
- 確定物理內(nèi)存足夠:free
- 確定Java進(jìn)程內(nèi)存足夠:jmap
- 確定主機(jī)環(huán)境徐钠,剩余內(nèi)存大小
- 查看Glog和其他日志
- 使用jstack對(duì)線(xiàn)程進(jìn)行摸底
- 對(duì)堆外內(nèi)存進(jìn)行排查
- 保留現(xiàn)場(chǎng)
- 采取措施
- 重復(fù)觀察
- 問(wèn)題解決