JVM入門——JVM內存結構
一、java代碼編譯執行過程
1.源碼編譯:通過Java源碼編譯器將Java代碼編譯成JVM字節碼(.class文件)
2.類加載:通過ClassLoader及其子類來完成JVM的類加載
3.類執行:字節碼被裝入內存,進入JVM虛擬機,被解釋器解釋執行
註:Java平臺由Java虛擬機和Java應用程序接口搭建,Java語言則是進入這個平臺的通道,
用Java語言編寫並編譯的程序可以運行在這個平臺上
二、JVM簡介
1.java程序經過一次編譯之後,將java代碼編譯為字節碼也就是class文件,然後在不同的操作系統上依靠不同的java虛擬機進行解釋,最後再轉換為不同平臺的機器碼,最終得到執行
2.Java虛擬機(JVM) 處在核心的位置,是程序與底層操作系統、硬件無關的關鍵。
JVM的下方是移植接口,移植接口由兩部分組成:適配器和Java操作系統, 其中依賴於平臺的部分稱為適配器,JVM 通過移植接口在具體的平臺和操作系統上實現
JVM 的上方是Java的基本類庫和擴展類庫以及它們的API, 利用Java API編寫的應用程序(application) 和小程序(Java applet) 可以在任何Java平臺上運行而無需考慮底層平臺
Java虛擬機(JVM)實現了程序與操作系統的分離,從而實現了Java 的跨平臺
3.JVM在它的生存周期中有一個明確的任務,那就是運行Java程序,因此當Java程序啟動的時候,就產生JVM的一個實例;當程序運行結束的時候,該實例也跟著消失了
4.三種JVM:① Sun公司的HotSpot ② BEA公司的JRockit ③ IBM公司的J9 JVM
在JDK1.7及其以前我們所使用的都是Sun公司的HotSpot,但由於Sun公司和BEA公司都被oracle收購,jdk1.8將采用Sun公司的HotSpot和BEA公司的JRockit兩個JVM中精華形成jdk1.8的JVM。
三、JVM體系結構
1.Class Loader類加載器 負責加載 .class文件,class文件在文件開頭有特定的文件標示,並且ClassLoader負責class文件的加載等,至於它是否可以運行,則由Execution Engine決定。 ① 定位和導入二進制class文件 ② 驗證導入類的正確性 ③ 為類分配初始化內存 ④ 幫助解析符號引用. 2.Native Interface本地接口 本地接口的作用是融合不同的編程語言為Java所用,它的初衷是融合C/C++程序,Java誕生的時候C/C++橫行的時候,要想立足,必須有調用C/C++程序,於是就在內存中專門開辟了一塊區域處理標記為 native的代碼,它的具體作法是Native Method Stack中登記native方法,在Execution Engine執行時加載native libraies。 目前該方法使用的越來越少了,除非是與硬件有關的應用,比如通過Java程序驅動打印機,或者Java系統管理生產設備,在企業級應用中已經比較少見。 因為現在的異構領域間的通信很發達,比如可以使用Socket通信,也可以使用Web Service等。 3.Execution Engine 執行引擎:執行包在裝載類的方法中的指令,也就是方法。 4.Runtime data area 運行數據區(即:虛擬機內存或者JVM內存 下節介紹) 從整個計算機內存中開辟一塊內存存儲Jvm需要用到的對象,變量等,分為:方法區,堆,虛擬機棧,程序計數器,本地方法棧。
四、JVM內存結構
1.程序計數器 PC Register
每個線程都有一個程序計算器,就是一個指針,指向方法區中的方法字節碼(下一個將要執行的指令代碼),由執行引擎讀取下一條指令,是一個非常小的內存空間,幾乎可以忽略不記。
程序計數器(Program Counter Register)是一塊較小的內存空間,它的作用可以看做是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型裏(僅是概念模型,各種虛擬機可能會通過一些更高效的方式去實現),字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。由於Java 虛擬機的多線程是通過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對於多核處理器來說是一個內核)只會執行一條線程中的指令。因此,為了線程切換後能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,我們稱這類內存區域為“線程私有”的內存。如果線程正在執行的是一個Java 方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是Natvie 方法,這個計數器值則為空(Undefined)。此內存區域是唯一一個在Java 虛擬機規範中沒有規定任何OutOfMemoryError 情況的區域。
2.本地方法棧 Native Method Stack
Native Method Stack中登記native方法,在Execution Engine執行時加載native libraies
本地方法棧與虛擬機棧基本類似,區別在於虛擬機棧為虛擬機執行的java方法服務,而本地方法棧則是為Native方法服務
3.方法區 Method Area
用於存儲虛擬機加載的:靜態變量+常量+類信息+運行時常量池 (類信息:類的版本、字段、方法、接口、構造函數等描述信息 )
默認最小值為16MB,最大值為64MB,可以通過-XX:PermSize
和 -XX:MaxPermSize
參數限制方法區的大小
對於習慣在HotSpot 虛擬機上開發和部署程序的開發者來說,很多人願意把方法區稱為“永久代”(Permanent Generation),本質上兩者並不等價,僅僅是因為HotSpot 虛擬機的設計團隊選擇把GC 分代收集擴展至方法區,或者說使用永久代來實現方法區而已。對於其他虛擬機(如BEA JRockit、IBM J9 等)來說是不存在永久代的概念的。即使是HotSpot 虛擬機本身,根據官方發布的路線圖信息,現在也有放棄永久代並“搬家”至Native Memory 來實現方法區的規劃了。Java 虛擬機規範對這個區域的限制非常寬松,除了和Java 堆一樣不需要連續的內存和可以選擇固定大小或者可擴展外,還可以選擇不實現垃圾收集。相對而言,垃圾收集行為在這個區域是比較少出現的,但並非數據進入了方法區就如永久代的名字一樣“永久”存在了。這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載,一般來說這個區域的回收“成績”比較難以令人滿意,尤其是類型的卸載,條件相當苛刻,但是這部分區域的回收確實是有必要的。在Sun 公司的BUG 列表中,曾出現過的若幹個嚴重的BUG 就是由於低版本的HotSpot 虛擬機對此區域未完全回收而導致內存泄漏。根據Java 虛擬機規範的規定,當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError 異常。
4.棧 JVM Stack
編譯器可知的各種基本數據類型(boolean
、byte
、char
、short
、int
、float
、long
、double
)、對象引用(引用指針,並非對象本身)
棧是java 方法執行的內存模型:
每個方法被執行的時候 都會創建一個“棧幀”用於存儲局部變量表(包括參數)、操作棧、方法出口等信息。
每個方法被調用到執行完的過程,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。
(局部變量表:存放了編譯器可知的各種基本數據類型(boolean
、byte
、char
、short
、int
、float
、long
、double
)、對象引用(引用指針,並非對象本身),
其中64位長度的long和double類型的數據會占用2個局部變量的空間,其余數據類型只占1個。
局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀中分配多大的局部變量是完全確定的,在運行期間棧幀不會改變局部變量表的大小空間。)
棧的生命期是跟隨線程的生命期,線程創建時創建,線程結束棧內存也就釋放,是線程私有的。
5.堆
所有的對象實例以及數組都要在堆上分配,此內存區域的唯一目的就是存放對象實例
Java 堆(Java Heap)是Java 虛擬機所管理的內存中最大的一塊。Java 堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。
堆是理解Java GC機制最重要的區域,沒有之一
結構:新生代(Eden區+2個Survivor區) 老年代 永久代(HotSpot有)
新生代:新創建的對象——>Eden區
GC之後,存活的對象由Eden區 Survivor區0進入Survivor區1
再次GC,存活的對象由Eden區 Survivor區1進入Survivor區0
老年代:對象如果在新生代存活了足夠長的時間而沒有被清理掉(即在幾次Young GC後存活了下來),則會被復制到老年代
如果新創建對象比較大(比如長字符串或大數組),新生代空間不足,則大對象會直接分配到老年代上(大對象可能觸發提前GC,應少用,更應避免使用短命的大對象)
老年代的空間一般比新生代大,能存放更多的對象,在老年代上發生的GC次數也比年輕代少
永久代:可以簡單理解為方法區(本質上兩者並不等價),永久代是一個常駐內存區域,用於存放JDK自身所攜帶的 Class,Interface 的元數據,也就是說它存儲的是運行環境必須的類信息
如上文所說:對於習慣在HotSpot 虛擬機上開發和部署程序的開發者來說,很多人願意把方法區稱為“永久代”(Permanent Generation),本質上兩者並不等價,
僅僅是因為HotSpot 虛擬機的設計團隊選擇把GC 分代收集擴展至方法區,或者說使用永久代來實現方法區而已。
對於其他虛擬機(如BEA JRockit、IBM J9 等)來說是不存在永久代的概念的。
即使是HotSpot 虛擬機本身,根據官方發布的路線圖信息,現在也有放棄永久代並“搬家”至Native Memory 來實現方法區的規劃了。
Jdk1.6及之前:常量池分配在永久代
Jdk1.7:有,但已經逐步“去永久代”
Jdk1.8及之後:沒有永久代(java.lang.OutOfMemoryError: PermGen space,這種錯誤將不會出現在JDK1.8中)。
參考資料
1.JVM工作原理 http://raising.iteye.com/blog/2377709
2.JVM運行原理詳解 https://blog.csdn.net/luomingkui1109/article/details/72820232
3.Java 內存區域和GC機制 https://www.cnblogs.com/zhguang/p/3257367.html
4.深入理解JVM基本原理 https://www.cnblogs.com/leefreeman/p/7344460.html
JVM入門——JVM內存結構