偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

JVM堆外內(nèi)存導(dǎo)致的FGC問題排查

開發(fā) 后端
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í)銷毀,也就是線程私有。

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

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

圖片

我使用的版本是 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)建的對象放置的區(qū)域。是最大的,最需要調(diào)優(yōu)的地方。

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

如果計(jì)算需要的堆超過了自動(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)編程語言的符號(hào)表,盡管它包含比典型符號(hào)表更廣泛的數(shù)據(jù)范圍。

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

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

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

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


圖片


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

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

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

簡單嘗試

首先先定死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)并沒有用。

是否能從堆看出些端倪?

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

將堆dump下來進(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)存,通過它有三個(gè)取值,無論調(diào)成什么都沒辦法阻止堆外內(nèi)存的上漲。其實(shí)在這就有點(diǎn)無頭蒼蠅亂撞了。

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

1、要么是netty有bug 。

2、要么是使用方法不對。

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

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

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

找到原因

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

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

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

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

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

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

如下圖:

圖片圖片

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

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

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

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

代碼修改

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

這個(gè)簡單,打開idea,double shift,調(diào)search everywhere。

圖片

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

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

MVEL.executeExpression()
MVEL.compileExpression()

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

圖片

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

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

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

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

圖片

經(jīng)過修改后,問題得以解決。

圖片

一條平平的線,并且沒有full gc,皆大歡喜

圖片

總結(jié)

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

責(zé)任編輯:姜華 來源: 凱哥的Java技術(shù)活
相關(guān)推薦

2020-08-27 21:36:50

JVM內(nèi)存泄漏

2017-01-11 14:02:32

JVM源碼內(nèi)存

2022-06-15 16:04:13

Java編程語言

2025-06-16 07:40:00

2019-12-17 10:01:40

開發(fā)技能代碼

2020-05-09 13:49:00

內(nèi)存空間垃圾

2014-02-27 13:30:26

CacheLinux系統(tǒng)內(nèi)存不足

2021-06-01 09:29:43

ArthasJVM內(nèi)存

2018-11-06 12:12:00

MySQL內(nèi)存排查

2021-06-28 08:00:00

Python開發(fā)編程語言

2019-02-26 14:33:22

JVM內(nèi)存虛擬機(jī)

2022-04-29 08:05:06

內(nèi)存堆外GC

2024-10-10 15:32:51

2019-02-14 13:30:54

內(nèi)存泄露運(yùn)維

2022-11-09 17:10:47

JVM內(nèi)存區(qū)域

2010-09-27 13:41:22

JVM內(nèi)存回收

2015-07-20 10:23:24

NET內(nèi)存問題排查

2024-08-19 00:10:00

C++內(nèi)存

2022-04-15 07:51:12

off-heap堆外內(nèi)存JVM

2022-09-21 08:39:52

堆外內(nèi)存泄露內(nèi)存分布
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)