JVM 可以分為 5 個部分,分別是:
類加載器(Class Loader):加載字節碼文件到內存。
運行時數據區(Runtime Data Area):JVM 核心內存空間結構模型。
執行引擎(Execution Engine):對 JVM 指令進行解析,翻譯成機器碼,解析完成后提交到操作系統中。
本地庫接口(Native Interface):供 Java 調用的融合了不同開發語言的原生庫。
本地方法庫(Native Libraies):Java 本地方法的具體實現。
JVM 的結構如下圖所示:

這其中最復雜的是運行時數據區,它也是 JVM 內存結構最重要的部分。運行時數據區又可以分為方法區、虛擬機棧、本地方法棧、堆以及程序計數器,并且方法區和堆是線程共享的,虛擬機棧、本地方法棧、程序計數器是線程隔離的。下面詳細講解運行時數據區的各個組成部分。
1.線程共享
方法區:方法區與 Java 堆一樣,是各個線程共享的內存區域,它用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。雖然 Java 虛擬機規范把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做 Non-Heap(非堆),目的應該是與 Java 堆區分開來。
堆:該區域是所有線程共享的,存放幾乎所有的對象實例。jvm管理的內存中最大的一塊,也是GC收集器主要管理的區域。按回收內存的角度可以分為新生代,老年代和永久代;再細分的話有Eden空間,From survior,To survior空間;不管如何劃分都是為了更好的回收內存,存儲的也是對象實例。
新生代:新生代又可分為 Eden,from Survivor,to Survivor。Eden 區用來存放剛剛創建的對象,如果 Eden 區放不下,則放在 Survivor 區,甚至老年代中。Survivor 區又可分為 Survivor From 和 Survivor To,GC 回收時使用,將 Eden 中存活的對象存入 Survior From 中,下一次回收時,將 Survior From 中的對象存入 Survior To 中,清除 Survior From ,下一次回收時重復此步驟,Survior From 變成 Survior To,Survivor To 變成 Survivor From,依次循環,同時每次回收,對象的年齡都 +1,年齡增加到一定程度的對象,移動到老年代中。
老年代:存放年齡大的對象
永久代:jdk7之前就是用永久代來實現方法區,本質上來講兩者并不等價,僅因為Hotspot將GC分代擴展至方法區,所以將其稱為永久代。在物理內存上是和堆是連續的但邏輯上是隔離的;jdk8后移除了永久代,用元空間代替。使用的是本地內存,這樣減少了內存溢出的問題。

2.線程私有
本地方法棧:棧中方法都是用native修飾的,這些方法在Java中只有定義沒有具體實現。
- 由javah命令從.class文件轉換成.h文件(頭文件,里面包含函數原型和宏定義)
- 用.cpp文件實現 .h 文件中的方法
- 將 .cpp 文件編譯成動態鏈接庫文件 .dll
- 使用 System.loadLibrary() 加載動態連接庫文件
調用native方法的基本原理是利用反射機制,在運行時找到 .dll 文件并且解析,根據動態鏈接庫中的文件名稱創建出對象和方法,然后就可以利用對象調用方法了。
虛擬機棧:實際上,Java 虛擬機棧是由一個個棧幀組成,而每個棧幀中都擁有:局部變量表、操作數棧、動態鏈接、方法出口信息。
局部變量表主要存放了編譯器可知的各種數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference 類型,它不同于對象本身,可能是一個指向對象起始地址的引用指針,也可能是指向一個代表對象的句柄或其他與此對象相關的位置)。
Java 虛擬機棧會出現兩種錯誤:StackOverFlowError 和 OutOfMemoryError。
- StackOverFlowError: 若 Java 虛擬機棧的內存大小不允許動態擴展,那么當線程請求棧的深度超過當前 Java 虛擬機棧的最大深度的時候,就拋出 StackOverFlowError 錯誤。
- OutOfMemoryError: 若 Java 虛擬機棧的內存大小允許動態擴展,且當線程請求棧時內存用完了,無法再動態擴展了,此時拋出 OutOfMemoryError 錯誤。
程序計數器:它是一塊較小的空間,可以看做當前線程執行字節碼的行號指示器。字節碼解釋器通過改變計數器的值來選取下一條需要執行的字節碼指令。 另外,為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各線程之間計數器互不影響,獨立存儲,我們稱這類內存區域為“線程私有”的內存。它也是jvm內存里唯一一個不會出現OOM的區域。