文章目錄
- 程式計數器
- Java虛擬機器棧
- 本地方法棧
- Java堆
- 方法區
- 運行時常量池
- 直接記憶體
本系列為《深入理解Java虛擬機器 》(周志明著)讀書筆記。
Java程式員一般都知道JVM中存在棧和堆的,並簡單瞭解對象是在堆上分配的,這點從C/C++轉過Java的程式員很容易想到。但Java由於其自身的特性,還有一些其他的記憶體地區,如所示:
程式計數器程式計數器佔用的記憶體空間不大,裡面記錄了各線程當前位元組碼的行號。JVM的多線程是通過輪流執行CPU時間的方式實現,因此在一個線程離開自己的CPU切片時,需要記錄當前的狀態(位元組碼行號)。為了防止線程之間相互影響,每條線程都有自己獨立的程式計數器,這類記憶體地區也被稱為“線程私人”記憶體。例如在中,虛擬機器棧、本地方法棧和程式計數器都屬於“線程私人”記憶體。如果程式執行的是一個Java方法,程式計數器記錄當前線程的位元組碼地址;如果執行的是一個本地方法,則程式計數器為空白(undefined)。Java虛擬機器棧Java程式員常說“棧”和“堆”,那個“棧”指的正是Java虛擬機器棧。從可以看出,和程式計數器一樣,Java虛擬機器棧也是線程私人的,生命週期和線程一樣。虛擬機器棧儲存的是棧幀(Stack Frame):包含局部變數表,運算元棧,動態連結和返回資訊等。局部變數表存放了編譯期可知的各種基礎資料型別 (Elementary Data Type)(boolean, byte, char, short, int, float, long, double)、對象引用和ruturnAddress類型。從這裡可以看出,使用Integer和int在記憶體空間的佔用上是不同的。在32位機器中,Integer要佔用一個reference(8 bytes) + 一個int(4位元組), 而int則只佔4位元組自身的空間。在局部變數表中,8 bytes的long和double會佔據2個局部變數空間(slot),其他類型只佔據一個。本地方法棧本地方法棧和Java虛擬機器棧在結構和用途上類似,所不同的是本地方法棧執行的是Native方法,Java虛擬機器棧中執行的是Java(位元組碼)方法。Java堆Java堆是所有線程共用的一個地區,在虛擬機器啟動時建立。這個地區主要是用於存放對象執行個體,幾乎所有的對象執行個體都是在堆中分配。Java堆是垃圾收集器(GC)管理的主要區域。因為很多記憶體回收行程都採用“分代回收”的策略,因此Java堆還可以細分為:新生代和老生代。JVM規範規定Java堆只需在邏輯上連續,在物理上是否連續則無關緊要。方法區方法區與Java堆一樣,是多個線程共用的地區,用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的代碼等資料。相對而言,這個地區的記憶體很少需要回收,在這個地區的主要回收目標是針對常量池的回收和對類型的卸載。運行時常量池運行時常量池是方法區的一部分。Class檔案中除了類版本、欄位、方法、介面等描述性資訊外,還有一項資訊是常量池,用於存放編譯期產生的各種字面量和符號引用,這部分內容將在類載入後存放到方法區的運行時常量池中。運行時常量池想對於Class檔案常量池的另外一個重要特性是具備動態性,Java語言並不要求常量一定只能在編譯期產生,也就是並非只有預置入Class檔案中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中。直接記憶體直接記憶體並不是虛擬機器運行時資料區的一部分,也不是Java虛擬機器規範中的一部分,但這部分常在NIO中提及。NIO基於通道(Channel)和緩衝區(Buffer),它可以使用Native函數庫直接分配堆外記憶體,然後通過一個儲存在Java堆中的DirectByteBuffer對象作為這塊記憶體的引用進行操作。這樣能在一些情境中顯著提高效能,因為避免了在Java堆和Native堆中來回複製資料。