Netty源碼解析——Buffer之ByteBuf 內(nèi)存泄漏檢測(cè)

Netty源碼解析——Buffer之ByteBuf 內(nèi)存泄漏檢測(cè)

0.引用計(jì)數(shù)器基礎(chǔ)知識(shí)

1)? 計(jì)數(shù)器基于AtomicIntegerFieldUpdater,因?yàn)锽yteBuf對(duì)象很多,如果都把int包一層AtomicInteger花銷較大从撼,而AtomicIntegerFieldUpdater只需要一個(gè)全局的靜態(tài)變量租副。所有的ByteBuf的引用計(jì)數(shù)器初始值為1.

private static final AtomicIntegerFieldUpdater<AbstractReferenceCounted> refCntUpdater =

? ? ?? AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCounted.class, "refCnt");

private volatile int refCnt = 1;

拓展:AtomicIntegerFieldUpdater:允許類中里面一個(gè)字段具有原子性践叠。這個(gè)字段必須是volatile類型的簿透,在線程間共享變量時(shí)保證其可見性.字段描述類型是與調(diào)用者與操作對(duì)象字段的關(guān)系一致,可以進(jìn)行反射進(jìn)行原子操作.對(duì)于父類的字段,子類不能直接操作,只能是實(shí)例變量和可修改的變量,不能再被修飾的類前面加 static 和final 的修飾符. 比如refCnt不能加static和final修飾符,如果是包裝類Integer和Long的化可以使用AtomicReferenceFieldUpdater類.

2)? release()和retain() 分別對(duì)應(yīng)著計(jì)數(shù)器refCnt加1和減1.等于零時(shí)deallocate()被調(diào)用進(jìn)行回收.由于每次操作只能操作的數(shù)為1,居然在程序里面直接判斷old值為1時(shí)就進(jìn)行deallocate(),為什么這么寫最蕾?當(dāng)引用計(jì)數(shù)器為0時(shí),底下的buffer已經(jīng)回收,即使ByteBuf對(duì)象還在祝拯,對(duì)它的各種訪問(wèn)操作都會(huì)拋出異常士袄。

3)? 有duplicate(),slice(),order()所衍生出來(lái)的ByteBuf與原對(duì)象共享底下的buffer,也共享引用計(jì)數(shù)器,所以它們經(jīng)常需要調(diào)用retain()來(lái)顯示自己的存在泻拦。copy()方法生成的ByteBuf完全獨(dú)立于原ByteBuf堤如,而slice()和duplicate()方法生成的ByteBuf與原ByteBuf共享相同的底層實(shí)現(xiàn),但是各自維護(hù)獨(dú)立的索引和標(biāo)記.slice()方法從原始的ByteBuf中截取一段,這段數(shù)據(jù)從readerIndex到writeIndex蒲列,返回時(shí)新的ByteBuf的最大容量maxCapacity為原始ByteBuf的readableBytes().而duplicate()則是把整個(gè)ByteBuf截取出來(lái).在一個(gè)函數(shù)體里面,只要增加了引用計(jì)數(shù)就必須調(diào)用release()方法.

1.Netty內(nèi)存泄漏檢測(cè)

內(nèi)存泄漏,主要是針對(duì)池化的ByteBuf.ByteBuf對(duì)象被JVM GC掉之前,沒(méi)有調(diào)用release()把底下的DirectByteBuffer或byte[]歸還到池里,會(huì)導(dǎo)致池越來(lái)越大.Netty默認(rèn)會(huì)從分配的ByteBuf里抽樣出大約1%的來(lái)進(jìn)行跟蹤窒朋。如果泄漏,會(huì)有如下語(yǔ)句打蛹掂帧:

LEAK: ByteBuf.release() was not called before it's garbage-collected. Enable advanced leak reporting to find out where the leak occurred. To enable advanced leak reporting, specify the JVM option '-Dio.netty.leakDetectionLevel=advanced' or call ResourceLeakDetector.setLevel()

禁用(DISABLED) - 完全禁止泄露檢測(cè)炼邀,省點(diǎn)消耗。

簡(jiǎn)單(SIMPLE) - 默認(rèn)等級(jí)剪侮,告訴我們?nèi)拥?%的ByteBuf是否發(fā)生了泄露拭宁,但總共一次只打印一次,看不到就沒(méi)有了瓣俯。

高級(jí)(ADVANCED) - 告訴我們?nèi)拥?%的ByteBuf發(fā)生泄露的地方杰标。每種類型的泄漏(創(chuàng)建的地方與訪問(wèn)路徑一致)只打印一次。對(duì)性能有影響彩匕。

偏執(zhí)(PARANOID) - 跟高級(jí)選項(xiàng)類似腔剂,但此選項(xiàng)檢測(cè)所有ByteBuf,而不僅僅是取樣的那1%驼仪。對(duì)性能有絕大的影響掸犬。

? ? ByteBufAllocator 可用于創(chuàng)建 ByteBuf 對(duì)象。創(chuàng)建的過(guò)程中绪爸,它會(huì)調(diào)用 #toLeakAwareBuffer(...) 方法湾碎,將 ByteBuf 裝飾成 LeakAware ( 可檢測(cè)內(nèi)存泄露 )的 ByteBuf 對(duì)象.

1.1 ResourceLeakDetector

ResourceLeakDetector 為了檢測(cè)內(nèi)存是否泄漏,使用了 WeakReference( 弱引用 )和 ReferenceQueue( 引用隊(duì)列 )奠货,過(guò)程如下:

根據(jù)檢測(cè)級(jí)別和采樣率的設(shè)置介褥,在需要時(shí)為需要檢測(cè)的 ByteBuf 創(chuàng)建WeakReference 引用。

當(dāng) JVM 回收掉 ByteBuf 對(duì)象時(shí)递惋,JVM 會(huì)將 WeakReference 放入ReferenceQueue 隊(duì)列中柔滔。

通過(guò)對(duì) ReferenceQueue 中 WeakReference 的檢查,判斷在 GC 前是否有釋放ByteBuf 的資源萍虽,就可以知道是否有資源釋放睛廊。

private final ConcurrentMap<DefaultResourceLeak<?>, LeakEntry> allLeaks = PlatformDependent.newConcurrentHashMap();//DefaultResourceLeak集合

private final ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();//引用隊(duì)列

private final ConcurrentMap<String, Boolean> reportedLeaks = PlatformDependent.newConcurrentHashMap();//已匯報(bào)的內(nèi)存泄露的資源類型的集合

private final String resourceType;//資源類型

private final int samplingInterval;//采樣頻率

1.2 ResourceLeakTracker

每個(gè)資源( 例如:ByteBuf 對(duì)象 ),會(huì)創(chuàng)建一個(gè)追蹤它是否內(nèi)存泄露的 ResourceLeakTracker 對(duì)象,代碼如下

leak = AbstractByteBuf.leakDetector.track(buf);

if (leak != null) {

?? buf = new AdvancedLeakAwareByteBuf(buf, leak);

}

1.3? DefaultResourceLeak

DefaultResourceLeak 繼承 java.lang.ref.WeakReference 類杉编,實(shí)現(xiàn) ResourceLeakTracker 接口超全,默認(rèn) ResourceLeakTracker 實(shí)現(xiàn)類。同時(shí),它是 ResourceLeakDetector 內(nèi)部靜態(tài)類.

private static final class DefaultResourceLeak<T>

? ? ?? extends WeakReference<Object> implements ResourceLeakTracker<T>, ResourceLeak

構(gòu)造函數(shù)

DefaultResourceLeak(

? ? ?? Object referent,

? ? ?? ReferenceQueue<Object> refQueue,

? ? ?? ConcurrentMap<DefaultResourceLeak<?>, LeakEntry> allLeaks) {

?? super(referent, refQueue);

?

?? trackedHash = System.identityHashCode(referent);

?? allLeaks.put(this, LeakEntry.INSTANCE);

?? // Create a new Record so we always have the creation stacktrace included.

?? headUpdater.set(this, new Record(Record.BOTTOM));

?? this.allLeaks = allLeaks;

}

[拓展]:引用隊(duì)列(Reference Queue)

一旦弱引用對(duì)象開始返回null王财,該弱引用指向的對(duì)象就被標(biāo)記成了垃圾卵迂。而這個(gè)弱引用對(duì)象(非其指向的對(duì)象)就沒(méi)有什么用了。通常這時(shí)候需要進(jìn)行一些清理工作绒净。比如WeakHashMap會(huì)在這時(shí)候移除沒(méi)用的條目來(lái)避免保存無(wú)限制增長(zhǎng)的沒(méi)有意義的弱引用见咒。

引用隊(duì)列可以很容易地實(shí)現(xiàn)跟蹤不需要的引用。當(dāng)你在構(gòu)造WeakReference時(shí)傳入一個(gè)ReferenceQueue對(duì)象挂疆,當(dāng)該引用指向的對(duì)象被標(biāo)記為垃圾的時(shí)候改览,這個(gè)引用對(duì)象會(huì)自動(dòng)地加入到引用隊(duì)列里面下翎。接下來(lái),你就可以在固定的周期宝当,處理傳入的引用隊(duì)列视事,比如做一些清理工作來(lái)處理這些沒(méi)有用的引用對(duì)象。

當(dāng)referent為 ByteBuf 對(duì)象時(shí),如果它被正確的釋放庆揩,即調(diào)用了release()方法俐东,從而調(diào)用了 AbstractReferenceCountedByteBuf 的closeLeak()方法,最終調(diào)用到 ResourceLeakTracker#close(trackedByteBuf)方法,那么該 ByteBuf 對(duì)象對(duì)應(yīng)的 ResourceLeakTracker 對(duì)象订晌,將從ResourceLeakDetector.allLeaks中移除虏辫。在ResourceLeakDetector#reportLeak()方法中,即使從refQueue隊(duì)列中锈拨,獲取到該 ByteBuf 對(duì)象對(duì)應(yīng) ResourceLeakTracker 對(duì)象砌庄,因?yàn)樵赗esourceLeakDetector.allLeaks中移除了,所以在ResourceLeakDetector#reportLeak()!ref.dispose() = true奕枢,continue 就不報(bào)告內(nèi)存泄漏了娄昆。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市缝彬,隨后出現(xiàn)的幾起案子萌焰,更是在濱河造成了極大的恐慌,老刑警劉巖跌造,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杆怕,死亡現(xiàn)場(chǎng)離奇詭異族购,居然都是意外死亡壳贪,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門寝杖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)违施,“玉大人,你說(shuō)我怎么就攤上這事瑟幕】钠眩” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵只盹,是天一觀的道長(zhǎng)辣往。 經(jīng)常有香客問(wèn)我,道長(zhǎng)殖卑,這世上最難降的妖魔是什么站削? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮孵稽,結(jié)果婚禮上许起,老公的妹妹穿的比我還像新娘十偶。我一直安慰自己,他們只是感情好园细,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布惦积。 她就那樣靜靜地躺著,像睡著了一般猛频。 火紅的嫁衣襯著肌膚如雪狮崩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天鹿寻,我揣著相機(jī)與錄音厉亏,去河邊找鬼。 笑死烈和,一個(gè)胖子當(dāng)著我的面吹牛爱只,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播招刹,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼恬试,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了疯暑?” 一聲冷哼從身側(cè)響起训柴,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎妇拯,沒(méi)想到半個(gè)月后幻馁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡越锈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年仗嗦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甘凭。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡稀拐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丹弱,到底是詐尸還是另有隱情德撬,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布躲胳,位于F島的核電站蜓洪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏坯苹。R本人自食惡果不足惜隆檀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刚操,春花似錦闸翅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鉴逞,卻和暖如春记某,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背构捡。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工液南, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人勾徽。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓滑凉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親喘帚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子畅姊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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