Java虛擬機在執行Java程序的過程中,會把它所管理的內存劃分為若干個不同的數據區域,我們稱之為運行時數據區域。
這些區域都有各自的用途,以及創建和銷毀的時間。有的區域隨著虛擬機進程的啟動而存在,可以被所有線程共享;有的區域則是依賴線程的啟動和結束,只能由該線程使用。一般情況下Java虛擬機運行時數據區域如下圖所示:
如上圖所示,Java虛擬機將其管理的內存主要分為2大塊,一塊為線程共享,一塊為非線程共享即線程私有。Java虛擬機在初始運行的時候都會分配好Method Area和Heap,而每遇到一個線程,都會為該線程分配一個Program Counter Register、VM Stack、Native Method Stack,當該線程終止時,這三者所占用的內存也會被釋放掉。非線程共享的那3個內存區域與線程的生命周期相同,而線程共享的內存區域與Java應用程序的生命周期相同,所以系統垃圾回收主要發生在線程共享的內存區域上(主要發生在Heap上)。
1.Java堆(Heap)
Java堆是被所有線程共享的一塊內存區域,在Java虛擬機啟動時創建。幾乎所有的對象實例以及數組都要在堆上分配內存,Java堆是垃圾收集器管理的主要區域,Java堆可以處于物理上不連續的內存空間中,只要邏輯上是連續的即可。如果在堆中沒有內存完成實例分配,并且堆也無法擴展時,將會拋出OutOfMemoryError的異常。
2.方法區(Method Area)
方法區同樣也是線程共享的內存區域,它用于存儲已被虛擬機加載的類信息、常量、靜態常量、JIT(即時編譯器)編譯后的代碼等數據。垃圾回收在這個區域比較少出現,這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載。同樣,當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常。
3.程序計數器(Program Counter Register)
它的作用可以看作是當前線程所執行的字節碼的行號指示器,字節碼解釋器工作時,就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令。
此內存區域是唯一一個不會發生OutOfMemoryError情況的區域。
4.Java虛擬機棧(VM Stack)
與程序計數器一樣,它也是線程私有的,與線程的生命周期一樣。對于棧來說不存在垃圾回收的問題,只要線程一結束,該棧所占內存就被釋放回收了。
Java虛擬機棧描述的是Java方法執行的內存模型:每個方法被執行的時候都會創建一個棧幀(Stack Frame),用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法被調用直至執行完成的過程,就對應著一個棧幀入棧、出棧的過程。
當一個方法A被調用時,就會創建一個棧幀F1并被壓入到棧中,A方法又調用了B方法,于是又會創建一個棧幀F2壓入到棧中,方法B執行完畢后,棧幀F2出棧,然后方法A執行完畢,則棧幀F1出棧。
這個區域有2種異常情況:
- 如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常;
- 1如果虛擬機棧可以動態擴展,當擴展時無法申請到足夠的內存時會拋出OutOfMemoryError異常。
5.本地方法棧(Native Method Stack)
虛擬機棧是為虛擬機執行Java方法服務,本地方法棧則是為虛擬機使用到的Native方法服務。
6.非虛擬機管理的內存區域
除了前面提到的5個虛擬機運行時數據區,還有一個我們稱之為直接內存(Direct Memory)的內存區域,它并不是虛擬機運行時數據區的一部分,也不是Java虛擬機規范中定義的內存區域,但是這部分內存也被頻繁地使用。
在Java的NIO中,引入了一種基于通道(Channel)與緩沖區(Buffer)的I/O方式,它可以使用Native函數直接分配堆外內存,然后通過一個存儲在Java堆里的DirectByteBuffer對象作為這塊內存的引用進行操作。