學習參考資料:周志明老師的著作《深入了解Java虛拟機(第3版)》
1.運作時資料區域
根據《Java虛拟機規範》的規定,Java虛拟機所管理的記憶體将會分為以下幾個運作時資料區域,如下圖所示。
其中每個線程有自己獨自的虛拟機棧、本地方法棧、程式計數器
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5CNkhTY4M2N2QzYwUjZhJDO2MWNxMjNyQGZkFjY0UTM08CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
1.1程式計數器
程式計數器(Program Counter Register) 是一塊較小的記憶體空間,通過改變這個計數器的值來選取下一條需要執行的位元組碼指令。
剛才我們說程式計數器是私有的,這是因為Java虛拟機在執行多線程時,采用的是線程輪流切換,處理器配置設定執行時間,這樣就要求每次切換後需要及時指向該線程所運作的位置且各自的計數器互不影響。
如果線程正在執行的是一個Java方法,那麼計數器記錄的是正在執行的虛拟機位元組碼指令的位址;
如果線程正在執行的是一個本地(Native)方法,那麼計數器的值為空。
1.2Java虛拟機棧
每個方法被執行時,都會在Java虛拟機棧中同步建立一個棧幀(用于存儲局部變量表、操作數棧、動态連接配接、方法出口等資訊),也就是說每個方法在執行至執行完畢後,都對應着一個棧幀從入棧到出棧的過程!
那麼這個棧幀有什麼作用呢?
接下來我們從棧幀的存儲局部變量表開始說起:
局部變量表存放了在編譯期可知的基本資料類型和對象引用;
局部變量表中的存儲空間以局部變量槽來表示,64位的long和double類型會占用兩個槽;存儲空間在編譯時就可以完成配置設定,即在運作時棧幀中的局部變量表的大小就已經确定好了,并且不會改變局部變量表的大小。
如果線程請求的深度大于虛拟機所允許的深度,将抛出
StackOverflowError
異常;
如果Java虛拟機棧容量可以動态擴充,當棧擴充時無法申請到足夠的記憶體會抛出
OutOfMemoryError
異常。
1.3本地方法棧
本地方法棧和虛拟機棧發揮的作用是類似的,隻不過虛拟機棧為虛拟機提供Java(也就是位元組碼)服務,本地方法棧是為虛拟機提供本地(Native)方法服務。
1.4Java堆
Java堆是記憶體虛拟機所管理記憶體中最大的一塊,幾乎所有的對象執行個體都在這裡配置設定記憶體。被所有線程共享的記憶體區域。
Java堆既可以被實作成固定大小的,也可以是可擴充的,不過目前主流的Java虛拟機都是按照可擴充來實作的(通過參數
-Xmx
和
-Xms
設定)。如果在Java堆中沒有記憶體完成執行個體配置設定,并且堆也無法再擴充時,Java虛拟機将會抛出
OutOfMemoryError
異常。
1.5方法區
方法區也是所有線程共享的記憶體區域,用來存儲已被虛拟機加載的類資訊、常量、靜态變量。
方法區隻是一個抽象的概念,具體的實作是通過以下兩種方式:
- 永久代:JDK1.7及以前
- 元空間:JDK1.8及以後
JDK1.7以前使用永久代來實作方法區,這樣就省去了專門為方法去編寫記憶體管理的工作。
但是随着時間的推移這種設計導緻了Java應用更容易遇到記憶體溢出的問題(因為永久代有
-XX:MaxPermSize
的上限即記憶體上限,不設定也會有!)
JDK1.7的時候為了臨時解決上面的情況,将永久代的字元串常量池、靜态變量等移到堆中。
JDK1.8把JDK1.7中永久代還剩餘的内容(主要是類型資訊)全部移到元空間!而元空間是占用的本地記憶體。
還有要注意的是,并非進入了方法區就跟永久代的名字一樣不會進行垃圾回收了。相對而言,會很少出現,主要是針對常量池的回收和對類型的解除安裝。
後面還會陸陸續續更新這系列的讀書筆記,期待您的關注~~