JVM是Java Virtual Machine(Java虛擬機)的縮寫。html
主要分爲五大模塊:類裝載器子系統、運行時數據區、執行引擎、本地方法接口和垃圾收集模塊。java
線程私有,與線程生命週期相同。不連續的內存空間。算法
它描述的是Java方法執行的內存模型:方法建立時會建立一個棧幀,用於存儲局部變量表,操做棧,動態連接,方法出口等信息。每個方法被調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。編程
局部變量表:是一組變量存儲空間,用於存放方法參數和方法內部的局部變量,即八大基礎數據類型和引用類型(指向對象地址的引用指針)。數組
操做棧:其結構同局部變量表,也是一個以字節長度爲單位的數組。不一樣的是,局部變量表經過索引進行訪問,而操做棧則是經過壓棧--出棧完成的。其中存儲數據的方式也和局部變量表同樣,如int、long、float、double、reference和returnType的存儲。對於byte、short以及char類型的值在壓入到操做數棧以前,也會被轉換爲int。虛擬機把操做數棧做爲它的工做區——大多數指令都要從這裏彈出數據,執行運算,而後把結果壓回操做數棧。安全
動態連接:虛擬機運行時,運行時常量池會保存大量的符號引用,此期間將符號引用轉換成直接引用,以訪問到真正的方法或對象,稱爲動態連接。數據結構
做用與虛擬機棧相似,區別在於:虛擬機棧爲虛擬機執行的.class文件服務,本地方法棧則爲虛擬機使用到的Native方法(一個java調用非Java代碼的接口)服務。和虛擬機棧同樣,也會拋出棧溢出(StackOverflowError)和堆溢出(OutOfMemoryError)。多線程
全部線程共有,在虛擬機啓動時建立。分爲兩個部分:年輕代和老年代。主要用於存放對象實例,幾乎全部的對象實例都在這裏分配內存,是垃圾收集器管理的主要區域,有時也被稱爲「GC」堆(Garbage Collected Heap)。jvm
年輕代:沒有經歷過屢次GC回收。分爲一個Eden區和兩個Survivor區,對象優先在Eden區分配。年輕代也稱新生代,這裏進行的垃圾回收也稱新生代GC(Minor GC),主要採用的是複製算法,由於這裏的GC進行比較頻繁,在通過屢次GC後仍然在Survivor區存在對象,那麼就將對象存入到老年代中。
老年代:經歷了必定次數的GC回收的年輕代。發生在老年代的GC稱爲Full GC,又稱爲Major GC,其常常會伴隨至少一次的Minor GC(並不是絕對,在Parallel Scavenge收集器中就有直接進行Full GC的策略選擇過程)。Major GC的速度通常會比Minor GC慢10倍以上。這裏使用的GC算法是標記整理算法(老年代回收算法)。ide
永久代:用於存儲類的信息、編譯後的代碼數據等。JDK8以前,方法區由永久代來實現,即GC分代收集至方法區,主要針對常量池的回收和類型的卸載。這樣HotSpot的垃圾收集器能夠像管理Java堆同樣管理這部份內存,能省去專門爲方法區編寫內存管理代碼的工做。
jdk8後,永久代被元數據區(元空間 Metaspace)替代,jdk8以前永久代(Perm)全部內容的字符串常量移至堆內存,其餘內容移至元空間,相比JDK8以前的永久代,元空間直接在本地內存分配,並不在虛擬機中。
元空間在邏輯上屬於堆區,但爲了與堆進行區分,也叫作非堆。
詳細參考:https://blog.csdn.net/weixin_40739833/article/details/80717638
主要用於存儲已被虛擬機加載的類信息(類的描述信息,全限定名、直接超類的全限定名、類的類型仍是接口類型、訪問修飾符、直接超接口的全限定名的有序列表)、靜態變量、即時編譯器編譯後的代碼、常量池(運行時、靜態常量池)等。
也是各個線程共享的區域。該區域不多發生垃圾回收,在這裏進行的GC主要是對方法區裏的常量池和類型的卸載。
靜態常量池:.class文件中的常量池,主要包含字面量和符號引用量。字面量至關於Java語言層面常量的概念,如文本字符串,聲明爲final的常量值等。符號引用量分爲三種:類和接口的全限定名、字段名稱和描述符、方法名稱和描述符。當所引用的類被加載後,它的符號引用將變成直接引用。
運行時常量池:類裝載完成後,將class文件中的常量池載入到內存中,並保存到方法區中。class文件中的常量池中的內容會在類加載後進入方法區的運行時常量池。相對於常量池,運行時常量池的重要特徵是具備動態性,java並不要求常量只有在編譯器纔會產生,運行期間也能夠將新的常量存放入池中,這種特性用的最多的String類中的intern()方法。
是記錄當前線程所執行字節碼的行號指示器。
內存空間較小,線程私有。經過改變這個計數器的值來選取下一條須要執行指令的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能。
在多線程環境下,一個程序的運行是經過線程的輪流切換並分配處理器執行時間的方式進行的,爲了使線程切換時能恢復到正確的位置,線程須要一個標記,即程序計數器。各線程之間的計數器互不影響,即「線程私有」。
若是線程正在執行的是一個 Java 方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;若是正在執行的是 Native 方法,這個計數器值則爲空(Undefined)。此內存區域是惟一一個在 Java 虛擬機規範中沒有規定任何 OutOfMemoryError 狀況的區域。
一個Java文件從編碼完成到最終執行,通常主要包括兩個過程:編譯和運行。
編譯:即把咱們寫好的Java文件,經過Javac命令編譯成字節碼文件,也就是.class文件。
運行:即把編譯好的.class文件交給Java虛擬機(jvm)執行。
類加載過程發生在運行階段。是Java虛擬機(jvm)把.class文件中類信息加載進內存,並進行解析生成對應的class對象的過程。
這個過程呢又分爲三個部分,分別是:加載、鏈接、初始化,其中鏈接又分爲三個小的部分:驗證、準備、解析。
①、加載:簡單來講,加載指的是把class字節碼文件從各個來源經過類加載器裝載入內存中。
Ⅰ、經過類的全限定名獲取定義此類的二進制字節流。
Ⅱ、將這個字節流表明的靜態存儲結構轉爲方法區的運行時數據結構.
Ⅲ、並在堆內存中生成一個表明這個類的java.lang.class對象,做爲訪問方法去這些數據結構的入口。
②、鏈接:
其中包含:
Ⅰ、驗證:主要是判斷class文件的合法性,對版本號進行驗證例如若是使用java1.8編譯後的class文件要再java1.6虛擬機上運行),還會對元數據,字節編碼等進行驗證,確保class文件裏的字節流信息符合當前虛擬機的要求,不會危害虛擬機的安全。
Ⅱ、準備:主要是分配內存,爲變量分配初始值,即在方法區中爲這些變量分配內存空間。
須要注意的是,這時候進行內存分配的僅僅是類變量(也就是被static修飾的變量),實例變量是不包括的,實例變量的初始化是在對象實例化的時候進行初始化,並且分配的內存區域是Java堆。這裏的初始值也就是在編程中默認值,也就是零值。
其中static修飾的變量,在準備階段會被初始化爲0,類的初始化階段纔會被賦值。
例:public static int i = 1;在準備階段i的值會被初始化爲0,後面的類的初始化階段纔會賦值爲1;
而被static final修飾的變量,在準備階段就會被賦值。
例:public static final int i = 1;對應常量(static final)i,在準備階段就會被賦值1;
Ⅲ、解析:
解析就是把類中的符號引用替換爲直接引用;例如某個類繼承了java.lang.Object,原來的符號引用記錄的是「java.lang.Object」,並非java.lang,Object對象,直接引用就是找出對應的java.lang.Object對應的內存地址,創建直接引用關係;
③、初始化:
初始化過程當中,會執行:類的構造函數,static修飾的變量賦值語句,static{}代碼塊,如有繼承關係存在,初始化子類以前,會先初始化父類,就像是兒子在端碗吃飯以前,父親先演示一遍怎麼端碗吃飯。Java中,Java.lang.Object是全部類的超類,因此類初始化以前,Java.lang.Object一定被初始化。
如下狀況不會執行類的初始化:
Ⅰ、經過子類引用父類的靜態字段,只會觸發父類的初始化,而不會觸發子類的初始化。
Ⅱ、定義對象數組,不會觸發該類的初始化。
Ⅲ、常量在編譯期間會存入調用類的常量池中,本質上並無直接引用定義常量的類,不會觸發定義常量所在的類。
Ⅳ、經過類名獲取Class對象,不會觸發類的初始化。
Ⅴ、經過Class.forName加載指定類時,若是指定參數initialize爲false時,也不會觸發類初始化,其實這個參數是告訴虛擬機,是否要對類進行初始化。
Ⅵ、經過ClassLoader默認的loadClass方法,也不會觸發初始化動做。
ps:類加載器:詳見http://www.javashuo.com/article/p-flqqiuqi-dd.html
BootStrap Classloader(啓動Classloader):主要加載JVM自身須要的類。這個加載器由C++編寫是虛擬機的一部分,負責加載 JAVA_HOME\lib 目錄中的,或經過-Xbootclasspath參數指定路徑中的,且被虛擬機承認(按文件名識別,如rt.jar)的類。
Extension Classloader(擴展Classloader):是sun.misc.Launcher中的內部類ExtClassLoader,負責加載 JAVA_HOME\lib\ext 目錄中的,或經過java.ext.dirs系統變量指定路徑中的類庫。
ClassLoader(應用Classloader):是sun.misc.Launcher中的內部類AppClassLoader,負責加載用戶路徑上的類庫。用戶也能夠經過繼承ClassLoader實現本身的類加載器。
雙親委派模式:當一個Hello.class這樣的文件要被加載時。不考慮咱們自定義類加載器,首先會在AppClassLoader中檢查是否加載過,若是有那就無需再加載了。若是沒有,那麼會拿到父加載器,而後調用父加載器的loadClass方法。父類中同理會先檢查本身是否已經加載過,若是沒有再往上。注意這個過程,知道到達Bootstrap classLoader以前,都是沒有哪一個加載器本身選擇加載的。若是父加載器沒法加載,會下沉到子加載器去加載,一直到最底層,若是沒有任何加載器能加載,就會拋出ClassNotFoundException。
優勢:避免一個類被重複加載。
注意:即便兩個類來源於相同的class文件,若是使用的類加載器不一樣,加載後的對象時徹底不一樣的,這個不一樣反應在對象的 equals()、isAssignableFrom()、isInstance()等方法的返回結果,也包括了使用 instanceof 關鍵字對對象所屬關係的斷定結果。
缺點:頂層ClassLoader,沒法加載底層ClassLoader的類。如:
javax.xml.parsers包中定義了xml解析的類接口,Service Provider Interface SPI 位於rt.jar ,即接口在啓動ClassLoader中。而SPI的實現類,在AppLoader這樣就沒法用BootstrapClassLoader去加載SPI的實現類。
解決辦法:基本思想是,在頂層ClassLoader中,傳入底層ClassLoader的實例。即雙親委派模式的破壞。
初始化階段完成,類的加載完成。
④、使用。
⑤、卸載:當該類的class對象再也不被引用後(即便反射也不能獲取該類的對象的引用時)、當該類的類加載器ClassLoader被回收時、當該類的全部實例被回收時,以上條件所有知足時,該類在方法區進行垃圾回收時會被jvm卸載,相似在方法區清空類的信息。
由Java虛擬機自帶的類加載器所加載的類,在虛擬機的生命週期中,始終不會被卸載,用戶自定義的加載器所加載的類會被卸載。
(多方整理收集而成,侵刪)
如有不正之處,歡迎指出。