【JVM】- 類加載與字節碼結構3

類加載階段

1. 加載

  • 加載:將類的字節碼載入方法區中,內部采用C++的instanceKlass描述java類。
  • 如果這個類的父類還沒加載,則先加載父類
  • 加載和鏈接可能是交替運行的

在這里插入圖片描述

  1. 通過全限定名獲取字節碼
    • 從文件系統(.class 文件)、JAR 包、網絡、動態代理生成等途徑讀取二進制數據。
  2. 將字節碼解析為方法區的運行時數據結構
    • 在方法區(元空間)存儲類的靜態結構(如類名、字段、方法、父類、接口等)。
  3. 在堆中生成 Class 對象
    • 創建一個 java.lang.Class 實例,作為方法區數據的訪問入口。

2. 鏈接

  1. 驗證:驗證類是否符合JVM規范(安全性檢查)
  2. 準備:為static變量分配空間,設置默認值
    • static變量分配空間和賦值是兩個步驟,分配空間在準備階段完成,賦值在初始化階段完成。
      • 如果static變量是final基本類型以及字符串常量:編譯階段就確定了,賦值在準備階段完成
      • 如果static變量是final的,但是屬于引用類型,賦值也會在初始化階段完成
  3. 解析:將常量池中的符號引用解析為直接引用。(用符號描述目標轉變為用他們在內存中的地址描述他們)

3. 初始化

<cint>()V方法

初始化即調用 <cint>()V方法,虛擬機會保證這個類的構造方法的線程安全

發生的時機

類的初始化是懶惰的。

  • main方法所在的類,優先被初始化
  • 首次訪問這個類的靜態變量或靜態方法
  • 子類初始化時,如果父類還沒初始化,會先初始化父類
  • 子類訪問父類的靜態變量,只會觸發父類的初始化。
  • 執行Class.forName
  • new會導致初始化

不會導致初始化:

  • 訪問類的static final靜態常量(基本類型和字符串),不會觸發初始化
  • 類對象.class不會觸發初始化
  • 創建該類的數組不會觸發初始化
  • 類加載器的loadClass方法,不會觸發初始化
  • Class.forName的參數2為false時,不會觸發初始化
public class Load01 {public static void main(String[] args) {System.out.println(E.a); // 不會被初始化(基本類型)System.out.println(E.b); // 不會被初始化(字符串)System.out.println(E.c); // 會被初始化(包裝類型)}
}
class E {public static final int a = 10;public static final String b = "hello";public static final Integer c = 20;static {System.out.println("init E");}
}

懶惰初始化單例模式

public class Load02 {public static void main(String[] args) {Singleton.test();System.out.println(Singleton.getInstance()); // 懶漢式,只有調用getInstance()方法時,才會加載內部的LazyHolder}
}
class Singleton {// 私有構造方法private Singleton(){}public static void test() {System.out.println("test");}private static class LazyHolder {private static Singleton SINGLETON = new Singleton();static {System.out.println("LazyHolder init");}}public static Singleton getInstance() {return LazyHolder.SINGLETON;}
}

類加載器

名稱加載哪的類說明
Bootstrap ClassLoaderJAVA_HOME/jre/lib無法直接訪問
Extension ClassLoaderJAVA_HOME/jre/lib/ext上級為Bootstrap
Application ClassLoaderclasspath上級為Extension
自定義類加載器自定義上級為Applicaiton

啟動類加載器

啟動類加載器是由C++程序編寫的,不能直接通過java代碼訪問,如果打印出來的是null,說明是啟動類加載器。

public class Load03 {public static void main(String[] args) throws ClassNotFoundException {Class<?> aClass = Class.forName("pers.xiaolin.jvm.load.F");System.out.println(aClass.getClassLoader()); // null}
}
public class F {static {System.out.println("bootstarp F init");}
}

使用java -Xbootclasspath/a:. pers.xiaolin.jvm.load.Load03將這個類加入bootclasspath之后,輸出null,說明是啟動類加載器加載的這個類

  • java -Xbootclasspath:<new bootclasspath>
  • java -Xbootclasspath/a:<追加路徑>
  • java -Xbootclasspath/p:<追加路徑>

應用程序類加載器

public class Load04 {public static void main(String[] args) throws ClassNotFoundException {Class<?> aClass = Class.forName("pers.xiaolin.jvm.load.G");System.out.println(aClass.getClassLoader()); // sun.misc.Launcher$AppClassLoader@18b4aac2(應用程序類加載器)}
}public class G {static {System.out.println("G init");}
}

雙親委派模式

雙親委派:調用類加載器loadClass方法時,查找類的規則。

每次都去上級類加載器中找,如果找到了就加載,如果上級沒找到,才由本級的類加載器進行加載。

執行流程

protected Class<?> loadClass(String name, boolean resolve) {synchronized (getClassLoadingLock(name)) {// 1. 檢查類是否已加載Class<?> c = findLoadedClass(name);if (c == null) {try {// 2. 委托父加載器加載if (parent != null) {c = parent.loadClass(name, false);} else { // parent == null,說明到了啟動類加載器c = findBootstrapClassOrNull(name); // 父加載器是 Bootstrap}} catch (ClassNotFoundException e) {}// 3. 父加載器未找到,則自行加載if (c == null) {c = findClass(name);}}return c;}
}

核心作用

  1. 避免類重復加載:確保一個類在JVM中只存在一份(由最頂層的類加載器優先加載),如果用戶自己定義了一個java.lang.String,那么這個類并不會被加載,而是由最頂層的Bootstrap加載核心的String類
  2. 保證安全性:防止核心類被篡改,通過優先委托父類加載器,確保核心類由可信源加載
  3. 分工明確:Bootstrap(加載JVM核心類)、Extension(加載擴展功能)、Application(加載用戶代碼)

破壞雙親委派場景

雙親委派并非強制約束,有些情況也會破壞它,否則有些類他是找不到的。

  1. 核心庫(JDBC)需要調用用戶實現的驅動(mysql-connector-java)

通過Thread.currentThread().getContextClassLoader()獲取線程上下文加載器(通常是Application ClassLoader),直接加載用戶類。

  1. 不同模塊可能需要隔離或共享類

自定義類加載器,按照需要選擇是否委派父加載器

  1. 熱部署:動態替換已經加載的類

自定義類加載器直接重新加載類,不委派父類加載器

自定義類加載器

使用場景

  1. 需要加載非classpath路徑中的類文件
  2. 框架設計:都是通過接口來實現,希望解耦
  3. tomcat容器:這些類有多種版本,不同版本的類希望能隔離。

步驟

  1. 繼承ClassLoader父類
  2. 要遵守雙親委派機制,重寫findClass方法(注意不是重寫loadClass方法,否則不會走雙親委派)
  3. 讀取類文件中的字節碼
  4. 調用父類的defineClass方法來加載類
  5. 使用者調用該類加載器的loadClass方法
public class Load05 {public static void main(String[] args) throws ClassNotFoundException {MyClassLoader classLoader = new MyClassLoader();// 5. 使用者調用該類加載器的loadClass方法Class<?> c1 = classLoader.loadClass("MapImpl1");Class<?> c2 = classLoader.loadClass("MapImpl1");System.out.println(c1 == c2); // trueMyClassLoader classLoader2 = new MyClassLoader();Class<?> c3 = classLoader2.loadClass("MapImpl1");System.out.println(c1 == c3); // false }
}// 1. 繼承ClassLoader父類
class MyClassLoader extends ClassLoader {// 2. 重寫findClass方法@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException { // name就是類名稱String path = "d:\\myclasspath" + name + ".class";try {ByteArrayOutputStream os = new ByteArrayOutputStream();Files.copy(Paths.get(path), os);// 3. 讀取類文件中的字節碼byte[] bytes = os.toByteArray();// 4. 調用父類的defineClass方法來加載類return defineClass(name, bytes, 0, bytes.length); // byte[] -> *.class} catch (IOException e) {e.printStackTrace();throw new ClassNotFoundException("類文件未找到", e);}}
}

唯一確定類的方式應該是:包名類名類加載器相同

運行期優化

逃逸分析

現象】:循環內創建了1000個Object對象,但未被外部引用。
JIT優化】:JIT編譯器(尤其是C2編譯器)會通過逃逸分析(Escape Analysis)發現這些對象是方法局部作用域且未逃逸(即不會被其他線程或方法訪問),因此會直接優化掉對象分配。實際運行時,這些對象可能根本不會在堆上分配內存,而是被替換為標量或直接在寄存器中處理。

public class JIT01 {public static void main(String[] args) {for(int i = 0; i < 200; ++i) {long start = System.nanoTime();for(int j = 0; j < 1000; ++j) {new Object();}long end = System.nanoTime();System.out.printf("%d\t%d\n", i, (end - start));}}
}

在運行期間,虛擬機會對這段代碼進行優化。
JVM將執行狀態分為5個層次:

  • 0層:解釋執行
  • 1層:使用C1即時編譯器編譯執行(不帶profiling)
  • 2層:使用C1即時編譯器編譯執行(帶基本的profiling)
  • 3層:使用C1即時編譯器編譯執行(帶完全的profiling)
  • 4層:使用C2即時編譯器編譯執行

profiling是在運行過程中收集一些程序執行狀態的數據(方法的調用次數、循環次數…)
解釋器:將字節碼解釋成機器碼,下次遇到相同的字節碼,仍然會執行重復的解釋
即時編譯器(JIT):就是把反復執行的代碼編譯成機器碼,存儲在Code Cache,下次再遇到相同的代碼,直接執行,無需編譯。
解釋器是將字節碼解釋為爭對所有平臺都通用的機器碼;JIT會根據平臺類型,生成平臺特定的機器碼。
對于占據大部分不常用的代碼,無需耗費時間將其編譯成機器碼,直接采取解釋執行的方式;對于僅占用小部分的熱點代碼, 可以將其編譯成機器碼。(運行效率:Iterpreter < C1 < C2

方法內聯

例子1

private static int square(final int i) {return i * i;
}
System.out.println(square(9));

如果發現square是熱點方法,并且長度不會太長時,就會進行內聯(把方法內的代碼拷貝到調用位置)

System.out.println(9 * 9);

例子2

public class JIT02 {int[] elements = randomInts(1_000);int sum = 0;void doSum(int x) {sum += x;}public void test() {for(int i = 0; i < elements.length(); ++i) {doSum(elements[i]);}}
}

方法內聯也會導致成員變量讀取時的優化操作。

上邊的test()方法,會被優化成:

public void test() {// elements.length首次讀取會緩存起來 ==> int[] localfor(int i = 0; i < elements.length(); ++i) { // 后續999次,求長度(不需要訪問成員變量,直接從loca中取)sum += elements; // 后續1000次,取下標(不需要訪問成員變量,直接從loca中取)}
}

反射優化

1. 初始階段:解釋執行(未優化)

  • 前幾次調用(約0~5次)
    • Method.invoke 會走完整的 Java反射邏輯,包括:
      • 方法權限檢查(AccessibleObject)。
      • 參數解包(Object[] 轉原始類型)。
      • 動態方法解析(通過JNI調用底層方法)。
    • 性能極差:單次調用耗時可能是直接調用的 20~100倍(微秒級 vs 納秒級)。

2. 中間階段:JIT初步優化(方法內聯+膨脹閾值)

  • 調用次數達到閾值(約5~15次)
    JIT編譯器(C2)開始介入優化:
    • 方法內聯(Inlining)
      • 如果 foo() 是簡單方法(如本例的 System.out.println),JIT會嘗試內聯它。
      • Method.invoke 本身 無法直接內聯(因反射調用是動態的)。
    • 膨脹閾值(Inflation Threshold)
      • JVM默認設置 -XX:InflationThreshold=N(通常N=15),當反射調用超過此閾值時,JVM會生成 動態字節碼存根(Native Method Accessor),替代原始反射邏輯。
      • 優化效果
        調用從JNI方式轉為直接調用生成的存根代碼,性能提升約 5~10倍

3. 最終階段:動態字節碼生成(最高效)

  • 超過膨脹閾值(如15次后)
    JVM為 foo.invoke() 生成專用的 字節碼訪問器(GeneratedMethodAccessor)
  // 偽代碼:生成的動態類class GeneratedMethodAccessor1 extends MethodAccessor {public Object invoke(Object obj, Object[] args) {Reflect01.foo(); // 直接調用目標方法,繞過反射檢查!return null;}}
  • 優化點
    • 完全跳過權限檢查參數解包(因JVM確認方法簽名固定)。
    • 通過字節碼直接調用 foo(),性能接近 直接方法調用(納秒級)。

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

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

相關文章

Qt藍圖式技能編輯器狀態機模塊設計與實現

設計概述 這個模塊是一個基于Qt的藍圖式技能編輯器狀態機&#xff0c;主要用于游戲開發中的技能狀態管理。核心功能包括&#xff1a; 狀態節點&#xff08;開始、結束、普通狀態&#xff09;的可視化 狀態間連線的繪制與管理 狀態轉換邏輯的可視化編輯 動作選擇與配置 核…

Unity AR識別物體的內容語音讀取+使用說明功能

因之前一直在開發項目&#xff0c;斷斷續續寫了一點博客&#xff0c;最后統一寫了一下博客記錄學習內容。 可以看到我的工作一直在進行。 目錄 一、識別內容語音讀取 二、點擊齒輪按鈕彈出使用說明界面 開發步驟 1. 創建齒輪按鈕 UI 2. 創建使用說明面板 UI 3. 編寫控制…

Unable to start embedded Tomcat

通常是由于xml文件配置錯誤導致 1. mapper 指向錯誤 <resultMap id"Waybill" type"c.Waybill"> 2. 字段類型錯誤 <result column"wstatus" property"stus" javaType"TINYINT"/>TINYINT 是數據庫類型<resu…

Mac電腦 充電限制保護工具 AlDente Pro

AlDente Pro一款充電限制保護工具&#xff0c;是可以限制最大充電百分比來保護電池的工具。 鋰離子和聚合物電池&#xff08;如 MacBook 中的電池&#xff09;在40&#xff05; 至 80&#xff05; 之間運行時&#xff0c;使用壽命最長。 始終將電池電量保持在 100&#xff05…

KungfuBot——基于物理約束和自適應運動追蹤的人形全身控制PBHC,用于學習打拳或跳舞(即RL下的動作模仿和運控)

前言 昨天618&#xff0c;我司「七月在線」同事朝陽為主力&#xff0c;我打雜&#xff0c;折騰了整整一天&#xff0c;終于可以通過VR搖操宇樹G1了——當然&#xff0c;搖操是為了做訓練數據的采集&#xff0c;從而方便 下一步的模型(策略)訓練&#xff0c;最終實現機器人自主…

Kafka多副本機制

副本和副本因子 Kafka 會為每個 Partition 創建多個副本。這些副本分布在不同的 Broker 上。副本確保了數據的冗余存儲&#xff0c;即使某個 Broker 宕機或失效&#xff0c;其他副本可以繼續提供服務。 副本因子指的是每個 Partition 有多少個副本。副本因子的設置決定了一個…

Vue3類似百度風格搜索框組件

Vue3百度風格搜索框組件&#xff0c;使用vue3進行設計&#xff0c;亦有vue3TS的版本。 vue3組件如下&#xff1a; <template><!-- 搜索組件容器 --><div class"search-container"><!-- 百度Logo - 新樣式 --><div class"logo-conta…

智凈未來:華為智選IAM以科技巧思優化家庭健康飲水體驗

在中國家庭中&#xff0c;凈水器早已成為廚房標配&#xff0c;但傳統凈水設備的使用體驗卻遠未達到理想狀態。根據《2023年中國家庭凈水器使用調研報告》顯示&#xff0c;超過65%的用戶對傳統凈水器存在不滿&#xff0c;主要痛點集中在功能單一、操作復雜、維護麻煩、噪音大、廢…

細說STM32單片機SPI-Flash芯片的FatFS移植

目錄 一、SPI-Flash芯片硬件電路 二、CubeMX項目基礎設置 1、RCC、SYS、Code Generator、USART6、NVIC 2、RTC 3、SPI2 4、GPIO 5、FatFS模式 6、FatFS參數設置概述 &#xff08;1&#xff09;Version組 &#xff08;2&#xff09;Function Parameters組 1&#x…

ubuntu 22.04 安裝部署logstash 7.10.0詳細教程

安裝部署logstash 7.10.0詳細教程 一、下載并安裝二、新建配置文件三、賦權文件權限四、檢測文件grok語法是否異常五、啟動服務六、安裝啟動常見問題 【背景】 整個elk安裝是基于ubuntu 22.04和jdk 11環境。logstash采用 *.deb方式安裝&#xff0c;需要服務器能聯網。ubuntu 22…

JVM對象創建與內存分配機制深度剖析

對象創建的主要流程 類加載檢查 在創建對象之前&#xff0c;JVM 首先會檢查該類是否已經加載、解析并初始化&#xff1a; 如果沒有&#xff0c;則會通過類加載機制加載類元信息&#xff08;Class Metadata&#xff09;到方法區。 這個過程包括&#xff1a;加載&#xff08;load…

Navicat 技術指引 | TiDB 的 AI 查詢交互功能

目前&#xff0c;Navicat 兩款工具支持對 TiDB 數據庫的管理開發功能&#xff1a;一款是旗艦款 Navicat Premium&#xff0c;另一款是其輕量化功能的 Navicat Premium Lite&#xff08;官方輕量級免費版&#xff09;。Navicat 自版本 17.1 開始支持 TiDB 7。它支持的系統有 Win…

以list為輸入條件,查詢數據庫表,java中的mapper層和mybatis層應該怎么寫?

根據一個 List 中的兩個字段 rangeCode 和 unitcd&#xff0c;查詢數據庫表 model_engineering_spatial_unit。這個需求在 Java MyBatis 項目中非常常見&#xff0c;下面我將為你詳細寫出 Mapper 接口&#xff08;Java&#xff09; 和 MyBatis XML 映射文件 的寫法。 ? 前提…

pyspark 創建DataFrame

from pyspark.sql import SparkSession from pyspark.sql import StructType, StructField, IntegerType,StringType spark SparkSession.builder.appName(test).getOrCreate() 1、 從列表中創建DataFrame data [(1,"alice"),(2,Blob),(3,Charlie)] columns [&qu…

Vim:從入門到進階的高效文本編輯器之旅

目錄 一、Vim簡介 二、Vim的基礎操作 2.1 進入和退出Vim 2.2 Vim的三種模式 2.3 基礎移動 三、Vim的高效編輯技巧 3.1 文本編輯 3.2 文本刪除與修改 3.3 復制與粘貼 四、Vim的進階使用 4.1 搜索與替換 4.2 寄存器與宏 4.3 插件與配置 五、結語 在編程界&#xff0…

Docker基礎理論與阿里云Linux服務器安裝指南

文章目錄 一、Docker核心概念二、阿里云環境準備三、Docker安裝與配置四、核心容器部署示例五、開發環境容器化六、運維管理技巧七、安全加固措施 一、Docker核心概念 容器化本質&#xff1a; 輕量級虛擬化技術&#xff0c;共享主機內核進程級隔離&#xff08;cgroups/namespac…

c#使用筆記之try catch和throw

一、try catch 一種報錯的捕捉機制&#xff0c;try塊里運行的代碼出現錯誤的時候就會去執行catch塊所以一般catch塊里都是把錯誤打印出來或者保存到log日志里&#xff1b; 1.1、具體使用 catch可以用&#xff08;&#xff09;來選擇捕捉什么類型的錯誤&#xff0c;一般用Exc…

(新手友好)MySQL學習筆記(9):索引(常見索引類型,查找結構的發展(二分查找法,二叉搜索樹,平衡二叉樹,B樹,B+樹))

目錄 索引 常見索引類型 B樹 二分查找法 二叉搜索樹和平衡二叉樹 B樹和B樹 索引 index&#xff0c;是存儲引擎用于快速找到數據的一種數據結構。 MySQL默認使用InnoDB存儲引擎&#xff0c;該存儲引擎是最重要&#xff0c;使用最廣泛的&#xff0c;除非有非常特別的原因需要使用…

進程間通信1(匿名管道)Linux

1 進程間通信的必要性 首先要明確進程間是相互獨立的&#xff08;獨享一份虛擬地址空間&#xff0c;頁表&#xff0c;資源&#xff09;&#xff0c;那怎么樣才能使得兩個進程間實現資源的發送&#xff1f;所以&#xff0c;兩個進程一定需要看到同一份資源&#xff0c;并且?個…

CAN2.0、DoIP、CAN-FD汽車協議詳解與應用

一、CAN2.0 協議詳解與應用示例 1. 技術原理與特性 協議架構&#xff1a;基于 ISO 11898 標準&#xff0c;采用載波監聽多路訪問 / 沖突檢測&#xff08;CSMA/CD&#xff09;機制&#xff0c;支持 11 位&#xff08;CAN2.0A&#xff09;或 29 位&#xff08;CAN2.0B&#xff…