日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
JVM堆外內(nèi)存導(dǎo)致的FGC問(wèn)題排查

問(wèn)題發(fā)現(xiàn)

服務(wù)在線上環(huán)境頻繁的Full GC。把相關(guān)運(yùn)行時(shí)數(shù)據(jù)區(qū)的監(jiān)控打開(kāi),發(fā)現(xiàn)堆外內(nèi)存一直在上升。

成都創(chuàng)新互聯(lián)公司專注于浦城企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站建設(shè),成都做商城網(wǎng)站。浦城網(wǎng)站建設(shè)公司,為浦城等地區(qū)提供建站服務(wù)。全流程按需求定制制作,專業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,成都創(chuàng)新互聯(lián)公司專業(yè)和態(tài)度為您提供的服務(wù)

我使用的版本是 java8,jvm廠商是orcale hotspot,垃圾回收器使用的CMS+ParNew。

我使用的jvm參數(shù)是:

-Xmx6g
-Xms6g
-XX:NewRatio=1
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
-XX:MaxTenuringThreshold=6
-XX:+ParallelRefProcEnabled
-XX:+CMSParallelRemarkEnabled
-XX:+UseCMSCompactAtFullCollection
-XX:+heapDumpOnOutOfMemoryError
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/export/Logs/gc.log

為了明確排查方向,需要研究堆外內(nèi)存都具體有什么東西。于是我翻看了jvm的虛擬機(jī)規(guī)范。解讀如下:

Java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)

Java虛擬機(jī)定義了程序執(zhí)行期間使用的各種運(yùn)行時(shí)數(shù)據(jù)區(qū)域。其中一些數(shù)據(jù)區(qū)域是在Java虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建的,只有在Java虛擬機(jī)退出時(shí)才會(huì)被銷毀,這部分線程共有。其他數(shù)據(jù)區(qū)域?yàn)槊總€(gè)線程。每線程數(shù)據(jù)區(qū)域在創(chuàng)建線程時(shí)創(chuàng)建,在線程退出時(shí)銷毀,也就是線程私有。

運(yùn)行時(shí)數(shù)據(jù)區(qū)分為以下幾個(gè)部分:

1、PC寄存器(The pc Register)

每個(gè)線程一個(gè),以保存當(dāng)前執(zhí)行指令的地址。一旦執(zhí)行了指令,PC寄存器將用下一條指令更新。

2、虛擬機(jī)棧( Java Virtual Machine Stacks)

每個(gè)Java虛擬機(jī)線程都有一個(gè)私有Java虛擬機(jī)堆棧,與線程同時(shí)創(chuàng)建。虛擬機(jī)棧存儲(chǔ)棧幀,它保存局部變量和部分結(jié)果。

虛擬機(jī)??赡軙?huì)出現(xiàn)Java虛擬機(jī)將拋出StackOverflowerError。

3、堆(Heap)

Java虛擬機(jī)線程之間共享堆,堆只有一個(gè)。堆是為所有類實(shí)例和數(shù)組分配內(nèi)存的運(yùn)行時(shí)數(shù)據(jù)區(qū)域。這也是我們創(chuàng)建的對(duì)象放置的區(qū)域。是最大的,最需要調(diào)優(yōu)的地方。

堆是在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建的。對(duì)象的堆存儲(chǔ)由垃圾收集器回收;對(duì)象永遠(yuǎn)不會(huì)顯式解除分配。

如果計(jì)算需要的堆超過(guò)了自動(dòng)存儲(chǔ)管理系統(tǒng)的可用堆,Java虛擬機(jī)會(huì)拋出OutOfMemoryError。

4、方法區(qū)(Method Area)

存儲(chǔ)所有類級(jí)別的數(shù)據(jù),包括靜態(tài)變量所有線程共享。Java虛擬機(jī)只有一個(gè)方法區(qū)。存儲(chǔ)的有類結(jié)構(gòu),例如運(yùn)行時(shí)常量池、字段和方法數(shù)據(jù),以及方法和構(gòu)造函數(shù)的代碼,包括類和實(shí)例初始化以及接口初始化中使用的特殊方法。

5、運(yùn)行時(shí)常量池(Run-Time Constant Pool)

運(yùn)行時(shí)常量池是類文件中常量池表的每類或每接口運(yùn)行時(shí)表示形式。它包含多種常量,從編譯時(shí)已知的數(shù)字文本到必須在運(yùn)行時(shí)解析的方法和字段引用。運(yùn)行時(shí)常量池的功能類似于傳統(tǒng)編程語(yǔ)言的符號(hào)表,盡管它包含比典型符號(hào)表更廣泛的數(shù)據(jù)范圍。

這段我抄的,為了保持完整性,運(yùn)行時(shí)常量池其實(shí)是方法區(qū)的一部分。

6、本地方法棧(Native Method Stacks)

存儲(chǔ)本地方法信息,線程私有。

整體結(jié)構(gòu)表示如下?


問(wèn)題:方法區(qū)和元空間有什么關(guān)系?

簡(jiǎn)單理解,方法區(qū)是java的定義,而元空間則是hotspot虛擬機(jī)在1.8及其以后的實(shí)現(xiàn)。在1.7之前叫永久代(永久代還包含了部分老年對(duì)象),如果使用java8的話忽略永久代就行了。

根據(jù)jvm的規(guī)范,方法區(qū)內(nèi)存儲(chǔ)的都是jvm類級(jí)別的數(shù)據(jù),包括什么構(gòu)造方法,什么常量池什么的。那什么操作會(huì)使得這方面一直在上漲呢?帶著問(wèn)題,一步步搞唄。

簡(jiǎn)單嘗試

首先先定死m(xù)etaspce的大小,不讓他動(dòng)態(tài)擴(kuò)容,因?yàn)樵臻g每次調(diào)整大小都會(huì)進(jìn)行一次full gc。

jvm啟動(dòng)參數(shù)新增。

-XX:MetaspaceSize=512m
-XX:MaxMetaspaceSize=512m

但是發(fā)現(xiàn)并沒(méi)有用。

是否能從堆看出些端倪?

堆外內(nèi)存,沒(méi)有特別好的查看方法。我決定還是把堆內(nèi)存dump下來(lái)看看,看能否通過(guò)堆內(nèi)存,看出一些貓膩來(lái)。

將堆dump下來(lái)進(jìn)行分析。

使用命令 jps 找到j(luò)ava進(jìn)程pid,指定生成文件的path。

jmap -dump:file=/path ${pid}

dump完畢后。

借助工具進(jìn)行查詢 首先使用mat,官方網(wǎng)站:https://www.eclipse.org/mat/。

這邊看到了很多Netty的PoolThreaCache。

聯(lián)想到netty使用了直接內(nèi)存,是否和這個(gè)有關(guān)呢?

為此查詢了大量資料,找到了一個(gè)參數(shù):-Dio.netty.maxDirectMemory 。

這個(gè)參數(shù)大概意思是調(diào)整netty堆外內(nèi)存,通過(guò)它有三個(gè)取值,無(wú)論調(diào)成什么都沒(méi)辦法阻止堆外內(nèi)存的上漲。其實(shí)在這就有點(diǎn)無(wú)頭蒼蠅亂撞了。

確實(shí),只有兩種情況會(huì)導(dǎo)致netty相關(guān)的堆外內(nèi)存上漲。

1、要么是netty有bug 。

2、要么是使用方法不對(duì)。

netty有bug,這個(gè)可能性就算了吧。使用的版本也不是最新的,也沒(méi)有直接引用netty包,都是通過(guò)例如http-client或者rpc框架引入的netty。

使用方法不對(duì)?在http client或者rpc服務(wù)的部分代碼排查了一遍,基本上都是比較簡(jiǎn)單的用法,并沒(méi)有直接設(shè)置很怪的參數(shù),或者很非常規(guī)的操作。

在這就確實(shí)在堆里面找不到有用的線索了。

找到原因

貌似確實(shí)沒(méi)轍了。

隨后我請(qǐng)教了我司的超級(jí)大佬:森哥。森哥給我要了相關(guān)權(quán)限后,上去機(jī)器一頓操作。推測(cè)可能是C2 Compiler或者什么即時(shí)編譯導(dǎo)致的問(wèn)題,因?yàn)槎淹舛际莏vm級(jí)別的數(shù)據(jù),常規(guī)的排查確實(shí)比較難找到線索。

聽(tīng)完后聯(lián)想到堆外不就是方法區(qū)嗎,我用的java8 hotspot虛擬機(jī),也就是元空間了。

代碼里面會(huì)有什么導(dǎo)致元空間上漲呢?

元空間是存儲(chǔ)jvm級(jí)別的數(shù)據(jù),是否有很多類加載?

帶著這個(gè)猜想,找到相應(yīng)的參數(shù) -verbose:class,這個(gè)會(huì)將類加載全部打印出來(lái)。

如下圖:

發(fā)現(xiàn)有非常多的ASMAccessorImpl_,而且是不會(huì)停止,一直在加載。

厚禮蟹,這就查到了原因。

那ASM是什么,如果研究過(guò)spring,就知道在aop擴(kuò)展動(dòng)態(tài)生成字節(jié)碼,最底層其實(shí)就是ASM生成的,其實(shí)是一個(gè)字節(jié)碼編輯框架。官網(wǎng):https://asm.ow2.io/。

也就是說(shuō),我的代碼有一個(gè)地方一直在動(dòng)態(tài)生成類字節(jié)碼,加載到方法區(qū)。從而導(dǎo)致堆外內(nèi)存一直在上漲,從而導(dǎo)致full gc。

代碼修改

那怎么定位到是哪段代碼?

這個(gè)簡(jiǎn)單,打開(kāi)idea,double shift,調(diào)search everywhere。

排查到是mvel這個(gè)依賴框架生成的。

關(guān)于mvel,其實(shí)是spel差不多,表達(dá)式解析引擎。在項(xiàng)目中,mvel的使用我們只用了兩行代碼。

MVEL.executeExpression()
MVEL.compileExpression()

然后我們也有把編譯完的進(jìn)行緩存,按道理說(shuō)不會(huì)一直生成類的。因?yàn)閙vel這個(gè)框架實(shí)在是相關(guān)文檔太少,沒(méi)人維護(hù)的感覺(jué),抱著死馬當(dāng)活馬醫(yī)的態(tài)度,去github上提一個(gè)issue,然后自己同時(shí)接著排查。

幸運(yùn)的是這個(gè)框架還沒(méi)死絕,還有人回復(fù)。

大概意思是說(shuō),我問(wèn)為什么使用你們的mvel會(huì)導(dǎo)致我jvm出現(xiàn)oom錯(cuò)誤(頻繁的full gc),另外如果說(shuō)每次編譯相同的內(nèi)容的話,為什么沒(méi)有框架層面緩存起來(lái)?;卮鹫f(shuō)是需要自己緩存的。

也就是我的代碼還是緩存失效了。

找到緩存的那一行,使用的是map,用key去查找的時(shí)候,發(fā)現(xiàn)用的是contains,而沒(méi)有用containsKey。這就導(dǎo)致了永遠(yuǎn)查不到,也就導(dǎo)致了永遠(yuǎn)會(huì)重新編譯。

經(jīng)過(guò)修改后,問(wèn)題得以解決。

一條平平的線,并且沒(méi)有full gc,皆大歡喜

總結(jié)

堆外內(nèi)存有點(diǎn)難搞,難以和代碼聯(lián)系起來(lái)。提供一個(gè)思路:可通過(guò)-verbose:class查看類加載的情況,然后具體分析。


本文標(biāo)題:JVM堆外內(nèi)存導(dǎo)致的FGC問(wèn)題排查
分享鏈接:http://www.5511xx.com/article/ccdephg.html