Java虛擬機在執行java程序的過程中會把它所管理的內存劃分成很多個不同的數據區域。這些區域都有各自的用途,以及創建和銷毀的時間,有的區域隨著虛擬機進程的啟動而存在,有些區域則是依賴用戶線程的啟動和結束而建立和銷毀。Java虛擬機規范中把java虛擬機所管理的內存劃分為以下幾個區域。
一、程序計數器(Program Counter Register)
程序計數器是一塊較小的內存空間,它的作用是當前所執行的字節碼的行號指示器。它是線程私有的,即各個線程都有獨立的程序計數器。
如果線程正在 執行一個java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是native方法,則這個計數器是空(undefined)。
此內存區域是唯一的一個不會拋出OutOfMemoryError異常的區域。
二、java虛擬機棧(Java Virtual Machine Stacks)
我們可能經常聽到說java內存分為堆內存和棧內存,其實這個說法中的棧內存是指java虛擬機棧中的局部變量表部分。
Java虛擬機棧描述的是java方法執行的內存模型:每個方法被執行時都會同時創建一個棧幀(Stack Frame),用於存儲局部變量表、操作棧、動態鏈接、方法出口等信息。每一個方法被調用直到執行完成的過程,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。Java虛擬機棧也是線程私有的,生命周期也線程相同。
局部變量表存放的類型包括以下三種:
1、 編譯期可知的基本數據類型:boolean、byte、char、short、int、float、long、double共8 種類型;
2、 對象引用:即reference類型,它存放的是一個指向堆中對象起始地址的引用指針,或一個代表對象的句柄或者其他與此對象相關的位置,根據虛擬機的不同實現而不同;
3、 returnAddress類型:存放指向一條字節碼指令的地址;
局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的局部變量表空間是完全確定了的,方法在運行期間不會改變局部變量表的大小。
Java虛擬機棧會拋出兩種異常:
-
OutOfMemoryError異常:如果虛擬本可以動態擴展,當擴展時無法申請到足夠的內存時拋出;
-
StackOverflowError異常:如果線程請求的棧深度大於虛擬機所允許的深度時拋出;
三、本地方法棧(Native Method Stacks)
本地方法棧與虛擬機棧的作用相似,它是為虛擬機在執行native方法時服務,而虛擬機棧是為虛擬機執行java方法服務。此內存區域也會拋出OutOfMemoryError異常和StackOverflowError異常。
四、java堆(Java Heap)
java堆是用於存放對象實例和數組。它是java虛擬機管理的內存中最大的一塊,被所有線程共享,在虛擬機啟動時創建,也是垃圾收集器管理的要區域,幾乎所有的對象實例都在這裡分配內存。
如果垃圾收集器采用的是分代收集它還,它還可以細分為新生代和老年代,再細致一點的有Enden空間、From Survivor空間、To Survivor空間等。
java堆可以處理物理上不連續的內存空間中,只要邏輯上連續即可。在實現時既可以實現豐固定大小的,也可以是可擴展的。如果是可擴展的,可以通過 -Xms和-Xmx來指定最小和最大值,如果-Xms和-Xmx的值相等,則相當於不可擴展了。如果堆中沒有內存可完成實例分配,此內存區域會拋出 OutOfMemoryError異常。
五、方法區
方法區用於存放已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。它是堆的一個邏輯部分,是各個線程共享的內存區域。如果是sun 的HotSpot虛擬機,它也叫做永久代(Permanent Generation),如果方法區無法滿足內存分配需求時,會拋出OutOfMemoryError異常。
方法區中的運行時常量池(RuntimeConstant Pool),用於存放編譯期生成的各種字面量和符號引用,它在類加載後存放到運行時常量池中。
運行時常量池具有動態性,即常量不一定只在編譯期產生,在運行期間也可能將新的常量存入池中,比如String類的intern()方法。
六、直接內存(DirectMemory)
它不是虛擬機運行時數據區的一部分,也不是java虛擬機規范中定義的內存區域。比如在JDK1.4中新加入的NIO類,有一種基於通道與緩沖區的I/O 方式,它可以使用native函數庫直接分配堆外內存,然後通過一個存儲在java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作。所以直接內存不會受到java堆大小的限制,但會受到本機總內存的大小及處理器尋址空間的限制。它也會拋出OutOfMemoryError異常。