棧異常
- 如果線程請(qǐng)求分配的棧容量超過(guò)JVM允許的最大容量時(shí),會(huì)拋出StackOverflowError異常
- 如果java虛擬機(jī)椨岸ぃ可以動(dòng)態(tài)擴(kuò)展寸认,并且擴(kuò)展的動(dòng)作已經(jīng)嘗試過(guò)纵顾,但是無(wú)法申請(qǐng)到足夠的內(nèi)存去擴(kuò)展,會(huì)拋出OutOfMemoryError
- 如果創(chuàng)建新線程時(shí)沒(méi)有足夠的內(nèi)存去創(chuàng)建對(duì)應(yīng)的java虛擬機(jī)棧间唉,也會(huì)拋出OutOfMemoryError
public class JavaVMStackSOF{
private int stackLength = 1;
public void stackLeak(){
stackLength++;
stackLeak(); // 遞歸調(diào)用自己
}
public static void main(String[] args){
JavaVMStackSOF sof = new JavaVMStackSOF();
try{
sof.stackLeak();
}catch(Throwable e){
System.out.println("stack length=" + sof.stackLength);
throw e;
}
}
}
執(zhí)行結(jié)果:
bash> java JavaVMStackSOF -Xss128K
stack length=18357
Exception in thread "main" java.lang.StackOverflowError
at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:7)
at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:7)
而一直分配新的線程绞灼,死循環(huán),就會(huì)導(dǎo)致OOM呈野,這里就不演示了低矮。
所有的對(duì)象依然在堆中,局部變量表际跪、操作數(shù)棧中只有對(duì)應(yīng)的reference引用商佛,以及基礎(chǔ)數(shù)據(jù)類型。
堆
堆是所有線程全局共享的內(nèi)存空間姆打,一般也是jvm內(nèi)存區(qū)域中最大的一部分良姆,幾乎所有對(duì)象都在其中。
是GC回收的主要區(qū)域幔戏,所以也叫g(shù)c堆玛追。
為了提高不同線程在堆中內(nèi)存分配時(shí)的效率,減少?zèng)_突闲延,有的jvm實(shí)現(xiàn)會(huì)為每個(gè)線程在創(chuàng)建時(shí)同時(shí)創(chuàng)建TLAB痊剖,即threadlocal allocating buffer。線程在各自的tlab中分配對(duì)象垒玲,當(dāng)tlab中內(nèi)存用完時(shí)陆馁,才會(huì)加鎖,然后去堆中再去申請(qǐng)新的內(nèi)存合愈。
java堆還有一種劃分方式叮贩,就是新生代、老年代這種分代劃分佛析。
java堆可以在物理上不連續(xù)的空間上分配益老,只要邏輯上看起來(lái)是連續(xù)的即可〈缒可以使用-Xmx捺萌,-Xms來(lái)設(shè)定堆空間的最大和最小值。
當(dāng)java堆空間已經(jīng)不足以分配一個(gè)新對(duì)象膘茎,并且無(wú)法擴(kuò)展新空間桃纯,即使垃圾回收也無(wú)法會(huì)受到足夠的空間酷誓,就會(huì)報(bào)OutOfMemoryError異常。
堆和棧的關(guān)聯(lián)
如上圖所示:
- obj只是一個(gè)引用慈参,存放在執(zhí)行這條語(yǔ)句的線程的java虛擬機(jī)棧中呛牲,其中一個(gè)slot存儲(chǔ)了這個(gè)reference。
- obj指向了java堆中的內(nèi)存地址驮配,這個(gè)地址對(duì)應(yīng)object對(duì)象。
- Object對(duì)象頭中一般會(huì)存放當(dāng)前實(shí)例對(duì)應(yīng)的類型信息着茸,用于從方法區(qū)中尋找到對(duì)應(yīng)的類信息壮锻。
int a = 1;
int[] array = new int[] {1,2};
a是局部變量,而且是基本數(shù)據(jù)類型涮阔,會(huì)直接存在java虛擬機(jī)棧中猜绣,不過(guò)這個(gè)其實(shí)算是jvm提供的一種性能優(yōu)化,引用類型還是會(huì)放在堆中的敬特,只是其引用在棧中掰邢,尋址會(huì)存在一定的性能消耗。
array是個(gè)對(duì)象伟阔,array會(huì)作為一個(gè)引用存在棧中辣之,而對(duì)應(yīng)的數(shù)組對(duì)象會(huì)存儲(chǔ)在堆中。
Java堆內(nèi)存溢出
import java.util.ArrayList;
import java.util.List;
/**
-Xmx20m -Xms20m -XX:+HeapDumpOnOutOfMemoryError
*/
public class HeapOOM{
static class MyObect{}
public static void main(String[] args){
List<MyObect> list = new ArrayList<MyObect>();
for(;;){
list.add(new MyObect());
}
}
}
執(zhí)行的時(shí)候帶上jvm參數(shù)皱炉。會(huì)報(bào)錯(cuò)OutOfMemoryError怀估。
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid2173.hprof ...
Heap dump file created [27844982 bytes in 0.135 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:265)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
at java.util.ArrayList.add(ArrayList.java:462)
at HeapOOM.main(HeapOOM.java:16)
Process finished with exit code 1
后面詳細(xì)說(shuō)明對(duì)應(yīng)的調(diào)試。
方法區(qū)
- 方法區(qū)也是各個(gè)線程共享的合搅。
- 用于存儲(chǔ)已經(jīng)被jvm加載到方法區(qū)中的類型信息多搀。
- gc效率很低,一般用于運(yùn)行時(shí)常量池的數(shù)據(jù)回收和類的卸載灾部。
運(yùn)行時(shí)常量池
- 是方法區(qū)的一個(gè)組成部分
- 存儲(chǔ)Java類文件中常量信息康铭,用于存儲(chǔ)編譯期就生成好的字面量和符號(hào)引用。這部分信息在類被加載到方法區(qū)支行赌髓,會(huì)存入運(yùn)行時(shí)常量池从藤。
- ?存在運(yùn)行期間生成的常量,比如string的intern方法對(duì)應(yīng)的常量春弥。
永久代—>方法區(qū)
- jdk6之前呛哟,hotspot使用永久代來(lái)實(shí)現(xiàn)方法去。
- jdk7中匿沛,開(kāi)始移除永久代:
- 符號(hào)表被移入native heap中
- 字符串常量和類的靜態(tài)引用被移到j(luò)ava heap中
- jdk8中扫责,metaspace替代永久代,metaspace是在native heap中的逃呼。
演示代碼:
public class RuntimeConstantPoolChange {
public static void main(String[] args){
String str1 = new StringBuilder("alan").append("jin").toString();
System.out.println(str1 == str1.intern()); // intern:初次鳖孤,如果不存在者娱,會(huì)加入到常量池中,但是返回的是本身的reference苏揣,返回true
// 如果存在黄鳍,則返回的是方法區(qū)中地址,而str1返回的是堆中地址,所以下面兩個(gè)輸出全是false
String str2 = new StringBuilder("alan").toString();
System.out.println(str2.intern() == str2); // false
String str3 = new StringBuilder("java").toString(); // java 關(guān)鍵字已經(jīng)存在平匈,而且是在方法區(qū)中框沟,不在堆中,false
System.out.println(str3.intern() == str3);
}
}
方法區(qū)中OOM的代碼:
import java.util.ArrayList;
import java.util.List;
/**
* VM args: -Xmx10m -Xms10m
*/
public class RuntimeConstantPoolOOM {
public static void main(String[] args){
List<String> strings = new ArrayList<>();
int i = 0;
// 在1.6版本中增炭,int的取值范圍2的31次方忍燥,足夠撐滿永久代,所以會(huì)oom
// 在1.7及以上的jdk版本中隙姿,運(yùn)行時(shí)常量池在java heap中梅垄,可以永久執(zhí)行下去。但是如果java heap太小输玷,會(huì)廚下以下異常:
while (true){
strings.add(String.valueOf(i++).intern());
}
}
}
GC overhead limt exceed
檢查是Hotspot VM 1.6定義的一個(gè)策略队丝,通過(guò)統(tǒng)計(jì)GC時(shí)間來(lái)預(yù)測(cè)是否要OOM了,提前拋出異常欲鹏,防止OOM發(fā)生机久。Sun 官方對(duì)此的定義是:并行/并發(fā)回收器在GC回收時(shí)間過(guò)長(zhǎng)時(shí)會(huì)拋出OutOfMemroyError。過(guò)長(zhǎng)的定義是貌虾,超過(guò)98%的時(shí)間用來(lái)做GC并且回收了不到2%的堆內(nèi)存吞加。用來(lái)避免內(nèi)存過(guò)小造成應(yīng)用不能正常工作。
方法區(qū)又分成兩個(gè)部分尽狠,PermGen和CodeCache衔憨。其中PermGen存放java類的相關(guān)信息,如靜態(tài)變量袄膏、成員方法和抽象方法等践图。codecache存放JIT編譯后的本地代碼。沉馆?native代碼么码党?
直接內(nèi)存
直接內(nèi)存并不是JVM運(yùn)行時(shí)內(nèi)存的一部分
堆外內(nèi)存,NIO被引入斥黑,目的是為了避免java堆和native堆中來(lái)回復(fù)制數(shù)據(jù)帶來(lái)的性能損耗揖盘。
通過(guò)一個(gè)存儲(chǔ)在java heap中的DirectByteBuffer對(duì)象引用(通過(guò)虛引用(Phantom Reference)來(lái)實(shí)現(xiàn)堆外內(nèi)存的釋放)來(lái)管理native heap中的內(nèi)存空間。
全局共享的內(nèi)存區(qū)域锌奴,能夠被管理兽狭,但是檢測(cè)手段上會(huì)比較簡(jiǎn)陋
-
會(huì)出現(xiàn)OOM
import sun.misc.Unsafe; import java.lang.reflect.Field; import java.nio.ByteBuffer; /** * -Xmx20M -XX:MaxDirectMemorySize=10M */ public class DirectByteBufferOOM { private static final int size = 1024 * 1024 * 128; // 128M public static void main(String[] args) throws IllegalAccessException { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; // 獲取unsafe對(duì)象 unsafeField.setAccessible(true); // 設(shè)置操作權(quán)限 Unsafe unsafe = (Unsafe) unsafeField.get(null); System.out.println(sun.misc.VM.maxDirectMemory()); while (true){ // unsafe.allocateMemory 就是DirectByteBuffer分配內(nèi)存時(shí)使用到的方法,不建議直接使用,只是為了展示 // unsafe.allocateMemory(size); // 以上代碼無(wú)法產(chǎn)生OOM,但是下面的方法可以: ByteBuffer.allocateDirect(size); // As the original answer says: // Unsafe.allocateMemory() is a wrapper around os::malloc which doesn't care about any memory limits imposed by the VM. //ByteBuffer.allocateDirect() will call this method but before that, // it will call Bits.reserveMemory() (In my version of Java 7: DirectByteBuffer.java:123) // which checks the memory usage of the process and throws the exception which you mention. } } }
產(chǎn)生的OOM異常如下:
10485760 Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory at java.nio.Bits.reserveMemory(Bits.java:694) at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123) at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311) at DirectByteBufferOOM.main(DirectByteBufferOOM.java:28)
可以看到箕慧,如果是堆外內(nèi)存OOM服球,會(huì)顯示Direct Buffer Memory字樣,或者如果沒(méi)有明確的指示颠焦,但是dump出來(lái)的內(nèi)存很小斩熊,而且代碼中直接或者間接使用了NIO,那么也大概率是堆外內(nèi)存惹的禍伐庭。