日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術(shù)文章
文章詳情頁

Java 分析并解決內(nèi)存泄漏的實(shí)例

瀏覽:33日期:2022-08-26 08:01:09

這幾天,一直在為Java的“內(nèi)存泄露”問題糾結(jié)。Java應(yīng)用程序占用的內(nèi)存在不斷的、有規(guī)律的上漲,最終超過了監(jiān)控閾值。福爾摩 斯不得不出手了!

分析內(nèi)存泄露的一般步驟

如果發(fā)現(xiàn)Java應(yīng)用程序占用的內(nèi)存出現(xiàn)了泄露的跡象,那么我們一般采用下面的步驟分析:

把Java應(yīng)用程序使用的heap dump下來 使用Java heap分析工具,找出內(nèi)存占用超出預(yù)期(一般是因?yàn)閿?shù)量太多)的嫌疑對象 必要時(shí),需要分析嫌疑對象和其他對象的引用關(guān)系。 查看程序的源代碼,找出嫌疑對象數(shù)量過多的原因。

dump heap

如果Java應(yīng)用程序出現(xiàn)了內(nèi)存泄露,千萬別著急著把應(yīng)用殺掉,而是要保存現(xiàn)場。如果是互聯(lián)網(wǎng)應(yīng)用,可以把流量切到其他服務(wù)器。保存現(xiàn)場的目的就是為了把 運(yùn)行中JVM的heap dump下來。

JDK自帶的jmap工具,可以做這件事情。它的執(zhí)行方法是:

jmap -dump:format=b,file=heap.bin <pid>

format=b的含義是,dump出來的文件時(shí)二進(jìn)制格式。file-heap.bin的含義是,dump出來的文件名是heap.bin。<pid>就是JVM的進(jìn)程號。(在linux下)先執(zhí)行ps aux | grep java,找到JVM的pid;然后再執(zhí)行jmap -dump:format=b,file=heap.bin <pid>,得到heap dump文件。

analyze heap

將二進(jìn)制的heap dump文件解析成human-readable的信息,自然是需要專業(yè)工具的幫助,這里推薦Memory Analyzer 。

Memory Analyzer,簡稱MAT,是Eclipse基金會的開源項(xiàng)目,由SAP和IBM捐助。巨頭公司出品的軟件還是很中用的,MAT可以分析包含數(shù)億級對 象的heap、快速計(jì)算每個(gè)對象占用的內(nèi)存大小、對象之間的引用關(guān)系、自動檢測內(nèi)存泄露的嫌疑對象,功能強(qiáng)大,而且界面友好易用。

MAT的界面基于Eclipse開發(fā),以兩種形式發(fā)布:Eclipse插件和Eclipe RCP。MAT的分析結(jié)果以圖片和報(bào)表的形式提供,一目了然??傊畟€(gè)人還是非常喜歡這個(gè)工具的。下面先貼兩張官方的screenshots:

Java 分析并解決內(nèi)存泄漏的實(shí)例

Java 分析并解決內(nèi)存泄漏的實(shí)例

言歸正傳,我用MAT打開了heap.bin,很容易看出,char[]的數(shù)量出其意料的多,占用90%以上的內(nèi)存 。一般來說,char[]在JVM確實(shí)會占用很多內(nèi)存,數(shù)量也非常多,因?yàn)镾tring對象以char[]作為內(nèi)部存儲。但是這次的char[]太貪婪 了,仔細(xì)一觀察,發(fā)現(xiàn)有數(shù)萬計(jì)的char[],每個(gè)都占用數(shù)百K的內(nèi)存 。這個(gè)現(xiàn)象說明,Java程序保存了數(shù)以萬計(jì)的大String對象 。結(jié)合程序的邏輯,這個(gè)是不應(yīng)該的,肯定在某個(gè)地方出了問題。

順藤摸瓜

在可疑的char[]中,任意挑了一個(gè),使用Path To GC Root功能,找到該char[]的引用路徑,發(fā)現(xiàn)String對象是被一個(gè)HashMap中引用的 。這個(gè)也是意料中的事情,Java的內(nèi)存泄露多半是因?yàn)閷ο蟊贿z留在全局的HashMap中得不到釋放。不過,該HashMap被用作一個(gè)緩存,設(shè)置了緩 存條目的閾值,導(dǎo)達(dá)到閾值后會自動淘汰。從這個(gè)邏輯分析,應(yīng)該不會出現(xiàn)內(nèi)存泄露的。雖然緩存中的String對象已經(jīng)達(dá)到數(shù)萬計(jì),但仍然沒有達(dá)到預(yù)先設(shè)置 的閾值(閾值設(shè)置地比較大,因?yàn)楫?dāng)時(shí)預(yù)估String對象都比較?。?。

但是,另一個(gè)問題引起了我的注意:為什么緩存的String對象如此巨大?內(nèi)部char[]的長度達(dá)數(shù)百K。雖然緩存中的 String對象數(shù)量還沒有達(dá)到閾值,但是String對象大小遠(yuǎn)遠(yuǎn)超出了我們的預(yù)期,最終導(dǎo)致內(nèi)存被大量消耗,形成內(nèi)存泄露的跡象(準(zhǔn)確說應(yīng)該是內(nèi)存消 耗過多) 。

就這個(gè)問題進(jìn)一步順藤摸瓜,看看String大對象是如何被放到HashMap中的。通過查看程序的源代碼,我發(fā)現(xiàn),確實(shí)有String大對象,不 過并沒有把String大對象放到HashMap中,而是把String大對象進(jìn)行split(調(diào)用String.split方法),然后將split出 來的String小對象放到HashMap中 了。

這就奇怪了,放到HashMap中明明是split之后的String小對象,怎么會占用那么大空間呢?難道是String類的split方法有問題?

查看代碼

帶著上述疑問,我查閱了Sun JDK6中String類的代碼,主要是是split方法的實(shí)現(xiàn):

public String[] split(String regex, int limit) { return Pattern.compile(regex).split(this, limit); }

可以看出,Stirng.split方法調(diào)用了Pattern.split方法。繼續(xù)看Pattern.split方法的代碼:

public String[] split(CharSequence input, int limit) { int index = 0; boolean matchLimited = limit > 0; ArrayList<String> matchList = new ArrayList<String>(); Matcher m = matcher(input); // Add segments before each match found while(m.find()) { if (!matchLimited || matchList.size() < limit - 1) { String match = input.subSequence(index, m.start()).toString(); matchList.add(match); index = m.end(); } else if (matchList.size() == limit - 1) { // last one String match = input.subSequence(index, input.length()).toString(); matchList.add(match); index = m.end(); } } // If no match was found, return this if (index == 0) return new String[] {input.toString()}; // Add remaining segment if (!matchLimited || matchList.size() < limit) matchList.add(input.subSequence(index, input.length()).toString()); // Construct result int resultSize = matchList.size(); if (limit == 0) while (resultSize > 0 && matchList.get(resultSize-1).equals('')) resultSize--; String[] result = new String[resultSize]; return matchList.subList(0, resultSize).toArray(result); } 注意看第9行:Stirng match = input.subSequence(intdex, m.start()).toString();

這里的match就是split出來的String小對象,它其實(shí)是String大對象subSequence的結(jié)果。繼續(xù)看 String.subSequence的代碼:

public CharSequence subSequence(int beginIndex, int endIndex) { return this.substring(beginIndex, endIndex); } String.subSequence有調(diào)用了String.subString,繼續(xù)看:

public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > count) { throw new StringIndexOutOfBoundsException(endIndex); } if (beginIndex > endIndex) { throw new StringIndexOutOfBoundsException(endIndex - beginIndex); } return ((beginIndex == 0) && (endIndex == count)) ? this : new String(offset + beginIndex, endIndex - beginIndex, value); }

看第11、12行,我們終于看出眉目,如果subString的內(nèi)容就是完整的原字符串,那么返回原String對象;否則,就會創(chuàng)建一個(gè)新的 String對象,但是這個(gè)String對象貌似使用了原String對象的char[]。我們通過String的構(gòu)造函數(shù)確認(rèn)這一點(diǎn):

// Package private constructor which shares value array for speed. String(int offset, int count, char value[]) { this.value = value; this.offset = offset; this.count = count; }

為了避免內(nèi)存拷貝、加快速度,Sun JDK直接復(fù)用了原String對象的char[],偏移量和長度來標(biāo)識不同的字符串內(nèi)容。也就是說,subString出的來String小對象 仍然會指向原String大對象的char[],split也是同樣的情況 。這就解釋了,為什么HashMap中String對象的char[]都那么大。

原因解釋

其實(shí)上一節(jié)已經(jīng)分析出了原因,這一節(jié)再整理一下:

程序從每個(gè)請求中得到一個(gè)String大對象,該對象內(nèi)部char[]的長度達(dá)數(shù)百K。程序?qū)tring大對象做split,將split得到的String小對象放到HashMap中,用作緩存。Sun JDK6對String.split方法做了優(yōu)化,split出來的Stirng對象直接使用原String對象的char[]HashMap中的每個(gè)String對象其實(shí)都指向了一個(gè)巨大的char[]HashMap的上限是萬級的,因此被緩存的Sting對象的總大小=萬*百K=G級。G級的內(nèi)存被緩存占用了,大量的內(nèi)存被浪費(fèi),造成內(nèi)存泄露的跡象。

解決方案

原因找到了,解決方案也就有了。split是要用的,但是我們不要把split出來的String對象直接放到HashMap中,而是調(diào)用一下 String的拷貝構(gòu)造函數(shù)String(String original),這個(gè)構(gòu)造函數(shù)是安全的,具體可以看代碼:

/** * Initializes a newly created {@code String} object so that it represents * the same sequence of characters as the argument; in other words, the * newly created string is a copy of the argument string. Unless an * explicit copy of {@code original} is needed, use of this constructor is * unnecessary since Strings are immutable. * * @param original * A {@code String} */ public String(String original) { int size = original.count; char[] originalValue = original.value; char[] v; if (originalValue.length > size) { // The array representing the String is bigger than the new // String itself. Perhaps this constructor is being called // in order to trim the baggage, so make a copy of the array. int off = original.offset; v = Arrays.copyOfRange(originalValue, off, off+size); } else { // The array representing the String is the same // size as the String, so no point in making a copy. v = originalValue; } this.offset = 0; this.count = size; this.value = v; }

只是,new String(string)的代碼很怪異,???;蛐恚?ubString和split應(yīng)該提供一個(gè)選項(xiàng),讓程序員控制是否復(fù)用String對象的 char[]。

是否Bug

雖然,subString和split的實(shí)現(xiàn)造成了現(xiàn)在的問題,但是這能否算String類的bug呢?個(gè)人覺得不好說。因?yàn)檫@樣的優(yōu)化是比較合理 的,subString和spit的結(jié)果肯定是原字符串的連續(xù)子序列。只能說,String不僅僅是一個(gè)核心類,它對于JVM來說是與原始類型同等重要的 類型。

JDK實(shí)現(xiàn)對String做各種可能的優(yōu)化都是可以理解的。但是優(yōu)化帶來了憂患,我們程序員足夠了解他們,才能用好他們。

一些補(bǔ)充

有個(gè)地方我沒有說清楚。

我的程序是一個(gè)Web程序,每次接受請求,就會創(chuàng)建一個(gè)大的String對象,然后對該String對象進(jìn)行split,最后split之后的String對象放到全局緩存中。如果接收了5W個(gè)請求,那么就會有5W個(gè)大String對象。這5W個(gè)大String對象都被存儲在全局緩存中,因此會造成內(nèi)存泄漏。我原以為緩存的是5W個(gè)小String,結(jié)果都是大String。

有同學(xué)后續(xù)建議用'java.io.StreamTokenizer'來解決本文的問題。確實(shí)是終極解決方案,比我上面提到的“new String()”,要好很多很多。

以上就是Java 分析并解決內(nèi)存泄漏的實(shí)例的詳細(xì)內(nèi)容,更多關(guān)于Java 內(nèi)存泄漏的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Java
相關(guān)文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
国产一区2区在线观看| 中文字幕日本一区二区| 国产情侣一区在线| 欧美韩一区二区| 国产精品嫩模av在线| 成人污污视频| 亚洲免费观看高清完整版在线观| 欧美日韩一区二区国产| 国产日韩电影| 丝袜美腿亚洲一区| 成人在线视频免费| 视频一区免费在线观看| 日本欧美国产| 日韩中文欧美在线| 国产传媒在线| 婷婷精品在线观看| 国产日韩欧美三级| 日本成人手机在线| 国产主播一区| 日本亚洲最大的色成网站www| 亚洲涩涩在线| 国产欧美69| 三级欧美在线一区| 福利在线免费视频| 日韩精品免费一区二区夜夜嗨 | 欧美精品观看| 欧美+亚洲+精品+三区| 国产麻豆一区二区三区精品视频| 午夜国产欧美理论在线播放| 免费一级欧美片在线观看网站 | 免费一区二区三区在线视频| 亚洲日本久久| 国产精品99一区二区三| 久久超级碰碰| 日韩精品福利一区二区三区| 国产视频一区三区| 日韩国产激情| 精品日产乱码久久久久久仙踪林| 欧美午夜不卡影院在线观看完整版免费| 精品资源在线| 91午夜精品| 在线观看亚洲精品福利片| 午夜久久一区| 欧美一级精品| 久久久久亚洲| 国产中文一区| 免费毛片在线不卡| 亚洲精品91| 韩国女主播一区二区三区| 精品网站aaa| 一区二区三区四区日韩| 久久99精品久久久野外观看| 麻豆精品网站| 欧美国产先锋| 亚洲精品日本| 午夜精品亚洲| 日韩理论片av| 欧美aⅴ一区二区三区视频| 一二三区精品| 日韩午夜电影| 人人精品亚洲| 国产精品成人3p一区二区三区| 欧美综合国产| 蜜桃tv一区二区三区| 国产网站在线| 精品一二三区| 欧美经典一区| 国产日产精品_国产精品毛片| 丝袜国产日韩另类美女| 久久伊人亚洲| 国产精品亚洲产品| 一区二区三区四区在线观看国产日韩| 99久久夜色精品国产亚洲狼| 国产一区二区三区网| 国产麻豆精品| 久久国产生活片100| 日韩精品视频在线看| 亚洲三级在线| 日本亚洲三级在线| 午夜亚洲福利| 日韩不卡一区二区| 日韩精品三区四区| 日韩成人av影视| 欧美在线不卡| 日本a级不卡| 欧美日韩调教| 国产香蕉精品| 国产精品magnet| 国产精品a级| 欧美精品不卡| 国产一区二区三区视频在线| 精品国产欧美日韩| 国产福利一区二区精品秒拍| 国产区精品区| 国产精品高清一区二区| 国产激情一区| 国产精品高颜值在线观看| 神马久久午夜| 久久精品国产www456c0m| 六月丁香综合在线视频| 精品一级视频| 日韩av首页| 99亚洲视频| 一区二区三区午夜视频| 日韩福利视频一区| 久久免费影院| 精品欧美一区二区三区在线观看| av最新在线| 午夜免费一区| 亚洲日韩视频| 国产精品国码视频| 日韩在线高清| 99国产精品视频免费观看一公开 | 日韩精品免费一区二区夜夜嗨 | 精品国产一区二区三区噜噜噜| 亚洲ab电影| 日韩欧美精品一区二区综合视频| 日本aⅴ精品一区二区三区| 国产欧美三级| 天海翼亚洲一区二区三区| 欧美片网站免费| 久久精品天堂| 欧美色图国产精品| 美女91精品| 日韩av中文字幕一区二区三区| 另类小说一区二区三区| 国产一区二区三区亚洲| 视频一区二区不卡| 综合一区二区三区| 国产精品66| 91精品国产成人观看| 蜜臀精品久久久久久蜜臀 | 久久精品99国产精品| 国产精品一区亚洲| 成人精品国产亚洲| 日韩视频在线一区二区三区 | 亚洲综合日韩| 国产精品久久久久久久久久白浆 | 婷婷精品视频| 综合激情一区| 久久精品资源| 影视先锋久久| 欧美一区影院| 欧美午夜精彩| 国产九一精品| 亚洲精品123区| 久久影院一区二区三区| 欧美va天堂在线| 国产欧美欧美| 欧美日韩国产高清| 欧美激情福利| 亚洲专区在线| 国产精品高颜值在线观看| 巨乳诱惑日韩免费av| 国产精品嫩模av在线| 久久久蜜桃一区二区人| 日韩高清一区| 欧美成人综合| 国产精品手机在线播放| 日韩午夜免费| 亚洲精品88| 国产探花一区| 热久久免费视频| 午夜av不卡| 国产精品免费精品自在线观看| 亚洲激情国产| 精品国产午夜| 亚洲精品中文字幕99999| 久久久人人人| 国产精品白丝一区二区三区| 欧美日韩激情在线一区二区三区| 婷婷视频一区二区三区| 在线国产一区二区| 91综合网人人| 国产精品大片| 7777精品| 免费人成在线不卡| 91精品一区二区三区综合| 久久不见久久见中文字幕免费 | 99香蕉国产精品偷在线观看| 欧美国产美女| 欧美国产视频| 亚洲精品少妇| 欧美jjzz| 久久久久久久久久久9不雅视频| 国产精品九九| 青青草国产精品亚洲专区无| 国产精品丝袜xxxxxxx| 91精品国产自产在线观看永久∴| 麻豆国产欧美一区二区三区| 国产亚洲一区二区三区啪| 亚洲精品无播放器在线播放| 亚洲在线观看| 亚洲欧美日韩一区在线观看| 自拍日韩欧美| 国产国产精品| 欧美色图国产精品| av中文资源在线资源免费观看| 国产精品久久国产愉拍| 88久久精品|