JVM04内存结构概述
JVM架構
類加載器的子系統:
- 類加載器子系統負責從文件系統或者網絡中加載class文件,class文件在文件頭有特定的文件標識
- ClassLoader只負責class文件加載,至于它是否可運行,則由Execution Engine決定
- 加載的類信息存放于一塊成為方法區的內存空間。除了類信息外,方法區中還會存放運行時常量池的信息,可能還包括字符串字面量和數字常量(這部分常量信息是Class文件中常量池部分的內存映射)
類加載器ClassLoader角色:
1.class file存在于本地硬盤上,可以理解為設計師畫在紙上的模板,而最終這個模板在執行的時候是要加載到JVM當中來,根據這個文件實例化出n個一模一樣的實例。
2.class file加載到JVM中,被成為DNA元數據模板,放在方法區
3.在.class文件—>JVM—>最終成為元數據模板,在此過程就要一個運輸工具(類裝載器Class Loader),扮演一個快遞員角色。
類加載過程:
1.加載(loading)
加載時jvm做了這三件事:
1)通過一個類的全限定名來獲取該類的二進制字節流2)將這個字節流的靜態存儲結構轉化為方法區運行時數據結構3)**在內存中生成一個代表該類的java.lang.Class對象**,作為方法區這個類的各種數據的訪問入口補充:加載.class文件的方式
從本地系統中直接加載
- 通過網絡獲取,典型場景:Web Applet
- 從zip壓縮包中讀取,成為日后jar、war格式的基礎
- 運行時計算生成的,使用最多的是:動態代理技術
- 有其他文件生成,典型場景:JSP的生成
- 從轉有的數據庫中提取.class文件,比較少見
- 從加密文件中獲取,典型的防class文件被反編譯的保護措施
2.鏈接:
驗證(Verify):
- 目的在于確保Class文件的字節流中包含信息符合當前虛擬機要求,保證被加載類的正確性,不會危害虛擬機自身安全。
- 主要包括四種驗證,文件格式驗證,元數據驗證,字節碼驗證,符號引用驗證
準備(Paper):
- 為變量分配內存并且設置該變量的默認初始值,即零值。
- 這里不包含用final修飾的static,因為final在編譯的時候會分配了,準備階段會顯式初始化;
- 這里不會為實例變量分配初始化,類變量會分配在方法區中,而實例變量是會隨著對象一起分配到Java堆中。
解析(Resolve):
- 將常量池內的符號引用轉換為直接引用過程。
- 事實上,解析操作往往會伴隨著JVM在執行完初始化之后再執行。
- 符號引用就是一組符號來描述所引用的目標。符號引用的字面量形式明確定義在《Java虛擬機規范》的Class文件格式中。直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標句柄。
- 解析動作主要針對類或接口、字段、類方法、接口方法、方法類型等。對應常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
三、初始化(initialization):
- 初始化階段就是執行類構造器方法<clinit()的過程,clinit方法相當于class的初始化。
- 此方法不需要定義,是Javac編譯器自動收集類中的所有類變量的賦值動作和靜態代碼塊中的語句合并來的。
- 構造器方法中指令按語句來源文件中出現的順序執行
- <clinit()不同于類的構造器。(關聯:構造器是虛擬機視角下的())
- 若該類具有父類,JVM會保證子類的<clinit()執行前,父類的<clinit()已經執行完成
虛擬機必須保證一個類的方法在多線程下被同步加鎖。
(clint那里還有一個>,但是因為加了之后不顯示,所以先去掉)
具體談一下clint和init:
<clinit,類構造器方法,在jvm第一次加載class文件時調用,因為是類級別的,所以只加載一次,是編譯器自動收集類中所有類變量(static修飾的變量)和靜態語句塊(static{}),中的語句合并產生的,編譯器收集的順序,是由程序員在寫在源文件中的代碼的順序決定的。
<init,實例構造器方法,在實例創建出來的時候調用,包括調用new操作符;調用Class或java.lang.reflect.Constructor對象的newInstance()方法;調用任何現有對象的clone()方法;通過java.io.ObjectInputStream類的getObject()方法反序列化。
<clinit() 與 <init() 區別
<clinit()
Java 類加載的初始化過程中,編譯器按語句在源文件中出現的順序,依次自動收集類中的所有類變量的賦值動作和靜態代碼塊中的語句合并產生 <clinit() 方法。 如果類中沒有靜態語句和靜態代碼塊,那可以不生成<clinit()` 方法。
clinit是類構造器方法,也就是在jvm進行類加載—–驗證—-解析—–初始化,中的初始化階段jvm會調用clinit方法。
并且 <clinit() 不需要顯式調用父類(接口除外,接口不需要調用父接口的初始化方法,只有使用到父接口中的靜態變量時才需要調用)的初始化方法 <clinit(),虛擬機會保證在子類的 <clinit() 方法執行之前,父類的 <clinit() 方法已經執行完畢。
<init()
對象構造時用以初始化對象的,構造器以及非靜態初始化塊中的代碼。
init是對象構造器方法,也就是說在程序執行 new 一個對象調用該對象類的 constructor 方法時才會執行init方法
init is the (or one of the) constructor(s) for the instance, and non-static field initialization.
clinit are the static initialization blocks for the class, and static field initialization.
上面這兩句是Stack Overflow上的解析,很清楚init是instance實例構造器,對非靜態變量解析初始化,而clinit是class類構造器對靜態變量,靜態代碼塊進行初始化。
即一個為類加載過程(clinit): clinit方法中的執行順序為:父類靜態變量初始化,父類靜態代碼塊,子類靜態變量初始化,子類靜態代碼塊; clinit()方法只執行一次。
一個是對象實例化過程, init()方法中的執行順序為:父類變量初始化,父類代碼塊,父類構造器,子類變量初始化,子類代碼塊,子類構造器。
clinit()方法優先于init()方法執行,所以整個順序就是:
父類靜態變量初始化,父類靜態代碼塊,子類靜態變量初始化,子類靜態代碼塊,父類非靜態變量初始化,父類非靜態代碼塊,父類構造器,子類非靜態變量初始化,子類非靜態代碼塊,子類構造器。
<clinit() 與 <init() 執行順序
一個代碼用例,用的別人文章里的
輸出信息:
static開始 構造器開始 x=0;y=0 x=1;y=1 構造器結束 static結束 x=6 y=1虛擬機首先執行的是類加載初始化過程中的 <clinit() 方法,也就是靜態變量賦值以及靜態代碼塊中的代碼,如果 <clinit() 方法中觸發了對象的初始化,也就是 <init() 方法,那么會進入執行 <init() 方法,執行 <init() 方法完成之后,再回來繼續執行 <clinit() 方法。
這段用的例子的地址如下:
https://www.jianshu.com/p/7ff65c3040ec
四.類加載器的分類
- JVM支持兩種類型的類加載器,分別為引導類加載器(Bootstrap ClassLoader)和自定義類加載器(User-Defined
ClassLoader). - 從概念上來講,自定義類加載器一般指的是程序中由開發人員自定義的一類加載器,但是Java虛擬機規范卻沒有這么定義,而是將所有派生于抽象類ClassLoader的類加載器都劃分為自定義類加載器.
- 無論類加載器的類型如何劃分,在程序中我們最常見的類加載器始終只有3個
1. 啟動類加載器:Bootstrap ClassLoader(引導類加載器)
負責加載存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被-Xbootclasspath參數指定的路徑中的,并且能被虛擬機識別的類庫(如rt.jar,所有的java.開頭的類均被Bootstrap ClassLoader加載)。啟動類加載器是無法被Java程序直接引用的。 - 這個類使用c/c++語言實現,嵌套在JVM內部
- 用來加載Java核心庫(JAVA_HOME/jre/lib/rt.jar\resourece.jar或sun.boot.class.path下的內容),用于提供JVM自身需要的類
- 并不繼承自Java.lang.ClassLoader,沒有父類加載器
- 加載擴展類和應用程序類加載器,并指定為他們的父類加載器
- 出于安全考慮,Bootstrap啟動類加載器只加載包名為java、javax、sun等開頭的類
2. 擴展類加載器:Extension ClassLoader
該加載器由sun.misc.Launcher$ExtClassLoader實現,它負責加載JDK\jre\lib\ext目錄中,或者由java.ext.dirs系統變量指定的路徑中的所有類庫(如javax.開頭的類),開發者可以直接使用擴展類加載器。
- Java語言編寫
- 派生于ClassLoader類
- 父類加載器為啟動類加載器
- 從Java.ext.dirs系統屬性所指定的目錄中加載類庫,或從JDK的安裝目錄的jre/lib/ext子目錄(擴展目錄)下加載類庫。如果用戶創建的JAR放在此目錄下,也會自動由擴展類加載器加載。
3. 應用程序類加載器:Application ClassLoader(系統類加載器)
該類加載器由sun.misc.Launcher$AppClassLoader來實現,它負責加載用戶類路徑(ClassPath)所指定的類,開發者可以直接使用該類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。
- Java語言編寫,由sun.misc.Launcher$AppClassLoader實現
- 派生于ClassLoader類
- 父類加載器為擴展類加載器
- 它負責加載環境變量classpath或系統屬性 java.class.path指定路徑下的類庫
- 該類加載是程序中默認的類加載器,一般來說,Java應用類都是由它來完成加載的
通過ClassLoader#getSystemClassLoader()方法可用獲取到該類加載器
4. 自定義類加載器(反射)UrlClassLoader
UrlClassLoader u=new UrlClassLoader(new RUL[] {“c:/a.jar”}) //要加載的路徑數組
Class c =u.loadClass(“cn.et.A”); //Class 是通用 類加載器
Object obj=c.newInstance(); //實例化
Method method =c.getMethod(“syso”); //通用方法Method 獲取加載類a的syso方法
method.invoke(obj) //反射,執行方法
為什么要自定義類加載器:
- 隔離加載類
- 修改類加載的方式
- 擴展加載源
- 防止源碼泄露
用戶自定義類加載器實現步驟:
1.開發人員可用通過繼承抽象類Java.lang.ClassLoader類的方式,實現自己的類加載器,以滿足一些特殊的需求
2.在JDK1.2之前,在自定義類加載器時,總會去繼承ClassLoader類并重寫loadClass()方法,從而實現自定義的類加載類,但是JDK1.2之后,已不再建議用戶去覆蓋loadClass()方法,而是建議把自定義的類加載邏輯寫在findClass()方法中
3.在編寫自定義類加載器時,如果沒有太過于負責的需求,可用之間繼承URLClassLoader類,這樣就可以避免自己去寫findClass()方法以及其獲取字節碼流的方式,使自定義類加載器編寫更加簡潔。
關于ClassLoader
是一個抽象類,其后所有的類加載器都繼承自ClassLoader(不包括啟動類加載器)。
獲取ClassLoader的途徑
方式一:獲取當前類的ClassLoader
class.getClassLoader()
方式二:獲取當前線程上下文的ClassLoader
Thread.currentThread().getContextClassLoader()
方式三:獲取系統的ClassLoader
ClassLoader.getSystemClassLoader()
方式四:獲取調用者的ClassLoader
DriverManger.getCallerClassLoader()
總結
以上是生活随笔為你收集整理的JVM04内存结构概述的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: harmonyos不用jvm,关于har
- 下一篇: php下拉上滑分页,Flutter实现下