在前幾節的文章咱們屢次講到 Class 對象須要分配入 JVM 內存,並在 JVM 內存中執行 Java 代碼,完成對象內存的分配、執行、回收等操做,所以,現在讓咱們來走入 JVM,看看 JVM 中的內存結構是如何構造的,下面就讓咱們一探究竟吧。java
在本小節中,咱們以《Java 虛擬機規範》中的要求,並以當前主流虛擬機 Hotspot VM 爲例,詳細講述內存區域中各個模塊的劃分,瞭解其各自的用途以及其爲什麼如何劃分等。算法
首先讓咱們來看一下 Java 虛擬機內存的劃分方式。jvm
JVM 將內存劃分爲 5個部分,分別爲線程共享的 堆 和 方法區,以及線程私有的 程序計數器,虛擬機棧 和 本地方法棧,下面就讓咱們針對這 5個區域進行學習,探究其存儲數據,生命週期和功能。學習
是一塊較小的內存區域,能夠看作是當前線程執行的字節碼的行號指示器。在虛擬機概念模型裏,字節碼解析器就是經過改變這個計數器的值來選取下一條須要執行的字節碼,所以其在分支,循環,跳轉,異常跳轉,線程恢復等功能上都有着大做用。優化
PS:若是執行的是本地方法,那麼這個計數器的值則爲空。this
虛擬機棧也是線程私有的,其內描述的是 Java 方法執行的內存模型,即在每一個執行同時建立一個棧幀,棧幀內存儲局部變量表,操做數棧,動態連接,方法出口等信息。每個方法從開始到結束就對應着一個棧幀從入棧到出棧的過程。同時只有位於棧頂的棧幀纔是有效的,與其關聯的方法稱爲當前方法,執行引擎的全部字節碼指令都只針對當前棧幀進行操做。spa
用於存放方法參數和方法內部定義的局部變量,其在 Java 程序被編譯爲 Class 文件後,就已經肯定了所需的最大容量。線程
其容量以變量槽(slot)爲最小單位。所以在使用過程當中是經過索引定位來使用局部變量表的,索引範圍爲 0~~slot 最大值。其中若是執行的是非 static 方法,那麼0則默認爲 方法所屬對象實例引用,對應 Java 關鍵字的 this。其他參數按照順序對應 1以後的槽位。設計
操做棧是一個後入先出的棧,其最大深度在編譯時也已經肯定。其對應着方法執行過程當中,各類字節碼指令往操做數棧寫入和提取內容,也就是所謂的 入棧/出棧 操做。調試
也正是操做數棧的存在,所以Java執行引擎也被稱爲 基於棧的執行引擎,與基於 基於寄存器的執行引擎 造成對比。
Java採起「基於棧的執行引擎」考慮到兩點:
- Java是一門跨平臺的語言,而不一樣機器的寄存器實現是不一樣的,有多又少,不利於統一;
- 爲了使 class 文件更加的緊湊,這樣設計可使得大多數指令對齊,而且操做碼只佔一個字節大小,減小數據量。
指向運行時常量池中該棧幀所屬方法的引用,經過這個引用能夠完成動態調用。
關於方法調用過程當中的引用詳細解析過程,在往後的「方法調用」中,再具體描述。
一個方法在執行完成後都須要返回到方法被調用的位置,讓程序繼續執行。
在方法正常執行完成退出後,調用者的程序計數器的值就能夠做爲返回地址存在棧幀中,而在方法異常退出後,返回地址則是經過異常處理器表來肯定了。
附加信息不是虛擬機規範中必需要求有的,但其容許實現者能夠增長一些特殊信息到棧幀中,例如與調試相關的信息,這部分信息取決於具體的虛擬機實現,在這裏再也不贅述。
本地方法棧和虛擬機棧的做用相似,區別僅僅是虛擬機棧爲虛擬機執行的 Java 方法服務,而本地方法棧則是爲 Native 方法服務。其具體實現由虛擬機自行規定。
Java 堆是線程共享的。在通常狀況下,堆能夠說是 Java 內存中最大的內存區域。其存放了對象實例,幾乎全部的對象實例在這裏存儲。(這裏說是幾乎,是由於 JIT優化的存在,可能會有對象不在堆上分配,而在棧上進行分配)。
因爲目前考慮到垃圾回收算法大部分都是分代算法,所以堆又能夠細分爲如下幾塊:
但從其內存本質來看,其並無詳細的區別,都是用來存儲對象實例的,這種劃分方式是從內存回收的角度來闡述的,所以具體存放邏輯放在「內存回收」中再詳細闡述。
方法區也是線程共享的。其中存放的是被虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯後的代碼等數據。在HotSpot JDK7 之前的具體實現中,這部分被稱爲永久代,和堆一塊兒 JVM 管理。但在JDK8以後,這部分已經用 元數據(meta space) 來替代了。此外像字符串常量池也被從這一模塊移除,轉而用堆來實現。
JDK7 以後將之前放在方法區的常量池放在堆中進行實現,例如 String 的 intern()
方法,在 JDK8 以後改成若是存在堆中的引用,則直接返回堆中引用,而並不會從新建立對象。
下面讓咱們來看一下這段代碼在 JDK8 下的結果是什麼。
String s = new String("1"); s.intern(); String s2 = "1"; System.out.println(s == s2); String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4);
該代碼在JDK8下輸出結果爲:
false true
下面就讓咱們用下圖來分析一下是爲何:
String s = new String("1")
這句生成了兩個對象,一個是對象 obj(1),另外一個在 String pool 中,是 "1",s 則是指向對象。s.intern()
由於 "1" 在String pool中已經存在,因此直接返回,String s2 = "1"
,則是直接返回String pool中的引用給s2,最後比較的是兩個指向不一樣地方的引用,所以結果不一樣。
String s3 = new String("1") + new String("1")
生成了兩個對象,一個是對象obj(11),一個是String pool 中的 "1",s3.intern()
判斷當 堆中存在對象的時候,則在字符串常量池中保存該對象的引用,而後返回該對象的引用值,String s4 = "11"
則讓 s4 指向 String pool 中的值,而 該引用的值就是obj(11)的引用,在最後 System.out.println(s3 == s4)
判斷相等的時候,兩個引用其實指向的是同一個值,所以返回相等。
Direct Memory 不屬於 JVM 所管的內存區域,其受到機器總內存的影響。在具體使用中採用一個在 Java 堆中的DirectByteBuffer
對象做爲這塊內存的引用進行操做。
在本文中咱們學習了 JVM 在其內部是如何劃分區域進行功能協做的。瞭解了其內部將 JVM 劃分哪幾個模塊,每一個模塊各自又都有神馬做用,其中存儲了什麼數據,每一個模塊的不一樣特性等。
在下文中,咱們將講述對象在堆中的存儲,使用方式,瞭解的Java的 對象模型。
文章在公衆號「iceWang」第一手更新,有興趣的朋友能夠關注公衆號,第一時間看到筆者分享的各項知識點,謝謝!筆芯!
本系列文章主要借鑑自《深刻分析 JavaWeb 技術內幕》和《深刻理解 Java 虛擬機-JVM 高級特性與最佳實踐》。