JVM內存模型詳解
1.基本概念
JVM實際上是運行在一個具體操作系統(tǒng)上的程序進程撒妈,對Java代碼而言恢暖,JVM就是操作系統(tǒng)的代理。
如圖所示是JVM的內存模型及數(shù)據交互狰右。JVM的內存模型依然是基于操作系統(tǒng)進程空間的杰捂,不過是自己設計了一套內存管理體系以支撐上層的Java代碼。
JVM的運行時內存可以簡單的分為線程私有和公共內存棋蚌,線程私有部分包含程序計數(shù)器嫁佳、Java棧、native方法棧谷暮。全局公共部分包含方法區(qū)蒿往、堆空間。
2.程序計數(shù)器
不止是JVM湿弦,操作系統(tǒng)本身就有程序計數(shù)器的概念瓤漏,可以把JVM的程序計數(shù)器看做是對操作系統(tǒng)本身程序計數(shù)器的一種抽象。
程序計數(shù)器會記錄當前線程下一條字節(jié)碼的位置。當線程被掛起然后被恢復的時候蔬充,會根據程序計數(shù)器恢復線程的執(zhí)行邏輯俯在。特別的,如果該線程正在執(zhí)行一個native方法娃惯,那么此時線程寄存器的值為”undefined”。
3.Java方法棧
Java棧也是線程私有的肥败。每個方法在執(zhí)行的時候都會同時生成一個棧幀趾浅,用于存儲局部變量、操作數(shù)棧馒稍、動態(tài)鏈接皿哨、方法出口等信息。方法執(zhí)行從開始到結束的過程纽谒,對應了棧幀在虛擬機Java棧中從入棧到出棧的過程证膨。
局部變量表中存儲了基本類型及引用類型(即對象的指針),其中64位長度的long和double類型的數(shù)據會占用2個局部變量空間(Slot)鼓黔,其余的數(shù)據類型只占用1個央勒。局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時澳化,這個方法需要在幀中分配多大的局部變量空間是完全確定的崔步,在方法運行期間不會改變局部變量表的大小。
4.native方法棧
native方法棧與Java方法棧類似缎谷,不過native方法棧是為虛擬機使用到的native方法服務的井濒。
Java虛擬機規(guī)范對于這塊沒有強制規(guī)定,因此Sun HotSpot甚至直接就把native方法棧和Java方法棧合二為一列林。
5.Java堆
Java堆(Java Heap)是Java虛擬機所管理的內存中最大的一塊瑞你。Java堆是被所有線程共享的一塊內存區(qū)域,在虛擬機啟動時創(chuàng)建希痴。此內存區(qū)域的唯一目的就是存放對象實例者甲,幾乎所有的對象實例都在這里分配內存。這一點在Java虛擬機規(guī)范中的描述是:所有的對象實例以及數(shù)組都要在堆上分配润梯。
Java堆是垃圾收集器管理的主要區(qū)域过牙,因此很多時候也被稱做“GC堆”。關于Java堆的詳細結構纺铭,也需要和GC機制一起來講才能比較清楚的理解寇钉,此處先跳過。
通過虛擬機啟動參數(shù)舶赔,我們可以控制Java堆的最大內存占用扫倡,如果超過最大內存,會觸發(fā)OutOfMemory異常,進而導致內存申請失敗撵溃。如果出現(xiàn)這種異常疚鲤,就要考慮是參數(shù)設置太小還是存在堆內存泄露。
6.方法區(qū)
方法區(qū)(Method Area)與Java堆一樣缘挑,是各個線程共享的內存區(qū)域集歇,它用于存儲已被虛擬機加載的類信息、常量语淘、靜態(tài)變量诲宇、即時編譯器編譯后的代碼等數(shù)據。雖然Java虛擬機規(guī)范把方法區(qū)描述為堆的一個邏輯部分惶翻,但是它卻有一個別名叫做Non-Heap(非堆)姑蓝,目的應該是與Java堆區(qū)分開來。
對于習慣在HotSpot虛擬機上開發(fā)吕粗、部署程序的開發(fā)者來說纺荧,很多人都更愿意把方法區(qū)稱為“永久代”(Permanent Generation),本質上兩者并不等價颅筋,僅僅是因為HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至方法區(qū)宙暇,或者說使用永久代來實現(xiàn)方法區(qū)而已,這樣HotSpot的垃圾收集器可以像管理Java堆一樣去管理這部分內存议泵。
但是使用永久代來實現(xiàn)方法區(qū)客给,并不是一個好主意,因為這樣更加容易遇到內存溢出問題肢簿,永久代的內存分配一般比較小且固定靶剑,但是當碰到String.intern這種運行時占用永久代內存空間的方法的時候,很容易導致永久代內存不夠用池充。因此在jdk1.7中桩引,已經把放在永久代中的字符串常量池移入到堆內存當中了。
7.運行時常量池
運行時常量池是方法區(qū)的一部分收夸,存放了class文件在編譯期生成的各種字面量和符號引用坑匠。這部分內容在類加載的時候放入運行時常量池中。
運行時常量池是動態(tài)變化的卧惜,不止存儲了class文件在編譯期生成的各種字面量厘灼,運行期間也可能放入新的常量,比如String類的intern方法咽瓷。
8.直接內存
直接內存并不是虛擬機運行時區(qū)域的一部分设凹,也不是Java虛擬機規(guī)范定義的內存區(qū)域。JDK 1.4中新加入的NIO類茅姜,引入了一種基于通道和緩沖區(qū)的I/O方式闪朱,它可以使用native函數(shù)庫直接分配堆外內存,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作。這樣做是為了能在一些場景中顯著提高性能奋姿,因為避免了Java堆和native堆來回復制數(shù)據锄开。
本機直接內存的分配不受到Java堆的大小限制,但會受到物理內存和操作系統(tǒng)的限制称诗。