JVM 虛擬機(2)自動內(nèi)存管理機制

如果把內(nèi)存比作蛋糕,那么堆、棧不過是其中的一小塊沸毁。

Java內(nèi)存區(qū)域

運行時數(shù)據(jù)區(qū)域

線程共享數(shù)據(jù)區(qū):方法區(qū)、堆
線程私有數(shù)據(jù)區(qū):虛擬機棧傻寂、本地方法棧息尺、程序計數(shù)器

Java虛擬機運行時數(shù)據(jù)區(qū)

程序計數(shù)器

主要記錄當(dāng)前線程正在執(zhí)行的虛擬機字節(jié)碼指令。因為程序計數(shù)器是線程私有的數(shù)據(jù)區(qū)疾掰,所以在多線程切換時搂誉,也不會造成指令錯亂。
另外静檬,這也是唯一一個Java虛擬機規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域炭懊。

虛擬機棧

虛擬機棧的生命周期和線程一樣,它描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的同時拂檩,都會創(chuàng)建出一個棧幀(Stack Frame)用于存儲局部變量表侮腹、操作數(shù)棧、動態(tài)鏈接稻励、方法出口等信息父阻。每一個方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機棧中入棧到出棧的過程。
通常所說的堆棧中的“椉用”钠署,指的就是這個虛擬機棧,更恰當(dāng)?shù)恼f荒椭,應(yīng)該是虛擬機棧中的局部變量表谐鼎。
局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte趣惠、char狸棍、short、int味悄、float草戈、long、double)侍瑟、對象引用(reference類型唐片,它不等同于對象本身,可能是一個指向?qū)ο笃鹗嫉刂返囊弥羔樥茄眨部赡苁侵赶蛞粋€代表對象的句柄或其它與此對象相關(guān)的位置)和returnAddress類型(指向了一條字節(jié)碼指令的地址)费韭。
注意!注意庭瑰!注意星持!局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進入一個方法時弹灭,這個方法需要在幀中分配多大的局部變量空間是完全確定的督暂,在方法運行期間不會改變局部變量表的大小。
兩種異常:

  1. StackOverflowError
    如果線程請求的棧深度大于虛擬機鎖允許的深度穷吮,將拋出該異常
  2. OutOfMemoryError
    如果虛擬機可以動態(tài)擴展逻翁,當(dāng)擴展時無法申請到足夠的內(nèi)存,就拋出該異常捡鱼。

本地方法棧(Native Method Stack)

它和虛擬機棧的區(qū)別是:虛擬機棧為虛擬機執(zhí)行Java方法(也就是字節(jié)碼)服務(wù)八回,而本地方法棧則為虛擬機使用到的Native方法服務(wù)。
虛擬機規(guī)范沒有限制本地方法棧做特殊使用的語言堰汉、方法辽社、數(shù)據(jù)結(jié)構(gòu)等伟墙,所以不同的虛擬機可以有不同的實現(xiàn)翘鸭,甚至如HotSpot虛擬機,將本地方法棧和虛擬機棧合二為一戳葵。

Java堆

堆是Java虛擬機管理的最大的一塊內(nèi)存就乓。
所有實例對象以及數(shù)組都要在堆上分配。
堆也是垃圾收集器管理的主要區(qū)域(GC堆,Garbage Collected Heap)生蚁。
從內(nèi)存回收角度來看噩翠,由于現(xiàn)在的收集器基本都采用分代收集算法,所以Java堆中還可以分為:新生代邦投、老年代伤锚。再細(xì)致一點,可分為Eden空間志衣、From Survivor空間屯援、To Survivor空間等。
從內(nèi)存分配角度看念脯,線程共享的Java堆中可能劃分出多個線程私有的份額皮緩沖區(qū)(Thread Local Allocation Buffer狞洋, TLAB)。

方法區(qū) Method Area

用于存儲已被虛擬機加載的類信息绿店、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。

運行時常量池 Runtime Constant Pool

運行時常量池是方法區(qū)的一部分,Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息時常量池腕唧,用于存放編譯期產(chǎn)生的各種字面量和符號引用缺谴,這部分內(nèi)容將在類加載后進入方法區(qū)的運行時常量池中存放。

直接內(nèi)存 Direct Memory

它不是虛擬機運行時數(shù)據(jù)區(qū)的一部分苫纤,也不是Java虛擬機規(guī)范中定義的內(nèi)存區(qū)域。但是會經(jīng)常使用到乍赫。
在JDK1.4中新加入的NIO類改鲫,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O方式稽亏,它可以使用Native函數(shù)庫直接分配堆外內(nèi)存,然后通過一個存在Java堆中的DirectByteBuffer對象作為這塊內(nèi)存的引用進行操作缕题。這樣能在一些場景中顯著提高性能截歉,因為避免了在Java堆和Native堆中來回復(fù)制區(qū)域。

HotSpot虛擬機

對象創(chuàng)建

  1. Java中的new關(guān)鍵字避除,對于虛擬機來說是一條new指令怎披,當(dāng)虛擬機接收到這條指令后,首先去檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用瓶摆,并且檢查這個符號引用代表的類是否已經(jīng)被加載凉逛、解析和初始化過,如果沒有群井,那必須先執(zhí)行相應(yīng)的類加載過状飞。
  2. 類加載完成后,虛擬機將為新生對象分配內(nèi)存(虛擬機如何保證操作原子性书斜?)诬辈。對象所需內(nèi)存的 大小在類加載的時候是完成可確定的,為對象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從Java堆中劃分出來荐吉。常用的內(nèi)存分配方式有“指針碰撞Bump the Pointer焙糟、空閑列表Free List”。另外样屠,選擇哪種分配方式由Java堆是否規(guī)整決定穿撮,而Java堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。
    3.分配完成后痪欲,虛擬機會自動分配零值(不包括對象頭)悦穿,以保證對象的實例字段在Java代碼中可以不賦值就可以直接使用。
    4.虛擬機設(shè)置對象頭业踢。

對象的內(nèi)存布局

在HotSpot虛擬機中栗柒,對象在內(nèi)存中存儲的布局可以分為3塊區(qū)域:對象頭(Header)、實例數(shù)據(jù)(Instance Data)和對齊填充(Padding)知举。
對象頭分為兩部分

  1. 第一部分用于存儲對象自身的運行時數(shù)據(jù)瞬沦,如哈希嗎(HashCode)、GC分代年齡雇锡、鎖狀態(tài)標(biāo)志蛙埂、線程持有的鎖、偏向線程ID遮糖、偏向時間戳等绣的。
  2. 另一分部是類型指針,即對象指向它的類元數(shù)據(jù)的指針欲账,虛擬機通過這個指針來確定這個對象是哪個類的實例屡江。并不是所有的虛擬機實現(xiàn)都必須在對象數(shù)據(jù)上保留類型指針

實例數(shù)據(jù)
對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內(nèi)容赛不。

對齊填充
占位符的左右惩嘉。
由于HotSpot VM的自動內(nèi)存管理系統(tǒng)要求對象起始地址必須是8字節(jié)的整數(shù)倍,換句話說踢故,就是對象的大小必須是8字節(jié)的整數(shù)倍文黎。而對象頭部分正好是8字節(jié)的倍數(shù)(1倍或2倍)惹苗,因此,當(dāng)對象實例數(shù)據(jù)部分沒有對齊時耸峭,就需要通過對齊填充來補全桩蓉。

對象的訪問定位

  1. 通過句柄訪問對象
    如果使用句柄訪問對象,nameJava堆中將會劃分出一塊內(nèi)存來作為句柄池劳闹,reference引用中存儲的就是對象的句柄地址院究,而句柄中包含了對象實例數(shù)據(jù)和類型數(shù)據(jù)各自的具體地址信息。


    通過句柄訪問對象
  2. 通過直接指針訪問對象
    如果使用直接指針訪問本涕,那么Java堆對象的布局就必須考慮如何放置訪問類型數(shù)據(jù)的相關(guān)信息业汰,而reference中存儲的直接就是對象地址。


    通過直接指針訪問對象

Java 溢出異常

OutOfMemoryrror異常

堆溢出

Java堆用于存儲對象實例菩颖,只要不斷地創(chuàng)建對象样漆,并且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清楚這些對象,那么在對象數(shù)量到達最大對的容量限制后晦闰,就會產(chǎn)生內(nèi)存溢出異常氛濒。

/**
 * -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/error
 */
public class Main {

    static class OOMObject {

    }

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

其中:-Xms20m -Xmx20m用來控制堆大小為20m,-設(shè)置XX:+HeapDumpOnOutOfMemoryError是為了在內(nèi)存溢出的時候鹅髓,保存堆溢出的文件舞竿,后期可以通過工具分析該文件,找出具體的原因是什么窿冯。-XX:HeapDumpPath=/home/error是為了指定堆溢出時骗奖,文件存儲的位置,如果不指定醒串,默認(rèn)存儲到了項目根目錄下(我電腦上是這樣)执桌。

錯誤日志:

Connected to the target VM, address: '127.0.0.1:14065', transport: 'socket'
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid20124.hprof ...
Disconnected from the target VM, address: '127.0.0.1:14065', transport: 'socket'
Heap dump file created [28058107 bytes in 0.138 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:261)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
    at java.util.ArrayList.add(ArrayList.java:458)
    at com.Main.main(Main.java:21)

棧溢出

對于HotSpot虛擬機來說,棧容量只由-Xss參數(shù)設(shè)定芜赌。

/**
 * -Xss128k
 */
public class JavaVMStackSOF {
    private int stackLenth = 1;

    public void stackLeak() {
        stackLenth ++;
        stackLeak();
    }

    public static void main(String[] args) {
        JavaVMStackSOF stackSOF = new JavaVMStackSOF();
        try {
            stackSOF.stackLeak();
        } catch (Throwable e) {
            e.printStackTrace();
            throw e;
        }
    }
}

在這種單線程下仰挣,無論是由于棧幀太大還是虛擬機棧容量太小,當(dāng)內(nèi)存無法分配的時候缠沈,虛擬機拋出的都是StackOverflowError異常膘壶。
另外,如果測試不限于單線程洲愤,通過不斷創(chuàng)建線程的方式可以產(chǎn)生內(nèi)存溢出異常颓芭,原因是:操作系統(tǒng)分配給每個進程的內(nèi)存是有限的,譬如32位Windows限制為2GB柬赐。虛擬機提供了參數(shù)來控制Java堆和方法區(qū)的這兩部分內(nèi)存的最大值亡问。剩余的內(nèi)存為2GB(操作系統(tǒng)限制)-Xmx(最大堆容量)-MaxPermSize(最大方法區(qū)容量)-程序計數(shù)器(實際可忽略其內(nèi)存大小)肛宋。如果虛擬機進程本身消耗的內(nèi)存不計算在內(nèi)州藕,剩下 的內(nèi)存就由虛擬機棧和本地方法棧瓜分了束世,每個線程分配到的棧容量越大,可以建立的線程數(shù)量自然就越少床玻,建立線程的時候就越容易把剩下的內(nèi)存耗盡毁涉。

錯誤日志:

Connected to the target VM, address: '127.0.0.1:8169', transport: 'socket'
java.lang.StackOverflowError
    at com.memory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)
    at com.memory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
    at com.memory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
    at com.memory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
    at com.memory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)

方法區(qū)和運行時常量池溢出

/**
 * 該代碼是在jdk1.6上跑的,會出現(xiàn)OOM笨枯,但是在jdk1.7或以上版本薪丁,while循環(huán)會一直下去
 * -XX:PermSize=10M -XX:MaxPermSize=10M
 */
public class RuntimeConstantPoolOOM{
    public static void main(String[] args) {
        List<String> list = Lists.newArrayList();
        int i = 0;
        while (true) {
            list.add(String.valueOf(i++).intern());
        }
    }
}

錯誤日志:

Connected to the target VM, address: '127.0.0.1:8336', transport: 'socket'
Disconnected from the target VM, address: '127.0.0.1:8336', transport: 'socket'
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
    at java.lang.String.intern(Native Method)

方法區(qū)溢出遇西,因為方法區(qū)存放的是Class相關(guān)信息馅精,如類名、訪問修飾符粱檀、常量池洲敢、字段描述、方法描述等茄蚯。咱們可以通過不斷的創(chuàng)建類對象压彭,撐爆方法區(qū)。

/**
 * -XX:PermSize=10M -XX:MaxPermSize=10M
 */
public class JavaMethodAreaOOM{

    static class OOMObject {}

    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 o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invokeSuper(o, objects);
                }
            });
            enhancer.create();
        }
    }
}
Connected to the target VM, address: '127.0.0.1:8417', transport: 'socket'
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
Disconnected from the target VM, address: '127.0.0.1:8417', transport: 'socket'

注釋:

虛擬機如何保證操作原子性:虛擬機采用CAS+失敗重試的方式保證更新操作的原子性渗常。
指針碰撞:假設(shè)Java堆中內(nèi)存是絕對規(guī)整的壮不,所有用過的內(nèi)存都放在一邊,空閑的內(nèi)存放在另一邊皱碘,中間放著一個指針作為分界點的指示器询一。那所分配內(nèi)存就僅僅把那個指針向空閑空間那邊挪動一段與對象大小相等的距離。
空閑列表:如果Java堆中的內(nèi)存不是規(guī)整的癌椿,已使用的內(nèi)存和空閑的內(nèi)存相互交錯健蕊,那就沒有辦法簡單的進行指針碰撞了,虛擬機就必須維護一個列表踢俄,記錄哪些內(nèi)存是可用的缩功,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,并更新列表上的記錄都办。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嫡锌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子琳钉,更是在濱河造成了極大的恐慌世舰,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件槽卫,死亡現(xiàn)場離奇詭異跟压,居然都是意外死亡,警方通過查閱死者的電腦和手機歼培,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門震蒋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茸塞,“玉大人,你說我怎么就攤上這事查剖〖嘏埃” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵笋庄,是天一觀的道長效扫。 經(jīng)常有香客問我,道長直砂,這世上最難降的妖魔是什么菌仁? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮静暂,結(jié)果婚禮上济丘,老公的妹妹穿的比我還像新娘。我一直安慰自己洽蛀,他們只是感情好摹迷,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著郊供,像睡著了一般峡碉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上驮审,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天鲫寄,我揣著相機與錄音,去河邊找鬼头岔。 笑死塔拳,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的峡竣。 我是一名探鬼主播靠抑,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼适掰!你這毒婦竟也來了颂碧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤类浪,失蹤者是張志新(化名)和其女友劉穎载城,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體费就,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡诉瓦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睬澡。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡固额,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出煞聪,到底是詐尸還是另有隱情斗躏,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布昔脯,位于F島的核電站啄糙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏云稚。R本人自食惡果不足惜隧饼,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望碱鳞。 院中可真熱鬧桑李,春花似錦踱蛀、人聲如沸窿给。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崩泡。三九已至,卻和暖如春猬膨,著一層夾襖步出監(jiān)牢的瞬間角撞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工勃痴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谒所,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓沛申,卻偏偏與公主長得像劣领,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子铁材,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359

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