學習技術特別是工做中常常運用不到的技術也許會讓不少人感受煩悶枯燥,理論若是不結合實踐終究會成爲過眼雲煙。技術沒有捷徑,須要講究方法地持續學習。既然是原理,他確定是一個動做化地一系列行爲總和。行爲的背後確定有主體或者實體以及引入的背景。因此我的以爲在面試中,若是面試官問一些原理性東西,最好的方式仍是why what how;why-用於解答爲何須要這個東西,what-說明這個東西是什麼以及其特色 how-主要是結合what行爲化的描述其是如何實現特色的。java
本節是我的學習總結,主要是結合簡單的案例demo代碼以圖文並貌的形式總結下本身學習的JVM工做原理。從爲何引入JVM的java代碼如何運行?到JVM運行時候須要用到什麼主體-類加載器、字節碼執行引擎、JVM的各個運行時候數據區以及JVM在怎樣運行咱們代碼的,類加載器加載的數據會加載存儲到哪些數據區,字節碼執行引擎的時候是如何結合咱們的JVM運行數據去來工做的?以此來加深本身的知識體系。面試
咱們工做中寫的代碼是如何運行起來呢?咱們的.java後綴的代碼(源代碼)通過打包編譯後成爲字節碼.class(jar/war包),而後啓動一個tomcat或者使用java -jar xxx.jar便可開啓一個JVM進程來運行咱們的java代碼。上面就是咱們爲何須要JVM的緣由。
tomcat
咱們從工做中寫的代碼如何運行起來能夠知道?JVM運行起來時候是從編譯後的.class文件進行加載,而後確定是加載到內存中,因此這裏面就有JVM的運行數據區,運行的代碼文件不可能永無止境在加載到內存,確定會存在內存滿的時候,因此就存在垃圾,而後就牽涉到垃圾回收。因此JVM運行時候主要從類加載->在JVM的內存區域運行代碼->而後運行的代碼進行垃圾回收。多線程
類加載確定是加載java中的.class文件,可是何時纔會加載一個類?以及類加載到使用釋放的過程?類加載有哪些類加載器以及類加載的時候類加載的原理。學習
JVM在什麼狀況下會加載一個類?
當咱們經過java -jar命令運行一個JVM進程,此時JVM會先加載咱們main方法所在的類,而後經過字節碼引擎執行執行main方法,當須要某一個類的時候就會加載某個類。fetch
類從加載到使用經歷的過程
當須要使用到一個類的時候,會加載這個類,這個類從加載到使用的過程以下:
加載->校驗->準備->解析->初始化->使用->卸載。
加載:類加載器加載須要使用到的類(按需加載)。
校驗:校驗加載的類是否符合JVM規範,不符合規範的報錯,再也不進行其餘步驟。
準備:給加載進來的類變量分配空間,並設定默認值。引用類型爲null,基本類型爲對應值的原始值,好比int類型的爲0等
解析:將符號引用替換爲直接引用
初始化:先初始化類相關的代碼好比靜態成員變量,靜態代碼塊,而後初始化實例數據,初始化一個類時候,發現父類還沒初始化,先初始化父類。
使用:使用對象。
卸載:垃圾回收。 this
咱們看下下面的代碼執行步驟: spa
ReplicaManager
:線程
/** * @Author: yexinming * @Description: 副本管理器 * @Date: 2021/5/29 9:24 上午 */ public class ReplicaManager extends AbstractReplicaManager{ public static int flushInterval = Configuration.getInt("replica.flush.interval"); public static Map<String,Replica> replicas; private Integer count; private int cnt; public ReplicaManager(){ super(4); System.out.println("==ReplicaManager constructor 對象成員變量:count 以前=="+this.count); System.out.println("==ReplicaManager constructor 對象成員變量:cnt 以前=="+this.cnt); System.out.println("==ReplicaManager constructor 構造器方法執行=="); this.count = 5; System.out.println("==ReplicaManager constructor 對象成員變量:count 以後=="+this.count); } static { System.out.println("==ReplicaManager static 類成員變量:flushInterval=="+ReplicaManager.flushInterval); System.out.println("==ReplicaManager static 成員變量:replicas=="+ReplicaManager.replicas); System.out.println("==ReplicaManager static 靜態代碼塊執行=="); loadReplicaFromDisk(); } public static void loadReplicaFromDisk(){ System.out.println("==ReplicaManager loadReplicaFromDisk =從本地加載副本"); ReplicaManager.replicas = new HashMap<String, Replica>(); } public void load(){ System.out.println("==ReplicaManager load =ReplicaManager加載"); } public static void main(String[] args) { ReplicaManager manager = new ReplicaManager(); manager.load(); } }
AbstractReplicaManager
:code
public class AbstractReplicaManager { private int size; static { System.out.println("==AbstractReplicaManager static== "); } public AbstractReplicaManager(int size){ System.out.println("==AbstractReplicaManager constructor before== size"+this.size); System.out.println("==AbstractReplicaManager constructor excuting== "); this.size = size; System.out.println("==AbstractReplicaManager constructor after==AbstractReplicaManager size"+this.size); } }
運行時候輸出:
總結
:在使用類的時候咱們建立對象,建立對象的時候會先進行類相關信息的建立(靜態類變量,靜態代碼塊),而後執行對象信息的建立(構造器進行對象實現初始化)。在初始化階段咱們發現其對應的累屬性跟成員屬性已經有默認值了,因此在準備階段就已經賦值了,在初始化類的時候會想執行其父類的初始化。
類加載器有哪些?
JVM的類加載起具備親子層級管理,主要分爲4級:
啓動類加載器:Bootstrap ClassLoader:主要負責加載咱們安裝在機器上的Java目錄下核心類。
擴展類加載器:Extension ClassLoader:負責加載安裝到機器上Java目錄下的lib/ext。
應用類加載器:Application ClassLoader負責加載ClassPath環境變量下類,能夠理解爲加載你寫好的java代碼。
自定義類加載器:自定義類加載器,根據本身需求加載你的類。
類加載器的規則
爲了不重複加載一個類:JVM類加載機制使用雙親委派機制,好比你的本身寫大代碼執行時候,會先詢問讓應用類加載器去加載,而後應用類加載器去詢問擴展類加載器是否能夠加載,擴展類加載器去詢問啓動類加載器去加載。而後啓動類加載器去在對應職權範圍內發現沒有找到這個類,那麼就讓其子擴展類加載器去加載,而後擴展類加載器去對應職權範圍內沒有發現的話交給器子類去加載,而後應用類加載器去加載
咱們經過字節碼執行引擎執行代碼而後將用到的類經過類加載器加載到內存以後,就會使用這些類執行咱們的代碼。首先咱們會把類加載到存放類信息以及常量的方法區(因爲類只會加載一次,因此此方法去是線程共享的),執行的代碼後咱們須要記錄下字節碼執行引擎執行的位置,因此須要程序計數器,記錄下每個線程對方法執行到的位置,加載完類以後會建立對象將對象存放到java堆內存中(線程共享),對象建立以後會執行對象的方法,因爲方法的執行是多線程的,因此此時會爲此方法建立棧幀,而後將方法跟對象的局部變量壓棧道Java虛擬機棧,一樣若是方法底層調用native方法時候還會將其native方法壓入本地方法棧中。固然還有不其餘內存,叫作堆外內存:NIO中allocateDirect建立的內存空間不屬於JVM,而是在堆外分配的內存空間。
JVM的垃圾回收機制是用來幹嗎的?爲何要垃圾回收?
對象的分配與引用
字節碼執行引擎執行機器指令的時候,若是執行到某一個方法時候,會分配一個線程對應的Java虛擬機棧,並建立一個方法棧幀;而後將方法跟建立的局部變量壓入棧幀。局部變量若是是建立的一個對象,那麼會在java堆內存分配,並讓java虛擬機棧的局部變量引用java堆內存對象。
一個方法執行完畢以後會怎麼樣?
方法執行完畢以後,其方法會從對象的線程的java虛擬機棧中出棧,而後方法的棧幀消失,局部變量小時,此時在Java堆內存裏面的對象將可能變成未被引用的對象,變成垃圾對象。
再也不須要的那些對象應該怎麼處理?
JVM一旦啓動,他就會自帶一個垃圾回收的後臺線程,這個線程會在後臺在觸發GC時候會不斷檢查JVM堆內存裏面的各個實例對象。java堆內存裏面的對象沒有被局部變量引用以後,他還佔用着空間,因此咱們須要使用JVM的垃圾回收機制去回收這個對象。
什麼是JVM中的"垃圾"?
ava堆內存裏面的實例對象,沒有任何一個方法的局部變量指向他,也沒有任何一個類的靜態變量,包括常量指向他。
什麼是JVM中的垃圾回收?
JVM的後臺垃圾回收線程按期回收垃圾對象,從內存裏面清除掉,讓他再也不佔用內存。
以下代碼執行:
public class HDFS { private static ReplicaFetcher fetcher = new ReplicaFetcher(); public static void main(String[] args) { loadReplicasFromDisk(); fetcher.loadReplicaFromRemote(); } public static void loadReplicasFromDisk(){ ReplicaManager manager = new ReplicaManager(); manager.load(); } }
咱們結合上面的代碼從加載到執行最後消失的JVM工做原理圖.
。
上圖說明了:
一、執行多個方法的調用時,如何把方法的棧幀壓入線程的Java虛擬機棧?
二、棧幀裏如何放局部變量?
三、如何在Java堆裏建立實例對象?
四、如何讓局部變量引用那個實例對象?
五、方法運行完以後如何出棧?
六、垃圾回收是如何運行的?
加載到方法區的類會被回收嗎?何時被回收呢?爲何?在此狀況下,方法去裏面的類會被回收:一、首先,該類的全部實例變量都已經從Java堆內存裏被回收。 二、其次加載這個類的ClassLoader已經被回收。三、最後,對該類的Class對象沒有任何引用。