Java虛擬機器的記憶體區域
最近複習Java知識,記錄一下程式執行時Java記憶體分配的原理,通過對物件的放置、記憶體的分配進一步理解程式的執行機制:
一、從概念上劃分(《Java程式設計思想》上的劃分)
1、暫存器:最快的儲存區,位置處理器內部,但其數量極其有限,所以暫存器應該根據需要進行分配,但程式不能直接控制,所以不會在程式中感覺到暫存器的存在,(C和C++中允許程式設計師向編譯器建議暫存器的分配方式)
2、棧:位於通用RAM(隨機訪問儲存器)中,儲存基本變數(成員變數、區域性變數)和引用(包括:基本資料型別的值、類的例項也稱物件的引用、載入方法時的幀)。通過堆疊指標可以從處理器那裡獲得直接支援(堆疊指標向下移動分配新記憶體,向上移動釋放記憶體),這種分配儲存方式快速有效、僅次於暫存器,但建立程式時Java系統必須知道儲存在堆疊內所有項的確切生命週期,以便上下移動堆疊指標(這一點體現了程式的靈活性)
3、堆:位於通用的記憶體池(也位於RAM區),用於存放所有Java物件(類例項化物件、陣列)(注意創建出來的物件只包含屬於各自物件的成員變數,並不包含成員方法。因為同一個類的物件擁有各自的成員變數,儲存在各自的堆中,但他們共享各自的成員變數,而不是每建立一個物件就把成員方法複製一次)。堆相比於堆疊的好處是編譯器不需要知道儲存在堆裡的資料存活時間,因此堆裡分配儲存有很大的靈活性(只需要new一下就會自動在堆裡進行儲存分配),代價是堆進行儲存分配和清理可能比用堆疊進行儲存分配需要更多的時間(如果地區上課以在Java中你在C++中一樣在棧中建立物件)
4、常量儲存:常量值通常直接存放在程式程式碼內部(因為它永遠不會改變),用於存放常量、字串以及靜態區的東西等等
5、非RAM儲存:資料完全存活於程式之外,即不受程式任何控制,在程式沒有執行時也可以存在。兩個基本例子:流物件(物件轉化成位元組流傳送給另一臺機器)和持久化物件(物件儲存於磁碟上),這種儲存方式的技巧在於可以將物件轉化成可以存放在其他媒介上的事物,需要時可以恢復成常規的、基於RAM的物件
二、從功能上劃分(《深入理解Java虛擬機器》上的劃分)
1、程式計數器:(每個執行緒需要一個獨立的程式計數器)當前執行緒所執行的位元組碼的行號指示器。位元組碼直譯器就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等功能都需要依賴這個計數器來完成
如果執行緒正在執行一個Java方法,則計數器是正在執行的虛擬機器位元組碼指令的地址;如果正在執行一個Native方法,則計數器值為空(Undefined)。此記憶體區域是唯一一個在Java虛擬機器規範中沒有規定任何OutOfMemoryError情況的區域
2、Java虛擬機器棧:(執行緒私有)方法執行時會建立棧楨(用於儲存區域性變量表、運算元棧、動態連結、方法出口等資訊)。一個方法從呼叫到執行完成,對應一個棧楨在虛擬機器棧中入棧到出棧的過程
該記憶體區域存在兩種異常:(1)執行緒請求棧的深度大於虛擬機器所允許的深度---StackOverflowError;(2)虛擬機器可動態擴充套件,但擴充套件時無法申請到足夠的記憶體---OutOfMemoryError
3、本地方法棧:(執行緒私有)對應Java虛擬機器棧(為虛擬機器執行Java方法服務),而本地方法棧(為虛擬機器使用到的Native方法服務)
4、Java堆:(所有執行緒共享一塊記憶體區域)存放物件例項。Java堆可以處於物理上不連續而邏輯上連續的空間。
如果堆中沒有記憶體完成例項分配 ,並且擴充套件堆也無法再擴充套件,將會丟擲OutOfMemoryError異常
5、方法區:(所有執行緒共享一塊記憶體區域)儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料
當方法區無法滿足記憶體分配需求時將丟擲OutOfMemoryError異常
6、執行時常量池:(方法區的一部分)用於存放編譯期生成的各種量和符號引用,這部分內容將在類載入後進入方法區的執行時常量池中存放
當常量池無法再申請到記憶體時會丟擲OutOfMemoryError異常
7、直接記憶體:(不是虛擬機器執行時資料區的一部分,也不Java虛擬機器規範中定義的記憶體區域)在JDK1.4後新增的NIO(New Input/Output)類,引入了基於通道(Channel)與緩衝區(Buffer)的I/O方式,它可以使用Native函式庫直接分配堆外記憶體,然後通過一處儲存在Java堆中的DrectByteBuffer物件偢這塊記憶體的引用進行操作
本機直接記憶體的分配不會受Java堆大小限制,但會受本機總記憶體以處理器建起空間的限制。所以當各個記憶體區域總和大於實體記憶體限制時會導致動態擴充套件時出現OutOfMemoryError異常