怎么加载文件_Java虚拟机从入门到入土之JVM的类加载机制
作者:六脈神劍
轉(zhuǎn)載于:https://juejin.im/post/5e1aaf626fb9a0301d11ac8e
JVM總體概述
JVM總體上是由
- 類裝載子系統(tǒng)(ClassLoader)
- 運(yùn)行時(shí)數(shù)據(jù)區(qū)
- 執(zhí)行引擎
- 內(nèi)存回收
- 類文件結(jié)構(gòu)
以上5個(gè)部分組成,每一個(gè)都是非常重要的,如果你要了解JVM,要學(xué)習(xí)JVM調(diào)優(yōu),那么只能是一個(gè)個(gè)去把他們啃了
什么是類加載機(jī)制
書上的原話:
虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)這些數(shù)據(jù)進(jìn)行校驗(yàn),轉(zhuǎn)換 解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型,這就是虛擬機(jī)的類加載機(jī)制
類加載的時(shí)機(jī)
類從被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止,它的生命周期包括
- 加載
- 驗(yàn)證
- 準(zhǔn)備
- 解析
- 初始化
- 使用
- 卸載
總共是7個(gè)階段
理解類加載三個(gè)字
首先 類 是指的.Class文件類,那么怎么生成這個(gè)文件呢?
- Java代碼編譯
- 原本就是.Class 文件
- 動(dòng)態(tài)代理生成
等等 還有很多
那么 加載 這2個(gè)字應(yīng)該怎么理解呢 大家可以看下圖
本地的.Class文件通過類加載器加載到JVM內(nèi)存中的方法區(qū)里面,然后通過這個(gè)對(duì)象來訪問數(shù)據(jù)區(qū)的數(shù)據(jù)
五種必須初始化的情況
Java并沒用規(guī)定生命時(shí)候進(jìn)行類加載的第一階段,但是對(duì)于初始化階段,虛擬機(jī)有嚴(yán)格的規(guī)范
- 遇到new 關(guān)鍵字的時(shí)候
- 使用reflect包的方法的時(shí)候
- 當(dāng)初始化一個(gè)類的時(shí)候發(fā)現(xiàn)父類還沒初始化,必須先初始化父類
- 當(dāng)虛擬機(jī)啟動(dòng)的時(shí)候,加載main方法的類
- 當(dāng)使用1.7的動(dòng)態(tài)語(yǔ)言支持的時(shí)候(這塊沒有接觸過,有沒有大佬懂的)
驗(yàn)證階段
分為以下幾種樣裝情況
- 文件格式的驗(yàn)證,驗(yàn)證當(dāng)前字節(jié)流是否能被JVM識(shí)別
- 元數(shù)據(jù)的驗(yàn)證,驗(yàn)證它的父類,它的繼承,是否是抽象類等
- 字節(jié)碼驗(yàn)證,驗(yàn)證邏輯是否合理
- 符合引用的驗(yàn)證 驗(yàn)證是否能通過生成的Class對(duì)象找到對(duì)應(yīng)的數(shù)據(jù)
準(zhǔn)備階段
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中分配。對(duì)于該階段有以下幾點(diǎn)需要注意:
1、這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(static),而不包括實(shí)例變量,實(shí)例變量會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一塊分配在Java堆中。
2、這里所設(shè)置的初始值通常情況下是數(shù)據(jù)類型默認(rèn)的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。
假設(shè)一個(gè)類變量的定義為:
public static int value = 3;
那么變量value在準(zhǔn)備階段過后的初始值為0,而不是3,因?yàn)檫@時(shí)候尚未開始執(zhí)行任何Java方法,而把value賦值為3的putstatic指令是在程序編譯后,存放于類構(gòu)造器()方法之中的,所以把value賦值為3的動(dòng)作將在初始化階段才會(huì)執(zhí)行。
下表列出了Java中所有基本數(shù)據(jù)類型以及reference類型的默認(rèn)零值:
這里還需要注意如下幾點(diǎn):
- 對(duì)基本數(shù)據(jù)類型來說,對(duì)于類變量(static)和全局變量,如果不顯式地對(duì)其賦值而直接使用,則系統(tǒng)會(huì)為其賦予默認(rèn)的零值,而- 對(duì)于局部變量來說,在使用前必須顯式地為其賦值,否則編譯時(shí)不通過。
- 對(duì)于同時(shí)被static和final修飾的常量,必須在聲明的時(shí)候就為其顯式地賦值,否則編譯時(shí)不通過;而只被final修飾的常量則既可以在聲明時(shí)顯式地為其賦值,也可以在類初始化時(shí)顯式地為其賦值,總之,在使用前必須為其顯式地賦值,系統(tǒng)不會(huì)為其賦予默認(rèn)零值。
- 對(duì)于引用數(shù)據(jù)類型reference來說,如數(shù)組引用、對(duì)象引用等,如果沒有對(duì)其進(jìn)行顯式地賦值而直接使用,系統(tǒng)都會(huì)為其賦予默認(rèn)的零值,即null。
- 如果在數(shù)組初始化時(shí)沒有對(duì)數(shù)組中的各元素賦值,那么其中的元素將根據(jù)對(duì)應(yīng)的數(shù)據(jù)類型而被賦予默認(rèn)的零值。
如果類字段的字段屬性表中存在ConstantValue屬性,即同時(shí)被final和static修飾,那么在準(zhǔn)備階段變量value就會(huì)被初始化為ConstValue屬性所指定的值。
解析階段
解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程 ##初始化階段 到了初始化階段,才真正開始執(zhí)行類中定義的Java代碼
類加載器
站在Java虛擬機(jī)的角度來講,只存在兩種不同的類加載器:
- 啟動(dòng)類加載器:它使用C++實(shí)現(xiàn)(這里僅限于Hotspot,也就是JDK1.5之后默認(rèn)的虛擬機(jī),有很多其他的虛擬機(jī)是用Java語(yǔ)言實(shí)現(xiàn)的),是虛擬機(jī)自身的一部分。
- 所有其他的類加載器:這些類加載器都由Java語(yǔ)言實(shí)現(xiàn),獨(dú)立于虛擬機(jī)之外,并且全部繼承自抽象類java.lang.ClassLoader,這些類加載器需要由啟動(dòng)類加載器加載到內(nèi)存中之后才能去加載其他的類。
站在Java開發(fā)人員的角度來看,類加載器可以大致劃分為以下四類:
- 啟動(dòng)類加載器 (C實(shí)現(xiàn))
- 擴(kuò)展類加載器 (ClassLoader)
- 應(yīng)用程序加載器 (ClassLoader)
- 自定義加載器 (ClassLoader)
這幾種類加載器的層次關(guān)系如下圖所示:
雙親委派模型
類加載器之間的這種層次關(guān)系叫做雙親委派模型。 雙親委派模型要求除了頂層的啟動(dòng)類加載器(Bootstrap ClassLoader)外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。這里的類加載器之間的父子關(guān)系一般不是以繼承關(guān)系實(shí)現(xiàn)的,而是用組合實(shí)現(xiàn)的。
雙親委派模型的工作過程
由我來概況就是 八個(gè)字 向上檢查,從下加載 如果一個(gè)類接受到類加載請(qǐng)求,他自己不會(huì)去加載這個(gè)請(qǐng)求,而是將這個(gè)類加載請(qǐng)求委派給父類加載器,這樣一層一層傳送,直到到達(dá)啟動(dòng)類加載器(Bootstrap ClassLoader)。 只有當(dāng)父類加載器無法加載這個(gè)請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去加載。
雙親委派模型的代碼實(shí)現(xiàn)
雙親委派模型的代碼實(shí)現(xiàn)集中在java.lang.ClassLoader的loadClass()方法當(dāng)中。
- 首先檢查類是否被加載,沒有則調(diào)用父類加載器的loadClass()方法;
- 若父類加載器為空,則默認(rèn)使用啟動(dòng)類加載器作為父加載器;
- 若父類加載失敗,拋出ClassNotFoundException 異常后,再調(diào)用自己的findClass() 方法。 loadClass源代碼如下:
自定義類加載器
class NetworkClassLoader extends ClassLoader { * String host; * int port; * * public Class findClass(String name) { * byte[] b = loadClassData(name); * return defineClass(name, b, 0, b.length); * } * private byte[] loadClassData(String name) { // load the class data from the connection . . . } }這個(gè)就是官方的例子
破環(huán)雙親委派
雙親委派模型很好的解決了各個(gè)類加載器加載基礎(chǔ)類的統(tǒng)一性問題。即越基礎(chǔ)的類由越上層的加載器進(jìn)行加載。 若加載的基礎(chǔ)類中需要回調(diào)用戶代碼,而這時(shí)頂層的類加載器無法識(shí)別這些用戶代碼,怎么辦呢?這時(shí)就需要破壞雙親委派模型了。
java默認(rèn)的線程上下文類加載器是系統(tǒng)類加載器(AppClassLoader).
// Now create the class loader to use to launch the application try { loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError( "Could not create application class loader" ); } // Also set the context class loader for the primordial thread. Thread.currentThread().setContextClassLoader(loader);以上代碼摘自sun.misc.Launch的無參構(gòu)造函數(shù)Launch()。
使用線程上下文類加載器,可以在執(zhí)行線程中,拋棄雙親委派加載鏈模式,使用線程上下文里的類加載器加載類.
典型的例子有,通過線程上下文來加載第三方庫(kù)jndi實(shí)現(xiàn),而不依賴于雙親委派.
大部分java app服務(wù)器(jboss, tomcat..)也是采用contextClassLoader來處理web服務(wù)。
結(jié)尾
今天把類加載機(jī)制好好講了一下,這樣大家就更加的熟悉了內(nèi)的加載過程,對(duì)于Java開發(fā)是有好處的
總結(jié)
以上是生活随笔為你收集整理的怎么加载文件_Java虚拟机从入门到入土之JVM的类加载机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php网站建设实训引言_网站建设实训报告
- 下一篇: java 匿名类型_Java之匿名类讲解