【JVM體系結構】

一、基礎知識

1. JVM實例:JVM實例對應了一個獨立運行的java程序,它是進程級別。

2. JVM執(zhí)行引擎實例:JVM執(zhí)行引擎實例則對應了屬于用戶運行程序的線程,它是線程級別的。

3. JVM生命周期:

? ? JVM實例的誕生:當啟動一個Java程序時,一個JVM實例就產(chǎn)生了。

? ? JVM實例的運行: main()作為該程序初始線程的起點,任何其他線程均由該線程啟動。

? ? JVM實例的消亡:當程序中的所有非守護線程都終止時,JVM才退出;若安全管理器允許,程序也可以使用Runtime類或者System.exit()來退出。

? ? 注: JVM內(nèi)部有兩種線程:守護線程和非守護線程,main()屬于非守護線程,守護線程通常由JVM自己使用,java程序也可以標明自己創(chuàng)建的線程是守護線程。

? ? 任何一個擁有public static void main(String[] args)函數(shù)的class都可以作為JVM實例運行的起點

二、JVM結構

JVM可以由不同的廠商來實現(xiàn)。由于廠商的不同必然導致JVM在實現(xiàn)上的一些不同,然而JVM還是可以實現(xiàn)跨平臺的特性,這就要歸功于設計JVM時的體系結構了。

JVM體系結構包含三部分:

? ? 類加載器(Class Loader)子系統(tǒng):類加載器(class loader)用來加載 Java 類到 Java 虛擬機中。一般來說,Java 虛擬機使用 Java 類的方式如下:Java 源程序(.java 文件)在經(jīng)過 Java 編譯器編譯之后就被轉(zhuǎn)換成 Java 字節(jié)代碼(.class 文件)。類加載器負責讀取 Java 字節(jié)代碼,并轉(zhuǎn)換成java.lang.Class類的一個實例。每個這樣的實例用來表示一個 Java 類。通過此實例的 newInstance()方法就可以創(chuàng)建出該類的一個對象。實際的情況可能更加復雜,比如 Java 字節(jié)代碼可能是通過工具動態(tài)生成的,也可能是通過網(wǎng)絡下載的。但第二次實例化一個類時,就從對應Class類newInstance(),不用每次都讀取.class文件。

? ? 執(zhí)行引擎(Execution Engine)

? ? 運行時數(shù)據(jù)區(qū)(Runtime Data Area):


Java程序執(zhí)行過程

Java整個程序以及運行的過程相當繁瑣,本文通過一個簡單的程序來簡單的說明整個流程。? ? ?

? ? ? ? ? 如下圖,Java程序從源文件創(chuàng)建到程序運行要經(jīng)過兩大步驟:1、源文件由編譯器編譯成字節(jié)碼(ByteCode)? 2、字節(jié)碼由java虛擬機解釋運行。因為java程序既要編譯同時也要經(jīng)過JVM的解釋運行,所以說Java被稱為半解釋語言( "semi-interpreted" language)。


三、運行時數(shù)據(jù)區(qū)

程序計數(shù)器(Program Counter Register)、Java棧(VM Stack)、本地方法棧(Native Method Stack)、方法區(qū)(Method Area)、堆(Heap)

image.png


? ? 程序計數(shù)器(Program Counter Register)

? ? 程序計數(shù)器是一塊較小的內(nèi)存空間, 可以看作是當前線程所執(zhí)行的字節(jié)碼的行號指示器。 分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復等基礎功能都需要依賴這個計數(shù)器來完成。

? ? 由于Java 虛擬機的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實現(xiàn)的,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內(nèi)核)只會執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復到正確的執(zhí)行位置,每條線程都需要有一個獨立的程序計數(shù)器,各條線程之間的計數(shù)器互不影響,獨立存儲,我們稱這類內(nèi)存區(qū)域為 “線程私有” 的內(nèi)存。

? ? 在JVM規(guī)范中規(guī)定,如果線程執(zhí)行的是非native方法,則程序計數(shù)器中保存的是當前需要執(zhí)行的指令的地址;如果線程執(zhí)行的是native方法,則程序計數(shù)器中的值是undefined。

? ? 由于程序計數(shù)器中存儲的數(shù)據(jù)所占空間的大小不會隨程序的執(zhí)行而發(fā)生改變,因此,對于程序計數(shù)器是不會發(fā)生內(nèi)存溢出現(xiàn)象(OutOfMemory)的。

? ? Java棧(VM Stack)

? ? Java棧也稱作虛擬機棧(Java Vitual Machine Stack),是Java方法執(zhí)行的內(nèi)存模型。

? ? ? ? Java棧中存放的是一個個的棧幀, 每個棧幀對應一個被調(diào)用的方法,在棧幀中包括局部變量表(Local Variables)、操作數(shù)棧(Operand Stack)、指向當前方法所屬的類的運行時常量池(運行時常量池的概念在方法區(qū)部分會談到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些額外的附加信息。


? ? ? ? image.png

? ? ? ? 當線程執(zhí)行一個方法時,就會隨之創(chuàng)建一個對應的棧幀,并將建立的棧幀壓棧。當方法執(zhí)行完畢之后,便會將棧幀出棧。

? ? ? ? 會有兩種異常StackOverFlowError和 OutOfMemoneyError。當線程請求棧深度大于虛擬機所允許的深度就會拋出StackOverFlowError錯誤;虛擬機棧動態(tài)擴展,當擴展無法申請到足夠的內(nèi)存空間時候,拋出OutOfMemoneyError。

? ? ? ? 它是線程私有的,生命周期與線程相同。

? ? 本地方法棧(Native Method Stack)

? ? 本地方法棧與Java棧的作用和原理非常相似。區(qū)別只不過是Java棧是為執(zhí)行Java方法服務的, 而本地方法棧則是為執(zhí)行本地方法(Native Method)服務的。

? ? ? ? 在JVM規(guī)范中,并沒有對本地方發(fā)展的具體實現(xiàn)方法以及數(shù)據(jù)結構作強制規(guī)定,虛擬機可以自由實現(xiàn)它。在HotSopt虛擬機中直接就把本地方法棧和Java棧合二為一。

? ? ? ? 與虛擬機棧一樣,本地方法棧區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常

? ? 方法區(qū)(Method Area)

? ? JDK8之后-JVM運行時數(shù)據(jù)區(qū)域

? ? ? ? JDK7及之前版本的方法區(qū)(Method Area)和Java堆一樣, 是各個線程共享的內(nèi)存區(qū)域,用于存儲已經(jīng)被虛擬機加載的類信息、常量、靜態(tài)常量、即時編譯器編譯后的代碼等數(shù)據(jù)。

? ? ? ? 在方法區(qū)中有一個非常重要的部分就是 運行時常量池 ,它是每一個類或接口的常量池的運行時表示形式, 在類和接口被加載到JVM后,對應的運行時常量池就被創(chuàng)建出來。當然并非Class文件常量池中的內(nèi)容才能進入運行時常量池,在運行期間也可將新的常量放入運行時常量池中,比如String的intern方法。

package com.jvm;

import java.util.ArrayList;

import java.util.List;

/**

* -XX:MaxPermSize=20M 方法區(qū)最大大小20M

* Created by yulinfeng on 7/11/17.

*/

public class Test {

? ? public static void main(String[] args) {

? ? ? ? List<String> list = new ArrayList<String>();

? ? ? ? int i = 0;

? ? ? ? while (true) {

? ? ? ? ? ? list.add(String.valueOf(i++).intern());? //不斷創(chuàng)建線程

? ? ? ? }

? ? }

}

實際上對于以上代碼,在JDK6、JDK7、JDK8運行結果均不一樣。原因就在于字符串常量池在JDK6的時候還是存放在方法區(qū)(永久代)所以它會拋出OutOfMemoryError:Permanent Space;而JDK7后則將字符串常量池移到了Java堆中,上面的代碼不會拋出OOM,若將堆內(nèi)存改為20M則會拋出OutOfMemoryError:Java heap space;至于JDK8則是純粹取消了方法區(qū)這個概念,取而代之的是”元空間(Metaspace)“,所以在JDK8中虛擬機參數(shù)”-XX:MaxPermSize”也就沒有了任何意義,取代它的是”-XX:MetaspaceSize“和”-XX:MaxMetaspaceSize”等。

? ? 在JVM規(guī)范中,沒有強制要求方法區(qū)必須實現(xiàn)垃圾回收。很多人習慣將方法區(qū)稱為“永久代”,是因為HotSpot虛擬機以永久代來實現(xiàn)方法區(qū),從而JVM的垃圾收集器可以像管理堆區(qū)一樣管理這部分區(qū)域,從而不需要專門為這部分設計垃圾回收機制。不過自從JDK7之后,Hotspot虛擬機便將運行時常量池從永久代移除了。

? ? 堆(Heap)

? ? ? ? JVM中所管理內(nèi)存中的最大的一塊。在虛擬機啟動時被創(chuàng)建。

? ? ? ? 唯一的目的是存放對象實例,幾乎所有的對象實例和數(shù)組都是在這里分配內(nèi)存。 (JVM規(guī)范中說的是所有的,但是隨著JIT便編譯器的發(fā)展和逃逸技術分析的成熟,一些實例可以不在這個區(qū)域分配內(nèi)存)

? ? ? ? 程序員基本不用去關心空間釋放的問題,Java的垃圾回收機制會自動進行處理,因此堆是垃圾收集管理的主要區(qū)域,所以也會被稱為”GC堆“。

? ? ? ? 堆是被所有線程共享的,在JVM中只有一個堆。

? ? ? ? 淺堆和深堆

? ? ? ? ? ? 淺堆(Shallow Heap)和深堆(Retained Heap)是兩個非常重要的概念,它們分別表示一個對象結構所占用的內(nèi)存大小和一個對象被GC回收后,可以真實釋放的內(nèi)存大小。

? ? ? ? ? ? 淺堆(Shallow Heap)是指一個對象所消耗的內(nèi)存。在32位系統(tǒng)中,一個對象引用會占據(jù)4個字節(jié),一個int類型會占據(jù)4個字節(jié),long型變量會占據(jù)8個字節(jié),每個對象需要占用8個字節(jié)。

? ? ? ? ? ? 深堆(Retained Heap)的概念略微復雜。要理解深堆,首先需要了解保留集(Retained Set)。對象A的保留集指當對象A被垃圾回收后,可以被釋放的所有對象集合(包括對象A本身),即對象A的保留集可以被認為是只能通過對象A被直接或間接訪問到的所有對象的集合。通俗地說,就是指僅被對象A所持有的對象的集合。深堆是指對象的保留集中所有的對象的淺堆大小之和。

? ? ? ? ? ? 例如:對象A引用了C和D,對象B引用了C和E。那么對象A的淺堆大小只是A本身,不含C和D,而A的實際大小為A、C、D三者之和。而A的深堆大小為A與D之和,由于對象C還可以通過對象B訪問到,因此不在對象A的深堆范圍內(nèi)。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 內(nèi)存溢出和內(nèi)存泄漏的區(qū)別 內(nèi)存溢出:out of memory,是指程序在申請內(nèi)存時,沒有足夠的內(nèi)存空間供其使用,...
    Aimerwhy閱讀 799評論 0 1
  • 一、基礎知識 1. JVM實例:JVM實例對應了一個獨立運行的java程序,它是進程級別。2. JVM執(zhí)行引擎實例...
    向前向后遇見改變閱讀 310評論 0 1
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡上收集的一些資料的整理,因此不免有一些不準確的地方,同時不同JDK版本的...
    高廣超閱讀 16,040評論 3 83
  • 表達意識,表達生命中最滲透靈魂的事物。從手指里冒出來,從語言里冒出來,從思維里冒出來,從每一次舞蹈中冒出來。 眼前...
    路郁閱讀 379評論 0 0
  • Tim M, Lee S, Mullin T J, et al. 2013. Best Practice for ...
    董八七閱讀 147評論 0 0

友情鏈接更多精彩內(nèi)容