日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

2021年度最全面JVM虚拟机,类加载过程与类加载器

發(fā)布時(shí)間:2023/12/4 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 2021年度最全面JVM虚拟机,类加载过程与类加载器 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

類裝載器子系統(tǒng)是JVM中非常重要的部分,是學(xué)習(xí)JVM繞不開的一關(guān)。

一般來說,Java 類的虛擬機(jī)使用 Java 方式如下:

Java 源程序(.java 文件)在經(jīng)過 Java 編譯器編譯之后就被轉(zhuǎn)換成 Java 字節(jié)代碼(.class 文件)。

類加載器負(fù)責(zé)讀取 Java 字節(jié)代碼,并轉(zhuǎn)換成 java.lang.Class類的一個(gè)實(shí)例。
每個(gè)這樣的實(shí)例用來表示一個(gè) Java 類。

通過此實(shí)例的 newInstance()方法就可以創(chuàng)建出該類的一個(gè)對象。

類的生命周期

我們先來看下類的生命周期,包括:

  • 加載

  • 連接

  • 初始化

  • 使用

  • 卸載

其中加載、連接、初始化屬于類加載過程。

使用是指我們new對象進(jìn)行使用。

卸載指對象被GC垃圾回收掉。

類加載過程

JVM的類加載的過程是通過引導(dǎo)類加載器(bootstrap class loader)創(chuàng)建一個(gè)初始類(initial class)來完成的,這個(gè)類是由JVM的具體實(shí)現(xiàn)指定的。

Class 文件需要加載到虛擬機(jī)中之后才能運(yùn)行和使用,系統(tǒng)加載 Class 類型的文件份如下幾步:

  • 加載
  • 連接
  • 驗(yàn)證
  • 準(zhǔn)備
  • 解析
  • 初始

順序是這樣一個(gè)順序,但是加載階段和連接階段的部分內(nèi)容是交叉進(jìn)行的,加載階段尚未結(jié)束,連接階段可能就已經(jīng)開始了。

下面我們來逐步解析

加載

這里的加載是微觀上的,是類加載過程中的一小步,也是第一步,類加載過程中的加載是宏觀上的。

加載的流程如下:

  • 通過全類名獲取定義此類的二進(jìn)制字節(jié)流

  • 將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)

  • 在內(nèi)存中生成一個(gè)代表該類的 Class 對象,作為方法區(qū)這些數(shù)據(jù)的訪問入口

  • 簡單來說就是:加載二進(jìn)制數(shù)據(jù)到內(nèi)存 —> 映射成JVM能識(shí)別的結(jié)構(gòu)—> 在內(nèi)存中生成class文件。

  • 在虛擬機(jī)規(guī)范上,對這部分的規(guī)定并不具體,所以實(shí)現(xiàn)方式是很靈活的。

加載階段我們可以用自定義類加載器去控制字節(jié)流的獲取方式,是非數(shù)組類的可控性最強(qiáng)的階段,而數(shù)組類型不通過類加載器創(chuàng)建,它由 Java 虛擬機(jī)直接創(chuàng)建。

關(guān)于類加載器是什么,后文再聊。//加入Java開發(fā)交流君樣:756584822一起吹水聊天

連接

連接分為三步,驗(yàn)證、準(zhǔn)備、解析,目的是將上面創(chuàng)建好的Class類合并至JVM中,使之能夠執(zhí)行的過程。

驗(yàn)證

確保class文件中的字節(jié)流包含的信息,符合當(dāng)前虛擬機(jī)的要求,保證這個(gè)被加載的class類的正確性,不會(huì)危害到虛擬機(jī)的安全。

準(zhǔn)備

為類中的靜態(tài)字段分配內(nèi)存,并設(shè)置默認(rèn)的初始值,比如int類型初始值是0。

被final修飾的static字段不會(huì)設(shè)置,因?yàn)閒inal在編譯的時(shí)候就分配了。

解析

解析階段的目的,是將常量池內(nèi)的符號引用轉(zhuǎn)換為直接引用的過程。

解析動(dòng)作主要針對類、接口、字段、類方法、接口方法、方法類型等。
如果符號引用指向一個(gè)未被加載的類,或者未被加載類的字段或方法,那么解析將觸發(fā)這個(gè)類的加載(但未必觸發(fā)這個(gè)類的鏈接以及初始化。)

符號引用就是一組符號來描述目標(biāo),可以是任何字面量。

直接引用就是直接指向目標(biāo)的指針、相對偏移量或一個(gè)間接定位到目標(biāo)的句柄。

舉個(gè)例子:

在程序執(zhí)行方法時(shí),系統(tǒng)需要明確知道這個(gè)方法所在的位置。

Java 虛擬機(jī)為每個(gè)類都準(zhǔn)備了一張方法表來存放類中所有的方法。

當(dāng)需要調(diào)用一個(gè)類的方法的時(shí)候,只要知道這個(gè)方法在方法表中的偏移量就可以直接調(diào)用該方法了。

通過解析操作符號引用就可以直接轉(zhuǎn)變?yōu)槟繕?biāo)方法在類中方法表的位置,從而使得方法可以被調(diào)用。

所以,解析階段是虛擬機(jī)將常量池內(nèi)的符號引用替換為直接引用的過程,也就是得到類或者字段、方法在內(nèi)存中的指針或者偏移量。

初始化

初始化就是執(zhí)行類的構(gòu)造器方法,是類加載的最后一步,這一步 JVM才開始真正執(zhí)行類中定義的 Java 程序代碼

這個(gè)方法不需要定義,是javac編譯器自動(dòng)收集類中所有類變量的賦值動(dòng)作和靜態(tài)代碼塊中的語句合并來的。

若該類具有父類,jvm會(huì)保證父類的init()先執(zhí)行,然后在執(zhí)行子類的init()。

對于初始化階段,虛擬機(jī)嚴(yán)格規(guī)范了有且只有 5 種情況下,必須對類進(jìn)行初始化,只有主動(dòng)去使用類才會(huì)初始化類:

  • 當(dāng)遇到 new 、getstatic、putstatic或invokestatic這 4 條直接碼指令時(shí)

  • 當(dāng)遇到一個(gè)類,讀取一個(gè)靜態(tài)字段(未被final修飾)、或調(diào)用一個(gè)類的靜態(tài)方法時(shí)。

  • 當(dāng) JVM執(zhí)行new指令時(shí)會(huì)初始化類。即當(dāng)程序創(chuàng)建一個(gè)類的實(shí)例對象。

  • 當(dāng) JVM執(zhí)行 getstatic指令時(shí)會(huì)初始化類。即程序訪問類的靜態(tài)變量(不是靜態(tài)常量,常量會(huì)被加載到運(yùn)行時(shí)常量池)。

  • 當(dāng) JVM執(zhí)行 putstatic指令時(shí)會(huì)初始化類。即程序給類的靜態(tài)變量賦值。

  • 當(dāng) JVM執(zhí)行invokestatic指令時(shí)會(huì)初始化類。即程序調(diào)用類的靜態(tài)方法。

  • 對類進(jìn)行反射調(diào)用時(shí),如果類沒初始化,需要觸發(fā)其初始化。

初始化一個(gè)類,如果其父類還未初始化,則先觸發(fā)該父類的初始化。

當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要定義一個(gè)要執(zhí)行的主類 (包含 main 方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)類。

MethodHandle 和 VarHandle 可以看作是輕量級的反射調(diào)用機(jī)制,而要想使用這 2 個(gè)調(diào)用, 就必須先使用 findStaticVarHandle 來初始化要調(diào)用的類。

「補(bǔ)充,來自issue745」 當(dāng)一個(gè)接口中定義了 JDK8 新加入的默認(rèn)方法(被 default 關(guān)鍵字修飾的接口方法)時(shí),如果有這個(gè)接口的實(shí)現(xiàn)類發(fā)生了初始化,那該接口要在其之前被初始化。

類加載器

三大類加載器

了解了類加載過程后,我們來看看類加載器。

類加載器(ClassLoader)用來加載 Java 類到 Java 虛擬機(jī)中。

JVM 中內(nèi)置了三個(gè)重要的 ClassLoader,同時(shí)按如下順序進(jìn)行加載:

  • BootstrapClassLoader啟動(dòng)類加載器:最頂層的加載類,由C++實(shí)現(xiàn),負(fù)責(zé)加載 %JAVA_HOME%/lib目錄下的核心jar包和類或者或被 -Xbootclasspath參數(shù)指定的路徑中的所有類。
  • ExtensionClassLoader擴(kuò)展類加載器:主要負(fù)責(zé)加載目錄%JRE_HOME%/lib/ext目錄下的jar包和類,或被 java.ext.dirs 系統(tǒng)變量所指定的路徑下的jar包。
  • AppClassLoader 應(yīng)用程序類加載器:面向我們用戶的加載器,負(fù)責(zé)加載當(dāng)前應(yīng)用classpath下的所有jar包和類。
    除了BootstrapClassLoader其他類加載器均由 Java 實(shí)現(xiàn)且全部繼承自java.lang.ClassLoader:

類的加載幾乎是由上述3種類加載器相互配合執(zhí)行的,在必要時(shí),我們還可以自定義類加載器。

需要注意的是,Java虛擬機(jī)對Class文件采用的是按需加載的方式,也就是說當(dāng)需要使用該類時(shí)才會(huì)將它的Class文件加載到內(nèi)存生成Class對象。

雙親委派模型

概念

每一個(gè)類都有一個(gè)對應(yīng)它的類加載器。在加載類的時(shí)候,是采用的雙親委派模型,即把請優(yōu)求先交給父類處理的一種任務(wù)委派模式。

系統(tǒng)中的類加載器在協(xié)同工作的時(shí)候會(huì)默認(rèn)使用 雙親委派模型 。//加入Java開發(fā)交流君樣:756584822一起吹水聊天

雙親委派模型的理論很簡單,分為如下幾步:

  • 即在類加載的時(shí)候,系統(tǒng)會(huì)首先判斷當(dāng)前類是否被加載過。已經(jīng)被加載的類會(huì)直接返回,否則才會(huì)嘗試加載。

  • 加載的時(shí)候,首先會(huì)把該請求委派給該父類加載器的 loadClass()處理,因此所有的請求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器 BootstrapClassLoader 中。
    當(dāng)父類加載器無法處理時(shí),才由自己來處理。

  • AppClassLoader的父類加載器為ExtensionClassLoader ,ExtensionClassLoader 的父類加載器為null,當(dāng)父類加載器為null時(shí),會(huì)使用啟動(dòng)類加載器BootstrapClassLoader 作為父類加載器。

為什么要使用雙親委派模型

試想一種情況,我們在項(xiàng)目目錄下,手動(dòng)創(chuàng)建了一個(gè)java.lang 包,并在該包下創(chuàng)建了一個(gè)Object,這時(shí)候我們再去啟動(dòng)Java程序,原生Object會(huì)被篡改嗎?當(dāng)然是不會(huì)的!

因?yàn)镺bject類是Java的核心庫類,由BootstrapClassLoader加載,而自定義的java.lang.Object類應(yīng)該是由AppClassLoader來加載。

BootstrapClassLoader先于AppClassLoader進(jìn)行加載,根據(jù)上面的雙親委派模型的概念,我們可以知道,java.lang.Object類已經(jīng)被加載,并且AppClassLoader要加載類之前都要先給其父類過目,所以自己寫的野類是無法撼動(dòng)核心庫類的。

結(jié)論

雙親委派模型保證了Java程序的穩(wěn)定運(yùn)行,可以避免類的重復(fù)加載,也保證了 Java 的核心 API 不被篡改。

源碼分析

雙親委派模型的都集中在java.lang.ClassLoader 的 loadClass()中,相關(guān)代碼如下所示:

private final ClassLoader parent; protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// 首先,檢查請求的類是否已經(jīng)被加載過Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {//父加載器不為空,調(diào)用父加載器loadClass()方法處理if (parent != null) {c = parent.loadClass(name, false);} else {//父加載器為空,使用啟動(dòng)類加載器 BootstrapClassLoader 加載c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {//拋出異常說明父類加載器無法完成加載請求}if (c == null) {long t1 = System.nanoTime();//自己嘗試加載c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}//加入Java開發(fā)交流君樣:756584822一起吹水聊天if (resolve) {resolveClass(c);}return c;}}

反雙親委派模型

雙親委派模型是Java默認(rèn)的,假如我們不想用雙親委派,我們要怎么辦呢?
我們可以自定義一個(gè)類加載器,除了BootstrapClassLoader其他類加載器均由 Java 實(shí)現(xiàn)且全部繼承自java.lang.ClassLoader。如果我們要自定義自己的類加載器,很明顯需要繼承ClassLoader。

從上面的源碼我們知道,雙親委派模型的都集中在java.lang.ClassLoader 的 loadClass()中,如果想打破雙親委派模型則需要重寫 loadClass() 方法。

如果我們不想打破雙親委派模型,就重寫 ClassLoader類中的 findClass()方法即可,無法被父類加載器加載的類最終會(huì)通過這個(gè)方法被加載。
最后,祝大家早日學(xué)有所成,拿到滿意offer

總結(jié)

以上是生活随笔為你收集整理的2021年度最全面JVM虚拟机,类加载过程与类加载器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。