近期學(xué)習(xí)了JVM,借此整理一下JVM有關(guān)的內(nèi)存模型和各種內(nèi)存溢出泼差。
運(yùn)行時(shí)數(shù)據(jù)區(qū)域
要理解Java的內(nèi)存模型椭盏,作者覺得最好是從線程的角度去理解比較好。分為線程共享部分和線程隔離部分。這樣各個(gè)區(qū)域有各自的用途利职,以及分配和清除的時(shí)間趣效。有些區(qū)域隨著用戶線程產(chǎn)生而產(chǎn)生,有些區(qū)域隨著虛擬機(jī)啟動(dòng)的時(shí)候就開始存在猪贪。Java程序運(yùn)行時(shí)的數(shù)據(jù)區(qū)域主要如下所示(注意:Java SE 1.7)
程序計(jì)數(shù)器
程序計(jì)數(shù)器是一塊比較小的內(nèi)存跷敬,可以看做是當(dāng)前線程的執(zhí)行的字節(jié)碼的行號(hào)指示器。因?yàn)镴ava程序的class文件是字節(jié)碼热押,虛擬機(jī)通過程序計(jì)數(shù)器來執(zhí)行下一條需要執(zhí)行的指令(循環(huán)西傀,跳轉(zhuǎn),異常處理桶癣,線程恢復(fù)等)拥褂。這其中有一個(gè)特別重要的功能:Java的多線程中,是通過爭奪cpu來執(zhí)行程序的牙寞,沒有爭奪到cpu的線程則會(huì)進(jìn)入等待的狀態(tài)饺鹃,而當(dāng)?shù)却木€程搶占到cpu后,會(huì)有狀態(tài)的切換碎税。這時(shí)候則根據(jù)程序計(jì)數(shù)器的指令來恢復(fù)到正確執(zhí)行的位置尤慰。還有一個(gè)重點(diǎn)就是,這個(gè)區(qū)域是Java虛擬機(jī)規(guī)范中唯一一個(gè)不會(huì)出現(xiàn)OutOfmemoryError的區(qū)域雷蹂。
虛擬機(jī)棧
每一個(gè)線程創(chuàng)建的時(shí)候同時(shí)會(huì)創(chuàng)建自己獨(dú)立的虛擬機(jī)棧伟端,用于存放棧幀,棧幀是用于存放局部變量和一些過程結(jié)果的地方匪煌。Java虛擬機(jī)規(guī)范規(guī)定虛擬機(jī)椩痱穑可以固定分配或者動(dòng)態(tài)擴(kuò)展。
虛擬機(jī)椢ィ可能發(fā)生如下異常情況:
- 如果線程請(qǐng)求分配的棧容量大于虛擬機(jī)允許的容量(也就是滿棧)的時(shí)候霜医,虛擬機(jī)就會(huì)拋出StackOverflowError異常。
- 如果線程新建時(shí)沒有足夠的內(nèi)存去創(chuàng)建虛擬機(jī)棧驳规,或者在平時(shí)動(dòng)態(tài)擴(kuò)展過程中肴敛,已經(jīng)申請(qǐng)擴(kuò)展,但無法申請(qǐng)到足夠的內(nèi)存去擴(kuò)展虛擬機(jī)棧吗购,那虛擬機(jī)就會(huì)拋出一個(gè)OutOfMemoryError異常医男。
/**
* 虛擬機(jī)棧StackOverflowError示例
* VM args:-Xss128k
*/
public class StackSOF{
public static void main(String[] args){
neverGoOut();
}
public static void neverGoOut(){
neverGoOut();
}
}
/**
* 虛擬機(jī)棧OutOfMemoryError示例
* 在Linux物理機(jī)上才能跑出來
* VM args: -Xss2M
*/
public class JavaVMStackOOM{
public static void main(String[] args){
while(true){
new Thread(new Runnable(){
@Override
public void run(){
neverDown();
}
}).start();
}
}
private static void neverDown(){
while(true){}
}
}
本地方法棧
和虛擬機(jī)棧一樣,每一個(gè)線程創(chuàng)建的時(shí)候也會(huì)創(chuàng)建自己獨(dú)立的本地方法棧捻勉,只不過這個(gè)棧是用來存放本地方法的镀梭,也就是native調(diào)用的方法。本地方法棧也是可以固定分配或者動(dòng)態(tài)擴(kuò)展踱启。
本地方法棻ㄕ耍可能發(fā)生如下異常情況:
- 如果線程請(qǐng)求分配的棧容量大于本地方法棧允許的容量的時(shí)候研底,虛擬機(jī)就會(huì)拋出一個(gè)StackOverflowError異常。
- 如果線程新建時(shí)沒有足夠的內(nèi)存去創(chuàng)建本地方法棧透罢,或者在平時(shí)動(dòng)態(tài)擴(kuò)展過程中榜晦,已經(jīng)申請(qǐng)擴(kuò)展,但無法申請(qǐng)到足夠的內(nèi)存去擴(kuò)展虛擬機(jī)棧琐凭,那虛擬機(jī)就會(huì)拋出一個(gè)OutOfMemoryError異常芽隆。
雖然HotSpot有-Xoss參數(shù)可以設(shè)置本地方法棧的大小,但實(shí)際上是無效的统屈,棧容量只有-Xss參數(shù)設(shè)定,所以該部分的驗(yàn)證方法參考虛擬機(jī)棧胚吁。
方法區(qū)
在Java虛擬機(jī)中,方法區(qū)是線程運(yùn)行是共享的區(qū)域愁憔,它存儲(chǔ)著每一個(gè)類的結(jié)構(gòu)信息腕扶,包括運(yùn)行時(shí)常量池,字段和方法數(shù)據(jù)吨掌,構(gòu)造方法和普通方法的字節(jié)碼內(nèi)容半抱。因?yàn)榉椒▍^(qū)是線程共享的部分,所以它在Java虛擬機(jī)啟動(dòng)的時(shí)候被創(chuàng)建膜宋。
方法區(qū)可能發(fā)生如下異常情況:
- 如果方法區(qū)的內(nèi)存空間不能滿足內(nèi)存分配的要求窿侈,Java虛擬機(jī)則會(huì)拋出一個(gè)OutOfMemoryError異常。
驗(yàn)證:
import java.util.List;
import java.util.ArrayList;
/**
* 方法區(qū)運(yùn)行常量池OutOfMemoryError示例
* Java version:1.6因?yàn)?.6的String.intern()方法是在首次出現(xiàn)的字符串復(fù)制入永久代秋茫,而1.7版本則只會(huì)放置一個(gè)引用到永久代(所以不能觸發(fā)內(nèi)存溢出)
* VM args: -XX:PermSize=10M -XX:MaxPermSize=10m
* */
public class RuntimeConstantPoolOOM{
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
int i = 0;
while(true){
list.add(String.valueOf(i++).intern());
}
}
}
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
*方法區(qū)加載類信息OutOfMemoryError示例
* jar包:cglib-3.2.4.jar,asm-5.1.jar
* VM args:-XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class JavaMethodAreaOOM{
public static void main(String[] args){
while(true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor(){
@Override
public Object intercept(Object obj, Method method, Object[] objs, MethodProxy proxy)throws Throwable{
return proxy.invokeSuper(obj, objs);
}
});
enhancer.create();
}
}
static class OOMObject{}
}
堆
Java堆
堆是各個(gè)線程共享的運(yùn)行時(shí)內(nèi)存區(qū)域史简,也就是每一個(gè)對(duì)象和數(shù)組的分配內(nèi)存的區(qū)域。堆在Java虛擬機(jī)啟動(dòng)的時(shí)候就創(chuàng)建了肛著,它存儲(chǔ)了內(nèi)存自動(dòng)管理系統(tǒng)圆兵,也就是我們常說的垃圾回收器。堆是可以固定大小也可以動(dòng)態(tài)分配的枢贿。
堆可能發(fā)生如下異常情況:
如果實(shí)際所需的堆超過了垃圾回收器提供的最大容量殉农,Java虛擬機(jī)則會(huì)拋出一個(gè)OutOfMemoryError異常。
驗(yàn)證:
/**
* Java堆OutOfMemoryError示例
* VM args:-Xmx10M -Xms10M
*/
public class HeapOOM{
public static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
byte[] b = new byte[10 * 1024 * 1024];
}
}
本機(jī)直接內(nèi)存
直接內(nèi)存分配可以越過堆直接向操作系統(tǒng)申請(qǐng)分配內(nèi)存局荚。DirectMemory容量可以通過-XX:MaxDirectMemorySize指定超凳,如果不指定,則默認(rèn)和Java堆最大值一樣耀态。以下示例代碼是用unsafe直接分配本機(jī)內(nèi)存導(dǎo)致的內(nèi)存溢出:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* 直接內(nèi)存分配OutOfMemoryError示例
* VM args:-Xmx20M -XX:MaxDirectMemorySize=10M
*/
public class DirectMemoryOOM{
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws Exception{
Field field = Unsafe.class.getDeclaredFields()[0];
field.setAccessible(true);
Unsafe unsafe = (Unsafe)field.get(null);
while(true){
unsafe.allocateMemory(_1MB);
}
}
}
參考資料
《深入理解Java虛擬機(jī)》
《Java 虛擬機(jī)規(guī)范(Java SE 7 版)》