關(guān)于java.lang.OutOfMemoryError知多少(一)

概況

在實(shí)際的java應(yīng)用中,我們可能會(huì)碰到成千上萬中java.lang.OutOfMemoryError的棍好,不過總體癥狀體現(xiàn)往往在如下8種情況中

一北秽、java.lang.OutOfMemoryError之java heap space

1畜号、簡述

一般呢脊串,java應(yīng)用程序只能使用有限的內(nèi)存诲泌,常常是在應(yīng)用啟動(dòng)的時(shí)候來指定的比如參數(shù)-Xmx和-XX:MaxPermSize。內(nèi)存會(huì)被劃分為兩個(gè)區(qū)域:heap(堆空間)和 permgen(永久代)


內(nèi)存構(gòu)成.png

對應(yīng)區(qū)域的大小是在Java虛擬機(jī)(JVM)啟動(dòng)期間設(shè)置的磕谅,可以通過指定JVM參數(shù)- xmx和- xx:MaxPermSize來定制。若沒有顯式地設(shè)置大小,則使用特定于平臺(tái)的默認(rèn)值甚亭。
往往出現(xiàn)java.lang.OutOfMemoryError,是由于應(yīng)用程序試圖向堆空間區(qū)域添加更多數(shù)據(jù)時(shí)击胜,卻沒有足夠的空間亏狰,便會(huì)觸發(fā)Java堆空間錯(cuò)誤。

注意:即使當(dāng)前機(jī)器可能有很多物理內(nèi)存可用偶摔,JVM達(dá)到堆大小限制時(shí)暇唾,仍然會(huì)出現(xiàn)java.lang.OutOfMemoryError,拋出Java堆空間錯(cuò)誤。

2策州、原因

對應(yīng)上面出現(xiàn)的問題瘸味,是什么原因所致?接下來我們來剖析下:
比較常見的當(dāng)我們常見將需要XXL大小的應(yīng)用放入到S大小的java堆空間抽活,中硫戈,由于應(yīng)用所需空間不足,應(yīng)用不能運(yùn)行下硕,導(dǎo)致OutOfMemoryError 丁逝。
除此之外其他程序錯(cuò)誤的導(dǎo)致OutOfMemoryError的原因比較復(fù)雜:

  • 流量/數(shù)據(jù)峰值
    應(yīng)用程序自身的處理存在一定的限額,比如一定數(shù)量的用戶或一定數(shù)量的數(shù)據(jù)梭姓。而當(dāng)用戶數(shù)量或數(shù)據(jù)量突然激增并超過預(yù)期的閾值時(shí)霜幼,那么就會(huì)峰值停止前正常運(yùn)行的操作將停止并觸發(fā)java . lang.OutOfMemoryError:Java堆空間錯(cuò)誤
  • 內(nèi)存泄漏
    編程中存在的缺陷錯(cuò)誤會(huì)導(dǎo)致應(yīng)用程序不斷消耗更多內(nèi)存。每次使用該應(yīng)用程序誉尖,都會(huì)將一些對象留到Java堆空間中罪既。隨著時(shí)間的推移,泄漏的對象消耗掉所有可用的Java堆空間铡恕,并觸發(fā)已經(jīng)熟悉的Java . lang.OutOfMemoryError:Java堆空間錯(cuò)誤
3琢感、實(shí)例

1、簡單實(shí)例
分配2M大小int[],當(dāng)我們通過java -Xmx12m OOM則會(huì)出現(xiàn)Java . lang.OutOfMemoryError探熔; 而一旦將java堆空間設(shè)置為13M則不會(huì)出現(xiàn)OutOfMemoryError

class OOM {
  static final int SIZE=2*1024*1024;
  public static void main(String[] a) {
    int[] i = new int[SIZE];
   }
}

關(guān)于數(shù)組內(nèi)存大小計(jì)算:
首先知道java對象內(nèi)存構(gòu)成 = 對象頭(Header) + 實(shí)例數(shù)據(jù)(Instance Data)+ 對齊填充(Padding)
-對象頭在32位系統(tǒng)上占用8bytes驹针,64位系統(tǒng)上占用16bytes
-引用類型在32位系統(tǒng)上每個(gè)占用4bytes, 在64位系統(tǒng)上每個(gè)占用8bytes;

-HotSpot的對齊方式為8字節(jié)對齊
-開啟(-XX:+UseCompressedOops)會(huì)導(dǎo)致指針壓縮诀艰,導(dǎo)致大小不一樣柬甥,詳情請自行學(xué)習(xí)《深入理解Java虛擬機(jī)》
上例:計(jì)算一維數(shù)組總內(nèi)存 = 對象頭(24bytes) + 4bytes * 210241024

>javac OOM.java
>java -Xmx12m OOM
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at OOM.main(OOM.java:4)
>java -Xmx13m OOM

2、內(nèi)存泄漏
在Java中其垄,開發(fā)人員創(chuàng)建并使用新對象(例如new Integer(5))時(shí)苛蒲,不必自己分配內(nèi)存——這是由Java虛擬機(jī)(JVM)負(fù)責(zé)的。在應(yīng)用程序的生命周期中绿满,JVM會(huì)定期檢查內(nèi)存中哪些對象仍在使用臂外,哪些不是。未使用的對象可以被丟棄喇颁,內(nèi)存被回收再利用寄月。這個(gè)過程稱為垃圾收集。負(fù)責(zé)收集的JVM中對應(yīng)的模塊稱為垃圾收集器(GC)无牵。
Java的自動(dòng)內(nèi)存管理依賴于GC周期性地查找未使用的對象并刪除它們漾肮。簡單地說,Java中的內(nèi)存泄漏是一些對象不再被應(yīng)用程序使用但垃圾收集無法識別的情況茎毁。因此克懊,這些未使用的對象仍然在Java堆空間中無限期地存在忱辅。不停的堆積最終會(huì)觸發(fā)java . lang.OutOfMemoryError

class KeylessEntry { 
   static class Key {
      Integer id; 
      Key(Integer id) {
         this.id = id;
      } 
      @Override
      public int hashCode() {
         return id.hashCode();
      }
   } 
   public static void main(String[] args) {
      Map m = new HashMap();
      while (true)
         for (int i = 0; i < 10000; i++)
            if (!m.containsKey(new Key(i)))
               m.put(new Key(i), "Number:" + i);
   }
}

當(dāng)執(zhí)行上面的代碼時(shí),可能會(huì)期望它永遠(yuǎn)運(yùn)行谭溉,不會(huì)出現(xiàn)任何問題墙懂,假設(shè)單純的緩存解決方案只將底層映射擴(kuò)展到10,000個(gè)元素,而不是所有鍵都已經(jīng)在HashMap中扮念。然而事實(shí)上元素將繼續(xù)被添加损搬,因?yàn)閗ey類并沒有重寫它的hashCode()和equals()。
隨著時(shí)間的推移柜与,隨著不斷使用的泄漏代碼巧勤,“緩存”的結(jié)果最終會(huì)消耗大量Java堆空間。當(dāng)泄漏內(nèi)存填充堆區(qū)域中的所有可用內(nèi)存時(shí)弄匕,垃圾收集無法清理它颅悉,java . lang.OutOfMemoryError。
相對來說對應(yīng)的解決方案比較簡單:重寫equals方法

@Override
public boolean equals(Object o) {
   boolean response = false;
   if (o instanceof Key) {
      response = (((Key)o).id).equals(this.id);
   }
   return response;
}

二迁匠、java.lang.OutOfMemoryError之GC overhead limit exceeded

1剩瓶、簡述

Java運(yùn)行時(shí)環(huán)境包含一個(gè)內(nèi)置的垃圾收集(GC)過程。在許多其他編程語言中城丧,開發(fā)人員需要手動(dòng)地分配和釋放內(nèi)存區(qū)域延曙,以便可以重用釋放的內(nèi)存。
另外亡哄,Java應(yīng)用程序只需要分配內(nèi)存搂鲫。每當(dāng)內(nèi)存中的某個(gè)特定空間不再使用時(shí),一個(gè)稱為“垃圾收集”的單獨(dú)進(jìn)程就會(huì)為它們清除內(nèi)存磺平。
當(dāng)應(yīng)用程序耗盡所有可用內(nèi)存時(shí),GC開銷限制超過了錯(cuò)誤拐辽,而GC多次未能清除它拣挪,這時(shí)便會(huì)引發(fā)java.lang.OutOfMemoryError

2、原因

當(dāng)JVM花費(fèi)大量的時(shí)間執(zhí)行GC俱诸,而收效甚微菠劝,而一旦整個(gè)GC的過程超過限制便會(huì)觸發(fā)錯(cuò)誤。默認(rèn)的jvm配置GC的時(shí)間超過98%睁搭,回收堆內(nèi)存低于2%赶诊。


實(shí)例圖.png

若是JVM沒有設(shè)置上圖的限制,會(huì)帶來什么樣的后果:意味著GC能夠清理的少量堆將很快再次被填充园骆,迫使GC再次重新啟動(dòng)清洗過程舔痪。這形成了一個(gè)惡性循環(huán),CPU 100%忙于GC锌唾,沒有實(shí)際工作可做锄码。應(yīng)用程序的最終用戶面臨極度的減速——通常以毫秒為單位的操作需要幾分鐘才能完成夺英。
因此," java. lang.OutOfMemoryError:GC開銷上限“是一個(gè)很好的快速失敗的例子

3、實(shí)例
class Wrapper {
  public static void main(String args[]) throws Exception {
    Map map = System.getProperties();
    Random r = new Random();
    while (true) {
      map.put(r.nextInt(), "value");
    }
  }
}

當(dāng)我們執(zhí)行 java -Xmx100m -XX:+UseParallelGC Wrapper
由于上述代碼將不會(huì)被停止滋捶,很快我們就會(huì)得到j(luò)ava.lang.OutOfMemoryError:GC開銷超過限額痛悯;不過該實(shí)例在不同堆大小或不同GC算法會(huì)得到不同的結(jié)果:
(1)、應(yīng)用程序?qū)伋龀R姷膉ava . lang.OutOfMemoryError及Java堆空間消息重窟。
(2)载萌、采用其他垃圾收集算法(比如- xx:+ UseConcMarkSweepGC或- xx:+ UseG1GC)運(yùn)行它時(shí),錯(cuò)誤被默認(rèn)的異常處理程序捕獲巡扇,并且沒有stacktrace扭仁,由于堆已經(jīng)耗盡,stacktrace甚至不能填充異常中
在資源受限的情況下霎迫,無法預(yù)測應(yīng)用程序?qū)⑷绾螔斓粽啵虼瞬荒軐⑵谕谔囟ǖ牟僮餍蛄衼硗瓿?/p>

三、解決方案

1知给、在某些情況下瓤帚,可能由于分配給JVM的堆的數(shù)量不足以滿足運(yùn)行在JVM上的應(yīng)用程序的需求。在這種情況下涩赢,一般是分配更多的堆來嘗試解決下戈次。指定JVM參數(shù)-Xmx的值
java -Xmx1073741824 XXX_Class
java -Xmx1048576k XXX_Class
java -Xmx1024m XXX_Class
java -Xmx1g XXX_Class
2、在許多情況下筒扒,提供更多的Java堆空間并不能解決問題怯邪。例如應(yīng)用程序包含內(nèi)存泄漏,添加更多堆將延遲java . lang.OutOfMemoryError:Java堆空間錯(cuò)誤花墩。此外悬秉,增加Java堆空間的數(shù)量也增加了影響應(yīng)用程序吞吐量或延遲的GC暫停時(shí)間。
如果希望解決Java堆空間的底層問題冰蘑,而不是掩蓋這些癥狀和泌,那么需要找出代碼中哪個(gè)部分負(fù)責(zé)分配最多的內(nèi)存.需要搞懂兩點(diǎn)
(1)、哪個(gè)對象占據(jù)了大部分堆內(nèi)存
(2)祠肥、在代碼什么地方分配了這些對象
操作的過程大致如下:

  • 首先獲得安全性許可武氓,以便獲取JVM執(zhí)行heap dump〕鹣洌“dumps”基本上是堆內(nèi)容的快照便于分析县恕。這些快照可以包含機(jī)密信息,如密碼剂桥、信用識別碼等忠烛,因此,由于安全原因权逗,導(dǎo)致不能獲取這些dumps况木。
  • 其次要在合適的時(shí)刻獲取dump垒拢;一旦獲取dump的時(shí)刻不合適會(huì)有很多干擾信息存在dump文件里面不便于我們的分析。另外一個(gè)事實(shí)是獲取dump時(shí)會(huì)“暫突鹁”JVM求类,影響應(yīng)用性能
  • 另外找一臺(tái)機(jī)器用來分析這些dumps,并且結(jié)合當(dāng)前這些jvm故障點(diǎn)使用heap大小屹耐,分析機(jī)器需要提供超過對應(yīng)的heap大小heap來完成分析尸疆。這時(shí)我們可以使用專門的工具比如 [Eclipse MAT]、【JProfiler】甚至其他的專業(yè)軟件惶岭。
  • 檢測堆的最大占用者的GC根路徑寿弱,這塊對初學(xué)者存在一定的難度,不過我們嘗試下
  • 最后結(jié)合前面分析的結(jié)果按灶,弄清楚在您的源代碼中症革,潛在危險(xiǎn)的大量對象被分配到哪里【需要對自身的代碼比較熟悉,這個(gè)過程相對比較簡單的】
    如下是采用[Plumbr]分析個(gè)結(jié)果


    Plumbr分析.png

    分析結(jié)果如下:
    (1)鸯旁、哪些對象正在消耗最多的內(nèi)存(271個(gè) com.example.map.impl.PartitionContainer實(shí)例消耗248MB總堆內(nèi)存中的173MB噪矛。
    (2)這些對象在什么地方分配的(其中大部分[99%]分配在MetricManagerImpl類,第304行)
    (3)铺罢、當(dāng)前引用這些對象的內(nèi)容(完整的引用鏈到GC根)
    有了這些信息艇挨,就可以將定位底層的根源,并確保數(shù)據(jù)結(jié)構(gòu)被縮減到合適內(nèi)存使用級別韭赘。
    如果當(dāng)從內(nèi)存分析或閱讀Plumbr報(bào)告得出的結(jié)論是缩滨,內(nèi)存使用是合法的,并且在源代碼中沒有任何更改泉瞻,這時(shí)就需要允許提供更多地運(yùn)行Java堆空間給JVM脉漏。在這種情況下,就調(diào)整JVM啟動(dòng)配置袖牙,并添加(或增加當(dāng)前的值)侧巨, 類似-Xmx1024m

四、其他

最后給各位推薦一個(gè)阿里JVM大神寒泉子的公眾號(lovestblog)及其開發(fā)的JVM參數(shù)小程序(JVMPcoket)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贼陶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子巧娱,更是在濱河造成了極大的恐慌碉怔,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件禁添,死亡現(xiàn)場離奇詭異撮胧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)老翘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門芹啥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锻离,“玉大人,你說我怎么就攤上這事墓怀∑溃” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵傀履,是天一觀的道長虱朵。 經(jīng)常有香客問我,道長钓账,這世上最難降的妖魔是什么碴犬? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮梆暮,結(jié)果婚禮上服协,老公的妹妹穿的比我還像新娘。我一直安慰自己啦粹,他們只是感情好偿荷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著卖陵,像睡著了一般遭顶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上泪蔫,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天棒旗,我揣著相機(jī)與錄音,去河邊找鬼撩荣。 笑死铣揉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的餐曹。 我是一名探鬼主播逛拱,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼台猴!你這毒婦竟也來了朽合?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤饱狂,失蹤者是張志新(化名)和其女友劉穎曹步,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體休讳,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡讲婚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了俊柔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筹麸。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡活合,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出物赶,到底是詐尸還是另有隱情白指,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布块差,位于F島的核電站侵续,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏憨闰。R本人自食惡果不足惜状蜗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鹉动。 院中可真熱鬧轧坎,春花似錦、人聲如沸泽示。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽械筛。三九已至捎泻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間埋哟,已是汗流浹背笆豁。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赤赊,地道東北人闯狱。 一個(gè)月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像抛计,于是被迫代替她去往敵國和親哄孤。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

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

  • 從三月份找實(shí)習(xí)到現(xiàn)在吹截,面了一些公司瘦陈,掛了不少,但最終還是拿到小米波俄、百度晨逝、阿里、京東弟断、新浪咏花、CVTE趴生、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,254評論 11 349
  • 1.什么是垃圾回收阀趴? 垃圾回收(Garbage Collection)是Java虛擬機(jī)(JVM)垃圾回收器提供...
    簡欲明心閱讀 89,503評論 17 311
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理昏翰,因此不免有一些不準(zhǔn)確的地方,同時(shí)不同JDK版本的...
    高廣超閱讀 15,608評論 3 83
  • 故黃河橋上交警攔住一輛電三輪刘急,伸手拔掉車鑰匙棚菊,要把三輪車拖走。三輪車的女主人叔汁,忙下跪磕頭求交警高抬貴手统求。馬上圍了路...
    銘玥詠全閱讀 178評論 0 0
  • 聚集函數(shù) 聚集函數(shù)(aggregate function)對某些行運(yùn)行的函數(shù),計(jì)算并返回一個(gè)值据块。 1AVG()函數(shù)...
    ATHAS閱讀 779評論 0 50