在日常中我們經(jīng)常遇到這樣的錯(cuò)誤:java.lang.OutOfMemoryError: Java heap space。
但是除了heap space 的OutOfMemoryError,還有其它幾種OutOfMemoryError情況奏属。今天我們就來了解一下:
1其馏、java.lang.OutOfMemoryError: Java heap space夭苗。
這是因?yàn)樘摂M機(jī)堆的空間所剩不多缀遍。當(dāng)準(zhǔn)備創(chuàng)建的對(duì)象需要的內(nèi)存已經(jīng)超過虛擬機(jī)堆所剩的空間筷弦。虛擬機(jī)會(huì)嘗試通過full GC來回收內(nèi)存溅潜,如果不行的話,就會(huì)拋出OutOfMemoryError。
導(dǎo)致OutOfMemoryError異常的常見原因有以下幾種:
內(nèi)存中加載的數(shù)據(jù)量過于龐大弧械,如一次性從DB取出過多數(shù)據(jù)蜕乡;
集合類中有對(duì)象的引用,使用完后未清空设捐,使得JVM不能回收借浊;
代碼中存在死循環(huán)或循環(huán)產(chǎn)生過多重復(fù)的對(duì)象實(shí)體;
啟動(dòng)參數(shù)內(nèi)存值設(shè)定的過小萝招。
舉一個(gè)常見的OutOfMemoryError場(chǎng)景:先從DB讀取數(shù)據(jù)存放到內(nèi)存中蚂斤,然后遍歷處理。
public class HeapSpaceOutOfMemory {
private static List<byte[]> getDataFromDb() {
List<byte[]> list = new ArrayList<>();
for (int i = 0; i < 500; i++) {
byte[] data = new byte[1024 * 1024];// 1M的對(duì)象
list.add(data);
}
return list;
}
public static void main(String[] args) {
// 1即寒、從db取數(shù)據(jù)橡淆,大小為500M
List<byte[]> list = getDataFromDb();
// 2、遍歷處理list
for (byte[] data : list) {
//do something
}
System.out.println("success");
}
}
JVM參數(shù): -Xms64M -Xmx128M -XX:+PrintGCDetails -Xloggc:/apps/logs/heap_demo.log
運(yùn)行結(jié)果:
查看GC Log母赵,可以看到有Full GC 的痕跡逸爵,但是Full GC的收效果不明顯,年輕代和年老代都沒有足夠的空間為即將創(chuàng)建的對(duì)象分配空間凹嘲。
解決OOM最快的方法就是調(diào)整-Xmx參數(shù)师倔,增加堆的大小。
調(diào)整JVM參數(shù): -Xms64M -Xmx1024M -XX:+PrintGCDetails -Xloggc:/apps/logs/heap_demo.log
雖然通過調(diào)整-Xmx參數(shù)解決了上面OutOfMemoryError的問題周蹭,但是如果DB的數(shù)據(jù)突然暴漲到5G趋艘、50G疲恢、500G的時(shí)候,還是會(huì)出現(xiàn)OutOfMemoryError瓷胧。此時(shí)通過調(diào)整-Xmx參數(shù)就不合適了显拳,先不說機(jī)器有沒有這么大的內(nèi)存分配,就算機(jī)器的內(nèi)存夠分配搓萧,F(xiàn)ull GC導(dǎo)致程序停頓的時(shí)間也會(huì)很長(zhǎng)杂数。因此我們要從代碼下手,分批次處理數(shù)據(jù)瘸洛,“小步快跑”揍移,將5G的數(shù)據(jù)拆分成10個(gè)批次,50G的數(shù)據(jù)拆分成100個(gè)批次反肋,500G的數(shù)據(jù)拆分成1000個(gè)批次那伐,每個(gè)批次處理500M的數(shù)據(jù),一個(gè)批次處理完后內(nèi)存回收石蔗。這樣的話就不用再擔(dān)心突然暴漲的數(shù)據(jù)量導(dǎo)致程序OutOfMemoryError罕邀。
2、java.lang.OutOfMemoryError: Metaspace
JDK1.7中抓督,存儲(chǔ)在永久代的部分?jǐn)?shù)據(jù)就已經(jīng)轉(zhuǎn)移到了Java Heap或者是 Native Heap燃少,譬如符號(hào)引用(Symbols)轉(zhuǎn)移到了native heap;字面量(interned strings)轉(zhuǎn)移到了java heap铃在;類的靜態(tài)變量(class statics)轉(zhuǎn)移到了java heap阵具。但永久代仍存在于JDK1.7中,并沒完全移除定铜。
JDK 8.HotSpot JVM使用本地化的內(nèi)存存放類的元數(shù)據(jù)阳液,這個(gè)空間叫做元空間(Metaspace)。官方定義:"In JDK 8, classes metadata is now stored in the native heap and this space is called Metaspace"揣炕。元空間的本質(zhì)和永久代類似帘皿,都是對(duì)JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)。不過元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中畸陡,而是使用本地內(nèi)存鹰溜。因此,默認(rèn)情況下丁恭,元空間的大小僅受本地內(nèi)存限制曹动,但可以通過以下參數(shù)來指定元空間的大小:-XX:MetaspaceSize牲览、-XX:MaxMetaspaceSize墓陈。
測(cè)試代碼:
public class MetaspaceOutOfMemory {
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
for (int i = 0;; i++) {
cp.makeClass("com.demo.MetaspaceClass" + i).toClass();
Thread.sleep(1);
}
}
}
JVM參數(shù):-XX:MetaspaceSize=32m -XX:MaxMetaspaceSize=64m -Xloggc:/apps/logs/heap_demo.log
打開VisualVm工具,可以發(fā)現(xiàn)Metaspace使用的空間大小隨類裝入的數(shù)量增加而增加,這也說明了Metaspace是用來存放類的元數(shù)據(jù)的贡必。
3兔港、java.lang.OutOfMemoryError: PermGen space
修改JVM參數(shù)為:-XX:PermSize=32M -XX:MaxPermSize=64M -Xloggc:/apps/logs/heap_demo.log,切換到JDK7下運(yùn)行MetaspaceOutOfMemory仔拟。打開VisualVm工具衫樊,此時(shí)出現(xiàn)了PermGen標(biāo)簽頁(yè)。隨著類裝載的數(shù)量增加理逊,最終出現(xiàn)了java.lang.OutOfMemoryError: PermGen space橡伞,進(jìn)程退出。
4晋被、java.lang.OutOfMemoryError: GC overhead limit exceeded
GC overhead limt exceed檢查是Hotspot VM 1.6定義的一個(gè)策略,通過統(tǒng)計(jì)GC時(shí)間來預(yù)測(cè)是否要OOM了刚盈,提前拋出異常羡洛。官方定義是:“并行/并發(fā)回收器在GC回收時(shí)間過長(zhǎng)時(shí)會(huì)拋出OutOfMemroyError。過長(zhǎng)的定義是藕漱,超過98%的時(shí)間用來做GC并且回收了不到2%的堆內(nèi)存"欲侮。JVM默認(rèn)啟動(dòng)的時(shí)候-XX:+UseGCOverheadLimit,即啟用了該特性肋联。
測(cè)試程序:
public class GCOverheadLimit {
public static void main(String[] args) {
List list = new ArrayList();
String str = "1";
for (int i = 0; i < 1000000; i++) {
for (int j = 0; j < 100; j++) {
str += "$" + i;
}
list.add(str);
}
}
}
JVM參數(shù):-Xms64M -Xmx128M -XX:+UseGCOverheadLimit
運(yùn)行結(jié)果:
最近項(xiàng)目有一個(gè)程序威蕉,因?yàn)轭l繁Full GC導(dǎo)致程序僵死現(xiàn)象,一致耗著橄仍,如果加上-XX:+UseGCOverheadLimit參數(shù)就可以讓程序提前退出韧涨,避免僵死程序長(zhǎng)期占用資源。
5侮繁、java.lang.OutOfMemoryError: unable to create new native thread
如果JVM正在請(qǐng)求操作系統(tǒng)創(chuàng)建一個(gè)本地線程虑粥,而操作系統(tǒng)無法創(chuàng)建的時(shí)候,就會(huì)出現(xiàn)這個(gè)報(bào)錯(cuò)信息宪哩。JVM中可以生成的最大線程數(shù)量由JVM的堆內(nèi)存大小娩贷、Thread的Stack內(nèi)存大小、系統(tǒng)最大可創(chuàng)建的線程數(shù)量(Java線程的實(shí)現(xiàn)是基于底層系統(tǒng)的線程機(jī)制來實(shí)現(xiàn)的锁孟,Windows下_beginthreadex彬祖,Linux下pthread_create)三個(gè)方面影響。
6品抽、java.lang.OutOfMemoryError: Requested array size exceeds VM limit
當(dāng)你正準(zhǔn)備創(chuàng)建一個(gè)超過虛擬機(jī)允許的大小的數(shù)組時(shí)储笑,這條錯(cuò)誤就會(huì)出現(xiàn)在你眼前。64位的操作系統(tǒng)上桑包,JDK7南蓬,如果數(shù)組的長(zhǎng)度是Integer.MAX_VALUE-1,就會(huì)出現(xiàn)。
byte a[] = new byte[Integer.MAX_VALUE-1];
7、java.lang.OutOfMemoryError: request bytes for . Out of swap space赘方?
這個(gè)錯(cuò)誤是當(dāng)虛擬機(jī)向本地操作系統(tǒng)申請(qǐng)內(nèi)存失敗時(shí)拋出的烧颖。這和你用完了堆或者持久化中的內(nèi)存的情況有些不同。這個(gè)錯(cuò)誤通常是在你的程序已經(jīng)逼近平臺(tái)限制的時(shí)候產(chǎn)生的窄陡。這個(gè)信息告訴你的是你可能已經(jīng)用光了物理內(nèi)存以及虛擬內(nèi)存了炕淮。由于虛擬內(nèi)存通常是用磁盤作為交換分區(qū),因此你最先想到的解決方法可能是先增加交換分區(qū)的大小跳夭。不過我從沒見過一個(gè)程序在頻繁進(jìn)行內(nèi)存交換還能正常運(yùn)行的涂圆,所以這個(gè)方法可能不會(huì)起到什么作用。