jvm運作時資料區域
JVM主要由類加載器子系統、運作時資料區(記憶體空間)、執行引擎以及與本地方法接口等組成。其中運作時資料區又由方法區、堆、Java棧、PC寄存器、本地方法棧,直接記憶體組成。其中本地方法棧與直接記憶體由作業系統提供,jvm負責調用。
PC寄存器
PC寄存器又稱作程式計數器,每一個線程都是私有的。用于儲存目前線程正在執行的位置,由于Java是支援多線程執行的,是以程式執行的軌迹不可能一直都是線性執行。當有多個線程交叉執行時,被暫停的線程的程式目前執行到的位置必然要儲存下來,以便用于被暫停的線程恢複執行時再按照被暫停時的指令位址繼續執行下去。
堆(Heap)
java堆是Java虛拟機所管理的記憶體中最大的一塊。Java堆是所有線程共享的一塊記憶體區域,在虛拟機啟動的時候建立。此記憶體區域的唯一目的就是存放對象執行個體,幾乎所有的對象都存在這個記憶體空間中(随着JIT編譯器的發展和逃逸分析技術的逐漸成熟,棧上配置設定,标量替換優化技術的出現,是以并不是絕對的所有而是機會所有)。
Java堆是自動進行管理的,通過垃圾回收系統自動的清理java堆中的垃圾對象,是以有時候被稱為‘GC堆’。
從記憶體回收的角度來看Java堆的分類可以細分為新生代和老年代。在細緻一點新生代分為eden區,s0區,s1區

從記憶體配置設定的角度來看,線程共享的Java堆中可能劃分出來多個線程私有的配置設定緩沖區。不過無論如何劃分,無論哪個區域,存儲的都是對象執行個體,進一步劃分是為了更好的回收記憶體,或者更快的配置設定記憶體。
Java堆可以是實體上不連續的記憶體空間,隻需要邏輯上連續即可。Java堆的配置設定上是擴充的通過-Xmx和-Xms來設定最大值和最小值來控制。當堆的空間動态擴充超過最大值的時候會抛出異常OutOfMemoryError
Java虛拟機棧(Stack)
Java虛拟機棧是線程私有的,它的生命周期與線程相同。它所描述的是Java方法執行的記憶體模型,每個方法執行的時候都會建立一個棧幀(Stack Frame)用于存儲局部變量,操作棧、動态連結、方法出口等資訊。每一個方法被調用直到執行完成的過程,就對應着一個棧幀在Java虛拟機棧中從入棧到出棧的過程。
如圖:方法1對應棧幀1,方法2,3,4分别對應棧幀2,3,4
方法1執行時,棧幀1入棧
方法1調用方法2時,棧幀2入棧
方法2調用方法3時,棧幀3入棧
方法3調用方法4,棧幀4入棧
方法4執行完畢,棧幀4出棧
方法3執行完畢,棧幀3出棧
方法2執行完畢,棧幀2出棧
方法1執行完畢,棧幀1出棧
Java出棧有兩種方式,一種正常執行完畢,另外一種就是抛出異常,但是不管怎麼樣都會出棧。
Java棧是一塊記憶體區域,是有空間大小限制的。棧幀的局部變量都會申請記憶體空間,超過了記憶體的限制就會抛出異常OutOfMemoryError。當線程請求的棧深度超過了Java虛拟機允許的深度會抛出異常Stack Overflow(-Xss表示來指定線程最大的棧空間)
本地方法棧
本地方法棧與虛拟機棧發揮的作用非常類似,差別在于Java虛拟機棧調用的是Java方法,本地方法棧調用的本地(Native)方法
方法區
方法區和堆一樣,也是一塊共享記憶體空間,它用于存儲已經被Java虛拟機加載的類的資訊,常量、靜态變量、字段,方法等。方法區的大小決定了可以儲存多少個類,如果定義的類太多,也會抛出記憶體溢出異常(OutOfMemoryError)
在HotSpot版本的Java虛拟機上方法區被稱為“永久區(Perm)”,是因為HotSpot虛拟機的設計團隊把GC分代擴充到了方法區,這塊區域永遠不會回收,是以也叫做永久區。但是其他版本的Java虛拟機來說不存在永久區的概念。并且在Hotspot版本的jdk1.8以後由“中繼資料區”取代了永久區。
永久區通過參數-XX:permSize和-XX:MaxPermSize來設定,預設最大值為64M,如果程式中有動态代理來動态的生成類,那麼需要指定一個合理的永久區大小,否則将會記憶體溢出
中繼資料區參數通過-XX:MaxMetaspaceSize來指定最大中繼資料區大小,如果不指定,它會不斷擴充,直到耗盡作業系統的記憶體。
直接記憶體
直接記憶體并不是虛拟機中的記憶體區域,而是作業系統所配置設定的記憶體區域。jdk1.4中加入了NIO,引入基于管道(Channel)與緩沖區(Buffer)的I/O方式,它可以使用Native函數庫直接配置設定堆外記憶體,然後通過一個存儲在Java堆裡面的DirectByteBuffer對象作為這塊記憶體的引用進行操作。這樣能夠提高性能,因為避免的Java堆和Native堆中來回複制。
對象通路
在Java語言中,對象通路無處不在。最普通的也是最簡單的通路,也要涉及到Java棧,Java堆,方法區三個最重要的記憶體區域。
Object obj = new Object();
ojb 是一個引用(Reference)存儲在Java棧
new Object() 是一個執行個體,存儲在Java堆
在Java虛拟機規範中并沒有定義這個引用類型(Reference)通過哪種方式定位,以及通路Java堆中對象的具體位置,是以不同的虛拟機實作不同,主流的有兩種:使用句柄和直接指針
如果是句柄通路,Java堆中會劃分出來一塊記憶體作為句柄池,Reference中存儲的是對象的句柄位址,而句柄中包含了對象的執行個體資料和類型資料各自的位址資訊
如果是直接使用指針方式通路,Java堆對象的布局就必須考慮如何放置通路類型資料的相關資訊,Reference中直接存儲的就是對象位址。
使用句柄的方式最大好處就是Reference存儲的是穩定的位址,對象被移動(垃圾收集時新生代,老生代移動是非常普遍的行為)時隻會改變句柄中的執行個體資料指針,而Reference本身不需要更改。
使用直接指針的方式最大的好處就是速度更快,它節省了一次指針定位的時間開銷。
Hotspot使用的是第二種方式.
運作時指定jvm參數
Java虛拟機可以通過 %JAVA_HOME%\bin\java 來啟動
options:表示Java虛拟機參數
args表示Java程式參數
class表示帶有main方法的Java類
小程式示例
public class Args {
public static void main(String[] args) {
for(int i = ; i < args.length ; i++){
System.out.println("第["+(i+)+"]個參數 : " + args[i]);
}
System.out.println("Xmx : " + Runtime.getRuntime().maxMemory()// + "M");
}
}
用eclipse執行:
輸出結果:
第[1]個參數 : 1
第[2]個參數 : 2
第[3]個參數 : 3
Xmx : 466M
記憶體溢出異常示例
在Java虛拟機規範中描述,除了程式計數器之外,虛拟機運作的其他幾個運作時區域都有發生OutOfMemoryError異常的可能,這裡示範一下堆記憶體溢出和棧的棧幀超過限制.
java堆溢出
Java堆用于存儲對象執行個體,是以隻要不斷建立對象執行個體,并且保證GC不回收對象那麼就會記憶體溢出。
代碼片段
import java.util.LinkedList;
import java.util.List;
public class HeapOOM {
public static void main(String[] args) {
List<Object> list = new LinkedList<Object>();
while(true){
list.add(new Object());
}
}
}
啟動參數:-Xms20m -Xmx20m -XX:-UseGCOverheadLimit
- -Xms20m -Xmx20m 設定堆的最大值最小值都是20M,避免自動擴充.
- -XX:-UseGCOverheadLimit : Hotspot VM1.6以後有一個特性,在堆快要溢出的時候提前預警。抛出GC overhead limit exceeded異常。這裡的參數是關閉提前預警的意思。
參數擴充
Sun 官方對此的定義是:“并行/并發回收器在GC回收時間過長時會抛出OutOfMemroyError。過長的定義是,超過%的時間用來做GC并且回收了不到%的堆記憶體。用來避免記憶體過小造成應用不能正常工作。“
那麼這樣捕獲這個異常,如果出現記憶體不足的情況,那麼在這最後的一點記憶體内可以在catch代碼塊寫上釋放記憶體,或者儲存資料,導出堆資訊。
輸出結果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Java虛拟機棧和本地方法棧溢出
在Hotspot虛拟機中并不區分虛拟機棧和本地方法棧.
棧溢出分兩種,1 棧深度超過限制StackOverflowError 2 棧申請的記憶體太大 OutOfMemoryError
StackOverflowError
public class StackOf {
public void stackLeek(){
stackLeek();
}
public static void main(String[] args) {
new StackOf().stackLeek();
}
}
輸出結果: