Java中什么是類加載及類加載的過程
類加載指的是把類加載到 JVM 中。把二進(jìn)制流存儲到內(nèi)存中,之后經(jīng)過一番解析、處理轉(zhuǎn)化成可用的 class 類
二進(jìn)制流可以來源于 class 文件,或通過字節(jié)碼工具生成的字節(jié)碼或來自于網(wǎng)絡(luò)。只要符合格式的二進(jìn)制流,JVM 來者不拒。
虛擬機(jī)遇到?條 new 指令時,?先將去檢查這個指令的參數(shù)是否能在常量池中定位到這個類的符號引?,并且檢查這個符號引?代表的類是否已被加載過、解析和初始化過。如果沒有,那必須先執(zhí)?相應(yīng)的類加載過程。類加載過程包括了加載、連接、初始化三個階段,其中連接還可以分為驗(yàn)證、準(zhǔn)備、解析
加載
將二進(jìn)制流讀入內(nèi)存中,生成一個 Class 對象
在加載階段,虛擬機(jī)需要完成以下三件事情:
通過一個類的全限定名來獲取其定義的二進(jìn)制字節(jié)流。
將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu)。
在Java堆中生成一個代表這個類的java.lang.Class對象,作為對方法區(qū)中這些數(shù)據(jù)的訪問入口。
這個階段既可以使用系統(tǒng)提供的類加載器來完成加載,也可以自定義自己的類加載器來完成加載。
驗(yàn)證
確保Class文件的字節(jié)流中包含的信息符合JVM規(guī)范,保證在運(yùn)行后不會危害虛擬機(jī)自身的安全。即安全性檢查,主要包括四種驗(yàn)證:
文件格式驗(yàn)證: 驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范;例如: 是否以0xCAFEBABE開頭、主次版本號是否在當(dāng)前虛擬機(jī)的處理范圍之內(nèi)、常量池中的常量是否有不被支持的類型。
元數(shù)據(jù)驗(yàn)證:: 對字節(jié)碼描述的信息進(jìn)行語義分析(注意: 對比javac編譯階段的語義分析),以保證其描述的信息符合Java語言規(guī)范的要求;例如: 這個類是否有父類,除了java.lang.Object之外。
字節(jié)碼驗(yàn)證:通過數(shù)據(jù)流和控制流分析,確定程序語義是合法的、符合邏輯的。
符號引用驗(yàn)證:確保解析動作能正確執(zhí)行
驗(yàn)證階段是非常重要的,但不是必須的,它對程序運(yùn)行期沒有影響,如果所引用的類經(jīng)過反復(fù)驗(yàn)證,那么可以考慮采用-Xverifynone參數(shù)來關(guān)閉大部分的類驗(yàn)證措施,以縮短虛擬機(jī)類加載的時間。
準(zhǔn)備
準(zhǔn)備階段是正式為static 變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中分配。
static變量在分配空間和賦值是在兩個階段完成的。分配空間在準(zhǔn)備階段完成,賦值在初始化階段完成。也就是說這里給類變量設(shè)置初始值,設(shè)置的是數(shù)據(jù)類型默認(rèn)的零值(如0、0L、null、false等)
如果 static 變量是 ?nal 的基本類型,以及字符串常量,那么編譯階段值就確定了,賦值在準(zhǔn)備階段完成
如果 static 變量是 ?nal 的,但屬于引用類型,那么賦值會在初始化階段完成
解析
將常量池內(nèi)的符號引用替換為直接引用的過程。符號引用用于描述目標(biāo),直接引用直接指向目標(biāo)的地址。
未解析時,常量池中的看到的對象僅是符號,未真正的存在于內(nèi)存中
解析以后,會將常量池中的符號引用解析為直接引用
初始化
初始化階段會執(zhí)行cinit方法來為 類變量static變量 賦上定義的值并執(zhí)行類中的靜態(tài)代碼塊;這里的賦值才是代碼里面的賦值,準(zhǔn)備階段只是設(shè)置初始值占個坑。
在Java中對類變量進(jìn)行初始值設(shè)定有兩種方式:
聲明類變量是指定初始值
使用靜態(tài)代碼塊為類變量指定初始值
何時進(jìn)行類加載?
定義了main的類,啟動main方法時該類會被加載
創(chuàng)建類的實(shí)例,即new對象的時候
訪問類的靜態(tài)方法
訪問類的靜態(tài)變量
反射 Class.forName()
JVM初始化步驟?
假如這個類還沒有被加載和連接,則程序先加載并連接該類
假如該類的直接父類還沒有被初始化,則先初始化其直接父類
假如類中有初始化語句,則系統(tǒng)依次執(zhí)行這些初始化語句
初始化發(fā)生的時機(jī)?
概括得說,類初始化是【懶惰的】,只有當(dāng)對類的主動使用的時候才會導(dǎo)致類的初始化
main 方法所在的類,總會被首先初始化
首次訪問這個類的靜態(tài)變量或靜態(tài)方法時
子類初始化,如果父類還沒初始化,會引發(fā)父類初始化
子類訪問父類的靜態(tài)變量,只會觸發(fā)父類的初始化
Class.forName new 會導(dǎo)致初始化
不會導(dǎo)致類初始化的情況?
訪問類的 static final 靜態(tài)常量(基本類型和字符串)不會觸發(fā)初始化
類對象.class 不會觸發(fā)初始化
創(chuàng)建該類的數(shù)組不會觸發(fā)初始化
類加載器的 loadClass 方法
Class.forName 的參數(shù) 2 為 false 時
cinit方法如果執(zhí)行失敗了怎么辦,這個類還能用嗎?
在Java類加載的過程中,cinit 方法實(shí)際上指的是類的靜態(tài)初始化方法,也就是類的靜態(tài)代碼塊或者靜態(tài)變量的初始化代碼。如果類的靜態(tài)初始化方法執(zhí)行失敗,通常會導(dǎo)致類的初始化失敗,這意味著這個類不能被正常使用。會拋出異常,如 ExceptionInInitializerError
在Java中,類的靜態(tài)初始化方法只會執(zhí)行一次,無論類被加載多少次,靜態(tài)初始化方法只會在首次加載類的時候執(zhí)行。因此,cinit 方法不會多次執(zhí)行。一旦類的靜態(tài)初始化方法執(zhí)行過,后續(xù)對同一個類的加載都不會再次觸發(fā)靜態(tài)初始化方法的執(zhí)行。這種機(jī)制確保了類的靜態(tài)初始化只會在需要的時候執(zhí)行一次,避免了不必要的開銷和重復(fù)操作。
分配內(nèi)存
在類加載后,接下來虛擬機(jī)將為新?對象分配內(nèi)存。
分配在哪?
主要就是根據(jù)JVM的分配機(jī)制:對象優(yōu)先分配Eden
- 先TLAB分配
- 再通過CAS在Eden區(qū)分配
- 大對象直接分配到老年代
TLAB:線程本地分配緩沖區(qū),為每?個線程預(yù)先在 Eden 區(qū)分配?塊?私有的緩存區(qū)域,JVM 在給線程中的對象分配內(nèi)存時,?先在 TLAB 分配,當(dāng)對象?于 TLAB 中的剩余內(nèi)存或 TLAB 的內(nèi)存已?盡時(或者未開啟TLAB),再采?上述的 CAS 進(jìn)?內(nèi)存分配。默認(rèn)情況TLAB僅占每個Eden區(qū)域的1%。它的主要目的是在多線程并發(fā)環(huán)境下需要進(jìn)行內(nèi)存分配的時候,減少線程之間對于內(nèi)存分配區(qū)域的競爭,加速內(nèi)存分配的速度。
為什么要CAS分配內(nèi)存?
多個并發(fā)執(zhí)行的線程需要創(chuàng)建對象、申請分配內(nèi)存的時候,有可能在 Java 堆的同一個位置申請,這時就需要對擬分配的內(nèi)存區(qū)域進(jìn)行加鎖或者采用 CAS 等操作,保證這個區(qū)域只能分配給一個線程。
JVM對象分配內(nèi)存如何保證線程安全
在JVM中,為對象分配內(nèi)存的過程需要確保線程安全,因?yàn)樵诙嗑€程環(huán)境下,多個線程可能會同時嘗試創(chuàng)建對象。為了保證內(nèi)存分配的線程安全性,JVM采用了以下幾種機(jī)制和技術(shù):
TLAB(Thread Local Allocation Buffer):
當(dāng)一個線程需要分配對象時,首先會嘗試在TLAB中進(jìn)行分配。如果TLAB有足夠的空間,分配過程就是線程安全的,因?yàn)闆]有其他線程訪問這個內(nèi)存塊。
不足:當(dāng)TLAB空間不足時,線程需要請求一個新的TLAB或者直接從共享堆中分配,這個過程需要一定的同步機(jī)制。
CAS(Compare-And-Swap)機(jī)制: 當(dāng)TLAB耗盡或在涉及到跨線程的堆內(nèi)存分配時,CAS有效避免了競爭條件。
分代收集: 雖然不是直接用于線程安全,但分代收集(年輕代、老年代、永久代/元空間)使得內(nèi)存管理更高效,減少了直接競爭的機(jī)會。
結(jié)合:TLAB一般對年輕代的內(nèi)存分配進(jìn)行優(yōu)化,更加局部化的內(nèi)存管理有助于線程安全。
通過運(yùn)用這些機(jī)制,JVM能夠在多線程環(huán)境下高效而安全地進(jìn)行內(nèi)存分配,并最大限度地減少同步操作帶來的性能損耗。這樣設(shè)計不僅提升了性能,也保證了對象內(nèi)存分配的安全性和一致性。
說說對象分配規(guī)則
在Java中,對象分配規(guī)則是關(guān)于如何為新對象分配內(nèi)存的一套規(guī)則,以確保內(nèi)存的有效使用和對象的正確初始化。以下是關(guān)于對象分配的主要規(guī)則:
- 內(nèi)存分配:新對象通常在堆內(nèi)存中分配內(nèi)存空間。
- 對象頭:在為對象分配內(nèi)存空間后,Java虛擬機(jī)會為對象分配一個對象頭。對象頭包含了一些關(guān)于對象的元信息,如對象的哈希碼、鎖狀態(tài)、垃圾回收信息等。
- 零值初始化:在對象內(nèi)存分配后,所有的成員變量會被初始化為零值。具體的零值取決于變量的數(shù)據(jù)類型。例如,整數(shù)類型會初始化為0,布爾類型會初始化為false,對象引用會初始化為null。
- 構(gòu)造函數(shù)調(diào)用:一旦對象內(nèi)存分配和零值初始化完成,Java虛擬機(jī)會調(diào)用對象的構(gòu)造函數(shù)。
- 對象引用:最后,new 關(guān)鍵字會返回對象的引用,將這個引用分配給一個變量,以便后續(xù)可以通過該變量訪問對象的屬性和方法。
- 垃圾回收管理:Java虛擬機(jī)會自動管理對象的內(nèi)存。如果對象不再被引用,它會被標(biāo)記為垃圾,并在適當(dāng)?shù)臅r機(jī)由垃圾回收器回收,釋放占用的內(nèi)存。
這些規(guī)則確保了對象在創(chuàng)建時的正確初始化和內(nèi)存管理。對于程序員來說,最重要的是編寫好構(gòu)造函數(shù)以確保對象在創(chuàng)建后具有合適的初始狀態(tài),并且不忘記在不再需要對象時將引用置為null,以便垃圾回收器能夠回收不再使用的對象。
何時進(jìn)行類卸載?
類的卸載條件很多,需要滿足以下三個條件,并且滿足了也不一定會被卸載:
該類所有的實(shí)例都已經(jīng)被回收,也就是堆中不存在該類的任何實(shí)例。
加載該類的 ClassLoader 已經(jīng)被回收。
該類對應(yīng)的 Class 對象沒有在任何地方被引用,也就無法在任何地方通過反射訪問該類方法。
可以通過 -Xnoclassgc 參數(shù)來控制是否對類進(jìn)行卸載。
Java虛擬機(jī)將結(jié)束生命周期的幾種情況?(什么情況會導(dǎo)致JVM退出)
- 正常程序終止: 當(dāng)程序執(zhí)行完main方法,包括所有非守護(hù)線程都終止時,JVM將正常退出。
- 調(diào)用System.exit(int status): 顯式調(diào)用System.exit()方法,以指定的狀態(tài)碼終止當(dāng)前運(yùn)行的Java虛擬機(jī)。
- 未捕獲的異?;蝈e誤: 如果某個線程拋出的異常沒有被捕獲,并且此異常傳播到了主線程,JVM可能會終止。
- Runtime.halt(int)或崩潰:
- 直接調(diào)用Runtime.halt()會立即停止Java進(jìn)程,類似于突然終止程序而不調(diào)用任何鉤子。
- JVM的致命錯誤(如內(nèi)存訪問違規(guī))也可能導(dǎo)致崩潰并退出。
- 外部命令強(qiáng)制關(guān)閉: 例如通過操作系統(tǒng)的任務(wù)管理器或者控制臺命令,如kill命令?;蛘卟僮飨到y(tǒng)出現(xiàn)錯誤而導(dǎo)致Java虛擬機(jī)進(jìn)程終止
到此這篇關(guān)于Java中什么是類加載?類加載的過程?的文章就介紹到這了,更多相關(guān)Java類加載內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot 整合druid數(shù)據(jù)庫密碼加密功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了springboot 整合druid數(shù)據(jù)庫密碼加密功能的實(shí)現(xiàn)代碼,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01SpringBoot實(shí)現(xiàn)分布式驗(yàn)證碼登錄方案小結(jié)
驗(yàn)證碼登錄作為一種有效的防護(hù)手段,可以防止惡意gongji、暴力pojie等,本文主要介紹了SpringBoot實(shí)現(xiàn)分布式驗(yàn)證碼登錄方案小結(jié),具有一定的參考價值,感興趣的可以了解一下2024-12-12Java遍歷輸出指定目錄、樹形結(jié)構(gòu)所有文件包括子目錄下的文件
這篇文章主要介紹了Java遍歷輸出指定目錄、樹形結(jié)構(gòu)下的所有文件包括子目錄中的文件,需要的朋友可以參考下2015-07-07SpringKafka消息發(fā)布之KafkaTemplate與事務(wù)支持功能
通過本文介紹的基本用法、序列化選項、事務(wù)支持、錯誤處理和性能優(yōu)化技術(shù),開發(fā)者可以構(gòu)建高效可靠的Kafka消息發(fā)布系統(tǒng),事務(wù)支持特性尤為重要,它確保了在分布式環(huán)境中的數(shù)據(jù)一致性,感興趣的朋友一起看看吧2025-04-04Spring?boot?運(yùn)用策略模式實(shí)現(xiàn)避免多次使用if的操作代碼
這篇文章主要介紹了Spring?boot?運(yùn)用策略模式實(shí)現(xiàn),避免多次使用if,使用策略模式后,新加一種支付策略時,只需要在策略枚舉中添加新加的策略信息,外加一個策略類即可,而不再需要添加新的if判斷,需要的朋友可以參考下2022-08-08