標籤:
JVM記憶體地區模型
1.方法區
也稱"永久代” 、“非堆”, 它用於儲存虛擬機器載入的類資訊、常量、靜態變數、是各個線程共用的記憶體地區。預設最小值為16MB,最大值為64MB,可以通過-XX:PermSize 和 -XX:MaxPermSize 參數限制方法區的大小。
運行時常量池:是方法區的一部分,Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池,用於存放編譯器產生的各種符號引用,這部分內容將在類載入後放到方法區的運行時常量池中。
2.虛擬機器棧
描述的是java 方法執行的記憶體模型:每個方法被執行的時候 都會建立一個“棧幀”用於儲存局部變數表(包括參數)、操作棧、方法出口等資訊。每個方法被調用到執行完的過程,就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。聲明周期與線程相同,是線程私人的。
局部變數表存放了編譯器可知的各種基礎資料型別 (Elementary Data Type)(boolean、byte、char、short、int、float、long、double)、對象引用(引用指標,並非對象本身),其中64位長度的long和double類型的資料會佔用2個局部變數的空間,其餘資料類型只佔1個。局部變數表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀中分配多大的局部變數是完全確定的,在運行期間棧幀不會改變局部變數表的大小空間。
3.本地方法棧
與虛擬機器棧基本類似,區別在於虛擬機器棧為虛擬機器執行的java方法服務,而本地方法棧則是為Native方法服務。
4.堆
也叫做java 堆、GC堆是java虛擬機器所管理的記憶體中最大的一塊記憶體地區,也是被各個線程共用的記憶體地區,在JVM啟動時建立。該記憶體地區存放了對象執行個體及數組(所有new的對象)。其大小通過-Xms(最小值)和-Xmx(最大值)參數設定,-Xms為JVM啟動時申請的最小記憶體,預設為作業系統實體記憶體的1/64但小於1G,-Xmx為JVM可申請的最大記憶體,預設為實體記憶體的1/4但小於1G,預設當空餘堆記憶體小於40%時,JVM會增大Heap到-Xmx指定的大小,可通過-XX:MinHeapFreeRation=來指定這個比列;當空餘堆記憶體大於70%時,JVM會減小heap的大小到-Xms指定的大小,可通過XX:MaxHeapFreeRation=來指定這個比列,對於運行系統,為避免在運行時頻繁調整Heap的大小,通常-Xms與-Xmx的值設成一樣。
由於現在收集器都是採用分代收集演算法,堆被劃分為新生代和老年代。新生代主要儲存新建立的對象和尚未進入老年代的對象。老年代儲存經過多次新生代GC(Minor GC)任然存活的對象。
新生代:
程式新建立的對象都是從新生代分配記憶體,新生代由Eden Space和兩塊相同大小的Survivor Space(通常又稱S0和S1或From和To)構成,可通過-Xmn參數來指定新生代的大小,也可以通過-XX:SurvivorRation來調整Eden Space及Survivor Space的大小。
老年代:
用於存放經過多次新生代GC任然存活的對象,例如緩衝對象,建立的對象也有可能直接進入老年代,主要有兩種情況:①.大對象,可通過啟動參數設定-XX:PretenureSizeThreshold=1024(單位為位元組,預設為0)來代表超過多大時就不在新生代分配,而是直接在老年代分配。②.大的數組對象,切數組中無引用外部對象。
老年代所佔的記憶體大小為-Xmx對應的值減去-Xmn對應的值。
5.程式計數器
是最小的一塊記憶體地區,它的作用是當前線程所執行的位元組碼的行號指標,在虛擬機器的模型裡,位元組碼解譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、異常處理、線程恢複等基礎功能都需要依賴計數器完成。
JVM載入過程
Java語言中,類只有被載入到JVM中才能運行,當運行指定的java程式時,JVM會將編譯產生的 .class檔案按照一定的規則載入到記憶體中,並組織成為一個完整的應用程式。類的載入過程是由類載入器完成的(即由ClassLoader和它的子類完成),而類載入器本身也是一個類,其實質是將類檔案由硬碟載入到記憶體中。
類的載入方式有兩種:
(1)明確式載入
通過調用class.forName()方法將所需的類載入到JVM中
(2)隱式載入
程式在建立新的對象時,隱式地調用類載入器把對應的類載入到JVM中
在java語言中,類的載入是動態且靈活的,往往一個大的項目包含很多類,而每一個類或介面都對應一個.class檔案,當程式運行時只需要將需要的類(保證程式啟動並執行基礎類,例如基類)載入到JVM中,暫時不需要的類可以先不載入,這樣一方面可以提高運行速度,另一方面也可以降低程式運行時對記憶體的開銷。而且,每一個類檔案都可以看成是動態載入單元,當項目需要對某個類進行修改時,修改完畢後只需要重新編譯載入被修改的類即可,而不用全部的類都重新進行編譯。
類可以分為三種:系統類別、擴充類、自訂類,而java根據不同的類提供了不同的類載入器
Bootstrap Loader <==載入系統類別(jre/lib/rt.jar的類)
ExtClassLoader <==載入擴充類(jar/lib/etc/*.jar的類)
AppClassLoader <==載入應用類(classpath指定的目錄或jar中的類)
具體步驟:
(1)首先java.exe會找到JRE,並且找到位於JRE內部的jvm.dll,這才是真正的java虛擬機器,然後載入到動態庫,啟用java虛擬機器。
(2)進行初始化操作,結束之後產生Bootstrap Loader啟動類載入器
(3)Bootstrap Loader除了進行一些基本的初始化動作外,最重要的是載入ExtClassLoader擴充類載入器,並且設定其Parent為null,也就代表其父載入器為Bootstrap Loader
(4)然後Bootstrap Loader再要求載入Launcher.java中的AppClassLoader(自訂類載入器),並設定其Parent為ExtClassLoader實體,這兩個載入器都是以靜態類的形式存在的。
※需要注意的是,其實parent是誰跟被誰載入的並沒有直接關係
我們可以測試一下:
package test; public class classloader { public static void main(String[] args) throws Exception{ ClassLoader App = classloader.class.getClassLoader();//class載入器 System.out.println(App); ClassLoader Ext = App.getParent();//上一層載入器 System.out.println(Ext); ClassLoader Boot = Ext.getParent();//根部載入器 System.out.println(Boot); } }
運行結果:
[email protected]
[email protected]
null
Bootstrap Loader輸出null的原因是它是由C++語言實現的,所以在java語言中看不到
程式說明classloader這個類是由AppClassLoader載入的
類的載入主要有三步:
(1)裝載:根據尋找路徑找到相應的class檔案並匯入
(2)連結:檢查class檔案是否正確-->給類中的靜態變數分配儲存空間-->將符號引用轉換成直接引用
(3)初始化:靜態變數和靜態代碼塊的初始化操作
雙親委託機制:
雙親委託模式也就是一個類載入器請求另一個類載入器來載入類型的過程。
除啟動類載入器以外的每一個類載入器,都有一個“雙親”類載入器 ,在某個特定的類載入器試圖以常用方式載入某個類以前,它會先預設地將這個任務“委派”給它的雙親,請求它的雙親來載入這個類。這個雙親再依次請求它自己的雙親來載入這個類型。這個委派的過程一直向上繼續,直到達到啟動類載入器,通常啟動類載入器是委派鏈中的最後一個類載入器。如果一個類載入器的雙親類載入器有能力來載入這個類型。則這個類載入器返回這個類型。否則,這個類載入器試圖自己來載入這個類。
當一個程式運行時,虛擬機器在啟動時執行個體化了兩個使用者自訂類載入器:一個“擴充類載入器”,一個“自訂類載入器”.這些類裝載器和啟動類載入器一起聯入一個Parent-Child委託鏈中,啟動類載入器在最頂端。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
【Java進階】JVM記憶體地區模型和載入過程