JVM類加載過程詳解

文章目錄

  • 前言
  • 1.加載
  • 2.鏈接
    • 驗證
      • 文件格式驗證
      • 元數據驗證
      • 字節碼驗證
      • 符號引用驗證
    • 準備
    • 解析
  • 3.初始化
  • 4.類卸載


前言

類從被加載到虛擬機內存中開始到卸載出內存為止,它的整個生命周期可以簡單概括為 7 個階段:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)。其中,驗證、準備和解析這三個階段可以統稱為連接(Linking)。
這 7 個階段的順序如下圖所示:
在這里插入圖片描述
注意:系統加載 Class 類型的文件主要三步:加載->連接->初始化。連接過程又可分為三步:驗證->準備->解析

1.加載

類加載過程的第一步,主要完成下面 3 件事情:

  1. 通過全類名獲取定義此類的二進制字節流。
  2. 將字節流所代表的靜態存儲結構轉換為方法區的運行時數據結構。
  3. 在內存中生成一個代表該類的 Class 對象,作為方法區這些數據的訪問入口。

加載這一步主要是通過我們后面要講到的 類加載器 完成的。類加載器有很多種,當我們想要加載一個類的時候,具體是哪個類加載器加載由 雙親委派模型 決定。

每個 Java 類都有一個引用指向加載它的 ClassLoader。不過,數組類不是通過 ClassLoader 創建的,而是 JVM 在需要的時候自動創建的,數組類通過getClassLoader()方法獲取 ClassLoader 的時候和該數組的元素類型的 ClassLoader 是一致的。

一個非數組類的加載階段(加載階段獲取類的二進制字節流的動作)是可控性最強的階段,這一步我們可以去完成還可以自定義類加載器去控制字節流的獲取方式(重寫一個類加載器的 loadClass() 方法)。

加載階段與連接階段的部分動作(如一部分字節碼文件格式驗證動作)是交叉進行的,加載階段尚未結束,連接階段可能就已經開始了。

將類的字節碼載入方法區中,內部采用 C++ 的 instanceKlass 描述 java 類,它的重要 field 有:

  • _java_mirror 即 java 的類鏡像,例如對 String 來說,就是 String.class,作用是把 klass 暴露給 java 使用
  • _super 即父類
  • _fields 即成員變量
  • _methods 即方法
  • _constants 即常量池
  • _class_loader 即類加載器
  • _vtable 虛方法表
  • _itable 接口方法表
    注意
  • instanceKlass 這樣的【元數據】是存儲在方法區(1.8 后的元空間內),但 _java_mirror是存儲在堆中
  • 可以通過 HSDB 工具查看
    在這里插入圖片描述

2.鏈接

驗證

驗證是連接階段的第一步,這一階段的目的是確保 Class 文件的字節流中包含的信息符合《Java 虛擬機規范》的全部約束要求,保證這些信息被當作代碼運行后不會危害虛擬機自身的安全。

驗證階段這一步在整個類加載過程中耗費的資源還是相對較多的,但很有必要,可以有效防止惡意代碼的執行。任何時候,程序安全都是第一位。

不過,驗證階段也不是必須要執行的階段。可以在生產環境的實施階段就可以考慮使用 -Xverify:none 參數來關閉大部分的類驗證措施,以縮短虛擬機類加載的時間。

但是需要注意的是 -Xverify:none 和 -noverify 在 JDK 13 中被標記為 deprecated ,在未來版本的 JDK 中可能會被移除。

驗證階段主要由四個檢驗階段組成:

  1. 文件格式驗證(Class 文件格式檢查)
  2. 元數據驗證(字節碼語義檢查)
  3. 字節碼驗證(程序語義檢查)
  4. 符號引用驗證(類的正確性檢查)
    在這里插入圖片描述

文件格式驗證

文件格式驗證這一階段是基于該類的二進制字節流進行的,主要目的是保證輸入的字節流能正確地解析并存儲于方法區之內,格式上符合描述一個 Java 類型信息的要求。

除了這一階段之外,其余三個驗證階段都是基于方法區的存儲結構上進行的,不會再直接讀取、操作字節流了。

這一階段可能包括下面這些驗證點:

  • 是否以魔數0xCAFEBABE開頭。
  • 主、次版本號是否在當前Java虛擬機接受范圍之內。
  • 常量池的常量中是否有不被支持的常量類型(檢查常量tag標志)。
  • 指向常量的各種索引值中是否有指向不存在的常量或不符合類型的常量。
  • CONSTANT_Utf8_info型的常量中是否有不符合UTF-8編碼的數據。
  • Class文件中各個部分及文件本身是否有被刪除的或附加的其他信息

注意:方法區屬于是 JVM 運行時數據區域的一塊邏輯區域,是各個線程共享的內存區域。當虛擬機要使用一個類時,它需要讀取并解析 Class 文件獲取相關信息,再將信息存入到方法區。方法區會存儲已被虛擬機加載的 類信息、字段信息、方法信息、常量、靜態變量、即時編譯器編譯后的代碼緩存等數據

元數據驗證

第二階段是對字節碼描述的信息進行語義分析,以保證其描述的信息符合《Java語言規范》的要
求,這個階段可能包括的驗證點如下:

  • 這個類是否有父類(除了java.lang.Object之外,所有的類都應當有父類)。
  • 這個類的父類是否繼承了不允許被繼承的類(被final修飾的類)。
  • 如果這個類不是抽象類,是否實現了其父類或接口之中要求實現的所有方法。
  • 類中的字段、方法是否與父類產生矛盾(例如覆蓋了父類的final字段,或者出現不符合規則的方法重載,例如方法參數都一致,但返回值類型卻不同等)。
  • ……

字節碼驗證

第三階段是整個驗證過程中最復雜的一個階段,主要目的是通過數據流分析和控制流分析,確定程序語義是合法的、符合邏輯的。
在第二階段對元數據信息中的數據類型校驗完畢以后,這階段就要對類的方法體(Class文件中的Code屬性)進行校驗分析,保證被校驗類的方法在運行時不會做出危害虛擬機安全的行為,例如:

  • 保證任意時刻操作數棧的數據類型與指令代碼序列都能配合工作,例如不會出現類似于“在操作棧放置了一個int類型的數據,使用時卻按long類型來加載入本地變量表中”這樣的情況。
  • 保證任何跳轉指令都不會跳轉到方法體以外的字節碼指令上。
  • 保證方法體中的類型轉換總是有效的,例如可以把一個子類對象賦值給父類數據類型,這是安全的,但是把父類對象賦值給子類數據類型,甚至把對象賦值給與它毫無繼承關系、完全不相干的一個數據類型,則是危險和不合法的。
  • ……

符號引用驗證

符號引用驗證發生在類加載過程中的解析階段,具體點說是 JVM 將符號引用轉化為直接引用的時候(解析階段會介紹符號引用和直接引用)。
符號引用驗證的主要目的是確保解析階段能正常執行,如果無法通過符號引用驗證,JVM 會拋出異常,比如:

  • java.lang.IllegalAccessError:當類試圖訪問或修改它沒有權限訪問的字段,或調用它沒有權限訪問的方法時,拋出該異常。
  • java.lang.NoSuchFieldError:當類試圖訪問或修改一個指定的對象字段,而該對象不再包含該字段時,拋出該異常。
  • java.lang.NoSuchMethodError:當類試圖訪問一個指定的方法,而該方法不存在時,拋出該異常。
  • ……

準備

準備階段是正式為類變量分配內存并設置類變量初始值的階段,這些內存都將在方法區中分配。對于該階段有以下幾點需要注意:

  • 這時候進行內存分配的僅包括類變量( Class Variables ,即靜態變量,被 static 關鍵字修飾的變量,只與類相關,因此被稱為類變量),而不包括實例變量。實例變量會在對象實例化時隨著對象一塊分配在 Java 堆中。
  • 從概念上講,類變量所使用的內存都應當在 方法區 中進行分配。不過有一點需要注意的是:JDK 7 之前,HotSpot 使用永久代來實現方法區的時候,實現是完全符合這種邏輯概念的。 而在 JDK 7 及之后,HotSpot 已經把原本放在永久代的字符串常量池、靜態變量等移動到堆中,這個時候類變量則會隨著 Class 對象一起存放在 Java 堆中。
  • 這里所設置的初始值"通常情況"下是數據類型默認的零值(如 0、0L、null、false 等)。

比如我們定義了public static int value=111 ,那么 value 變量在準備階段的初始值就是 0 而不是 111(初始化階段才會賦值)。因為這時尚未開始執行任何Java方法,而把value賦值為123的putstatic指令是程序被編譯后,存放于類構造器()方法之中,所以把value賦值為123的動作要到類的初始化階段才會被執行。

特殊情況:如果類字段的字段屬性表中存在ConstantValue屬性,那在準備階段變量值就會被初始化為ConstantValue屬性所指定的初始值。
比如給 value 變量加上了 final 關鍵字public static final int value=111 ,那么準備階段 value 的值就被賦值為 111。
在這里插入圖片描述

解析

解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。 解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用限定符 7 類符號引用進行。

舉個例子:在程序執行方法時,系統需要明確知道這個方法所在的位置。Java 虛擬機為每個類都準備了一張方法表來存放類中所有的方法。當需要調用一個類的方法的時候,只要知道這個方法在方法表中的偏移量就可以直接調用該方法了。通過解析操作符號引用就可以直接轉變為目標方法在類中方法表的位置,從而使得方法可以被調用。

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

3.初始化

初始化階段是執行初始化方法 ()方法的過程,是類加載的最后一步,這一步 JVM 才開始真正執行類中定義的 Java 程序代碼(字節碼)。

注意: ()方法是編譯之后自動生成的。

對于 () 方法的調用,虛擬機會自己確保其在多線程環境中的安全性。因為 () 方法是帶鎖線程安全,所以在多線程環境下進行類初始化的話可能會引起多個線程阻塞,并且這種阻塞很難被發現。

發生的時機
概括得說,類初始化是【懶惰的】

  • main 方法所在的類,總會被首先初始化
  • 首次訪問這個類的靜態變量或靜態方法時,會引發初始化
  • 子類初始化,如果父類還沒初始化,會引發初始化
  • 子類訪問父類的靜態變量,只會觸發父類的初始化
  • Class.forName 會導致初始化
  • new 會導致初始化
  • 當一個接口中定義了 JDK8 新加入的默認方法(被 default 關鍵字修飾的接口方法)時,如果有這個接口的實現類發生了初始化,那該接口要在其之前被初始化。

不會導致類初始化的情況

  • 訪問類的 static final 靜態常量(基本類型和字符串)不會觸發初始化
  • 類對象.class 不會觸發初始化
  • 創建該類的數組不會觸發初始化
  • 類加載器的 loadClass 方法
  • Class.forName 的參數 2 為 false 時

4.類卸載

卸載類即該類的 Class 對象被 GC。
卸載類需要滿足 3 個要求:

  1. 該類的所有的實例對象都已被 GC,也就是說堆不存在該類的實例對象。
  2. 該類沒有在其他任何地方被引用
  3. 該類的類加載器的實例已被 GC

所以,在 JVM 生命周期內,由 jvm 自帶的類加載器加載的類是不會被卸載的。但是由我們自定義的類加載器加載的類是可能被卸載的。

只要想通一點就好了,JDK 自帶的 BootstrapClassLoader, ExtClassLoader, AppClassLoader 負責加載 JDK 提供的類,所以它們(類加載器的實例)肯定不會被回收。而我們自定義的類加載器的實例是可以被回收的,所以使用我們自定義加載器加載的類是可以被卸載掉的。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/899174.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/899174.shtml
英文地址,請注明出處:http://en.pswp.cn/news/899174.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

el-select 可搜索下拉框 在ios、ipad 無法喚出鍵盤,造成無法輸入

下一篇:el-select 可搜索下拉框,選中選項后,希望立即失去焦點,收起鍵盤,執行其他邏輯 【效果圖】:分組展示選項 >【去界面操作體驗】 首先,通過 夸克瀏覽器的搜索: el-select 在 ipad 輸入框…

前端框架的定制化:滿足項目獨特需求

hello寶子們...我們是艾斯視覺擅長ui設計和前端數字孿生、大數據、三維建模、三維動畫10年經驗!希望我的分享能幫助到您!如需幫助可以評論關注私信我們一起探討!致敬感謝感恩! 在數字化轉型浪潮中,前端技術體系已成為企業構建數字競爭力的核心戰場。當標準化前端框架…

Flutter網絡請求封裝:高效、靈活、易用的Dio工具類

在Flutter開發中,網絡請求是必不可少的功能。為了簡化代碼、提高開發效率,我們通常會封裝一個網絡請求工具類。本文基于Dio庫,詳細介紹如何封裝一個高效、靈活、易用的網絡請求工具類,支持以下功能: 單例模式&#xf…

MyBatis-Plus(SpringBoot版)學習第一講:簡介入門案例

目錄 1. MyBatis-Plus簡介 1.1 簡介 1.2 特性 1.3 支持的數據庫 1.4 框架結構 1.5 代碼及文檔地址 2. 入門案例 2.1 開發環境 2.2 創建數據庫及表 1. 創建表 2. 添加數據 2.3 創建SpringBoot工程 1. 初始化工程 2. 引入依賴 3. IDEA中安裝lombok插件 ?編輯 2.4 編…

k8s高可用集群安裝

一、安裝負載均衡器 k8s負載均衡器 官方指南 1、準備三臺機器 節點名稱IPmaster-1192.168.1.11master-2192.168.1.12master-3192.168.1.13 2、在這三臺機器分別安裝haproxy和keepalived作為負載均衡器 # 安裝haproxy sudo dnf install haproxy -y# 安裝Keepalived sudo yum …

【PPO】小白的強化學習算法筆記

參考視頻 零基礎學習強化學習算法:ppo 基礎概念 environment環境agent:智能體,玩游戲的你state:當前狀態,observation看到的部分,有的游戲只能看見state的一部分action:agent做出的動作reward…

Flink介紹與安裝

Apache Flink是一個在有界數據流和無界數據流上進行有狀態計算分布式處理引擎和框架。Flink 設計旨在所有常見的集群環境中運行,以任意規模和內存級速度執行計算。 一、主要特點和功能 1. 實時流處理: 低延遲: Flink 能夠以亞秒級的延遲處理數據流,非常…

深入解析 JVM 內存區域及核心概念

深入解析 JVM 內存區域及核心概念 Java 虛擬機(JVM)內部劃分了多個內存區域,每個區域存儲不同類型的數據并承擔不同的職責。本文將詳細介紹以下內容: 程序計數器:記錄當前線程正在執行的字節碼指令及其“行號”信息&a…

Java操作RabbitMQ

文章目錄 Spring集成RabbitMQ1. AMQP&SpringAMQP2. SpringBoot集成RabbitMQ3. 模型work模型 4.交換機Fanout交換機Direct交換機Topic交換機 5.聲明式隊列和交換機基于API聲明基于注解聲明 6.消息轉換器 Spring集成RabbitMQ 1. AMQP&SpringAMQP AMQP(高級消…

Kotlin泛型: 協變|逆變|不變

引言 無論java 通配符上限還是下限,都多少存在缺陷,要么存不安全,要么取不安全。而kotlin就解決這個問題。讓out 純輸出, 讓in純輸入。 java這塊知識: java泛型的協變、逆變和不變-CSDN博客 協變 生產者out T 協變…

【Excel使用技巧】某列保留固定字段或內容

目錄 ? 方法一:使用 Excel 公式提取 body 部分 🔍 解釋: ? 方法二:批量處理整列數據 🚨 注意事項 🚨 處理效果 我想保留Excel某一列的固定內容,比如原內容是: thread entry i…

C# System.Text.Encoding 使用詳解

總目錄 前言 在C#編程中,處理字符串和字節數組之間的轉換是一個常見的任務。System.Text.Encoding類及其派生類提供了豐富的功能,幫助開發者實現不同字符編碼之間的轉換。本文將詳細講解System.Text.Encoding類的使用方法,包括常用編碼的介紹…

Pre-flash和Main flash

在相機拍照過程中,Pre-flash(預閃光) 和 Main flash(主閃光) 是常見的兩種閃光燈使用模式,通常用于提高低光環境下的拍攝質量,尤其在自動曝光(AE)和自動對焦(…

Kafka 4.0 發布:KRaft 替代 Zookeeper、新一代重平衡協議、點對點消息模型、移除舊協議 API

KRaft 全面替代 ZooKeeper Apache Kafka 4.0 是一個重要的里程碑,標志著第一個完全無需 Apache ZooKeeper 運行的主要版本。 通過默認運行在 KRaft 模式下,Kafka 簡化了部署和管理,消除了維護單獨 ZooKeeper 集群的復雜性。 這一變化顯著降…

SFT實驗報告

大模型微調實驗報告* 實驗目標 梳理大模型微調方法,評估各種基座和微調方法的實驗效果。 基礎模型 \1.Llama \2.Qwen \3.Chatglm4 \4. 微調策略 LoRA系列 低秩適配(LoRA)的核心思想是凍結原始參數,通過低秩分解引入可訓…

LLM - R1 強化學習 DRPO 策略優化 DAPO 與 Dr. GRPO 算法 教程

歡迎關注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/146533892 在強化學習算法中,DAPO (Decoupled Clip and Dynamic Sampling Policy Optimization),通過解耦裁剪和動態采樣策…

美攝科技智能汽車視頻延遲攝影解決方案,開啟智能出行新視界

在智能汽車時代,車載影像技術正以前所未有的速度發展,成為提升駕乘體驗和滿足用戶多樣化需求的關鍵因素。美攝科技憑借其卓越的技術實力和創新精神,推出了智能汽車視頻延遲攝影解決方案,為智能汽車行業帶來了一場視覺盛宴。 一、…

[250325] Claude AI 現已支持網絡搜索功能!| ReactOS 0.4.15 發布!

目錄 Claude AI 現已支持網絡搜索功能!ReactOS 0.4.15 發布! Claude AI 現已支持網絡搜索功能! 近日,Anthropic 公司宣布,其 AI 助手 Claude 現在可以進行網絡搜索,為用戶提供更及時、更相關的回復。這項新…

代碼規范之Variable Names變量名

代碼規范之Variable Names變量名 golang中 官方文檔:https://go.dev/wiki/CodeReviewComments#variable-names Variable names in Go should be short rather than long. This is especially true for local variables with limited scope. Prefer c to lineCoun…

Mybatis_plus

前言 Mybatis_plus 是在 mybatis 的基礎上進行了增強,在 MyBatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生。本文章只做簡單的使用介紹,更加詳細的內容大家可以參考官網。 下面是mybatis_plus 官網地址: mybatis_plu…