JVM虛擬機篇(一)深入理解JVM:組成部分、運行流程及程序計數器詳解

JVM虛擬機篇(一)深入理解JVM:組成部分、運行流程及程序計數器詳解

  • JVM虛擬機篇(一)深入理解JVM:組成部分、運行流程及程序計數器詳解
    • 一、引言
    • 二、JVM的組成部分
      • 2.1 類加載子系統
      • 2.2 運行時數據區
      • 2.3 執行引擎
      • 2.4 本地接口(Native Interface)
    • 三、JVM的運行流程
      • 3.1 類加載階段
      • 3.2 程序執行階段
    • 四、程序計數器詳解
      • 4.1 程序計數器的定義與作用
      • 4.2 程序計數器與字節碼執行
      • 4.3 程序計數器與異常處理
      • 4.4 程序計數器的特點與限制
    • 五、總結

)

JVM虛擬機篇(一)深入理解JVM:組成部分、運行流程及程序計數器詳解

一、引言

Java虛擬機(Java Virtual Machine,JVM)是Java語言能夠實現“一次編寫,到處運行”這一特性的關鍵所在。它如同一個幕后的魔法師,將Java代碼轉換為可以在不同操作系統和硬件平臺上執行的指令。深入了解JVM的組成結構、運行流程以及其中各個組件的功能,對于Java開發者來說至關重要。它不僅有助于我們編寫出更高質量、性能更優的代碼,還能在遇到問題時,更深入地進行分析和調試。接下來,我們就一起深入探究JVM的奧秘。

二、JVM的組成部分

在這里插入圖片描述
在這里插入圖片描述

2.1 類加載子系統

類加載子系統負責加載字節碼文件(.class文件)到JVM中。它主要包含以下幾個關鍵組件:

  • ClassLoader(類加載器):ClassLoader是類加載子系統的核心,它主要有三種類型:
    • 啟動類加載器(Bootstrap ClassLoader):它是JVM的內置類加載器,用C++編寫(在HotSpot虛擬機中),負責加載Java的核心類庫,比如java.lang包下的類。它是最頂層的類加載器,加載的路徑是Java安裝目錄下的lib目錄中被虛擬機認可的(按照文件名識別,如rt.jar等)類庫。
    • 擴展類加載器(Extension ClassLoader):由Java語言編寫,繼承自ClassLoader類。它負責加載Java的擴展類庫,加載路徑是Java安裝目錄下的lib/ext目錄或者由系統變量java.ext.dirs指定的路徑中的類庫。
    • 應用程序類加載器(Application ClassLoader):也稱為系統類加載器,同樣由Java語言編寫。它負責加載應用程序classpath路徑下的類庫,我們編寫的應用程序代碼基本都是由這個類加載器來加載的。
  • Class文件:Class文件是Java源文件經過編譯器編譯后生成的字節碼文件,它包含了類的元數據信息(如類名、父類名、接口名、字段信息、方法信息等)、常量池、字節碼指令等內容。這些信息是類加載子系統加載和解析的對象。
  • 運行時數據區:類加載子系統將Class文件加載到JVM后,會在運行時數據區中為類分配相應的內存空間,存儲類的相關信息,比如在方法區中存儲類的元數據,在堆中分配對象實例(如果該類有對象被創建的話)。

2.2 運行時數據區

運行時數據區是JVM在運行時管理內存的核心區域,它主要包括以下幾個部分:

  • 堆(Heap):堆是JVM中最大的一塊內存區域,被所有線程共享。它的主要作用是存放對象實例和數組。幾乎所有的對象實例都在堆上分配內存。堆可以分為新生代和老年代,新生代又可以進一步細分為伊甸園區(Eden Space)、幸存0區(Survivor 0 Space)和幸存1區(Survivor 1 Space)。對象首先會在伊甸園區分配內存,當伊甸園區空間不足時,會觸發Minor GC(新生代垃圾回收),將存活的對象移動到幸存區,經過多次GC后,如果對象仍然存活,會被晉升到老年代。堆的大小可以通過JVM參數(如-Xmx設置最大堆大小,-Xms設置初始堆大小)進行調整。
  • 方法區(Method Area):方法區也是被所有線程共享的區域,用于存儲已被加載的類的元數據信息(如類的結構信息、常量池、靜態變量、即時編譯器編譯后的代碼緩存等)。在Java 8之前,方法區的實現是永久代(PermGen),從Java 8開始,使用元空間(Meta - Space)來替代永久代。元空間使用本地內存,其大小不再受限于-XX:MaxPermSize參數,而是受限于系統的可用內存。
  • Java棧(Java Stack):Java棧是線程私有的,它描述的是Java方法執行的內存模型。每個方法在執行時都會創建一個棧幀(Stack Frame),棧幀中存儲了局部變量表、操作數棧、動態鏈接、方法返回地址等信息。當方法被調用時,對應的棧幀入棧,方法執行完畢后,棧幀出棧。Java棧的大小可以通過-Xss參數進行設置。
  • 本地方法棧(Native Method Stack):本地方法棧與Java棧類似,也是線程私有的,它主要用于支持Native方法的執行。當Java程序調用Native方法(通常是用C或C++編寫的本地代碼)時,會在本地方法棧中創建相應的棧幀來管理方法的執行。
  • 程序計數器(Program Counter Register):程序計數器也是線程私有的,它記錄了當前線程所執行的字節碼指令的地址(行號)。在多線程環境下,每個線程都有自己獨立的程序計數器,這樣當線程切換時,能夠保證線程繼續正確地執行。

2.3 執行引擎

執行引擎是JVM的執行核心,它負責執行加載到JVM中的字節碼指令。執行引擎主要包含以下幾個組件:

  • 解釋器(Interpreter):解釋器會逐條讀取字節碼指令,并將其解釋為對應平臺的機器碼并執行。它的優點是啟動速度快,因為不需要進行額外的編譯工作,但執行效率相對較低,因為每次執行都需要解釋。
  • 即時編譯器(Just - In - Time Compiler,JIT):即時編譯器會在運行時將熱點代碼(被頻繁執行的代碼)編譯成機器碼,這樣在后續執行時就可以直接執行編譯后的機器碼,提高執行效率。HotSpot虛擬機中包含兩種即時編譯器:C1編譯器和C2編譯器。C1編譯器又稱為客戶端編譯器,它的編譯速度較快,適用于對啟動速度要求較高的應用場景;C2編譯器又稱為服務器端編譯器,它的編譯優化程度更高,適用于對執行效率要求較高的服務器端應用。
  • 垃圾回收器(Garbage Collector,GC):雖然垃圾回收器主要的職責是回收堆中不再使用的對象所占用的內存空間,但它也與執行引擎密切相關。當執行引擎在執行字節碼指令時,會產生新的對象并分配內存,同時垃圾回收器會監控堆中對象的存活情況,當發現有不再使用的對象時,會進行垃圾回收操作,以保證堆有足夠的空間來分配新的對象。

2.4 本地接口(Native Interface)

本地接口的作用是使Java程序能夠調用本地代碼(通常是用C或C++編寫的代碼)。通過本地接口,Java程序可以與操作系統底層進行交互,例如訪問本地文件系統、網絡接口等。Java提供了JNI(Java Native Interface)來實現Java代碼與本地代碼的交互。在JNI中,定義了一系列的函數和數據結構,用于在Java虛擬機和本地代碼之間傳遞數據和控制執行流程。

三、JVM的運行流程

3.1 類加載階段

  1. 加載:首先,類加載器會根據類的全限定名(如com.example.HelloWorld)來查找對應的Class文件。如果是啟動類加載器,它會在指定的核心類庫路徑中查找;如果是擴展類加載器或應用程序類加載器,會在相應的加載路徑中查找。找到Class文件后,類加載器會將Class文件中的字節碼加載到內存中,并創建一個對應的java.lang.Class對象來表示這個類。
  2. 驗證:加載后的字節碼需要進行驗證,以確保其符合JVM的規范,不會對JVM的安全造成威脅。驗證階段主要包括文件格式驗證(檢查字節碼文件是否符合Class文件的格式規范)、元數據驗證(檢查類的元數據信息是否符合Java語言規范,如類的繼承關系是否正確等)、字節碼驗證(檢查字節碼指令是否合法,是否存在安全隱患等)和符號引用驗證(檢查符號引用是否能正確解析到實際的類、字段、方法等)。
  3. 準備:在準備階段,JVM會為類的靜態變量分配內存,并設置默認初始值。例如,對于public static int num = 10;,在準備階段,num會被初始化為0,而不是10,因為真正的賦值操作是在初始化階段進行的。
  4. 解析:解析階段是將符號引用轉換為直接引用的過程。符號引用是在Class文件中使用的一種對類、字段、方法等的符號化表示,而直接引用是可以直接指向目標的指針或句柄等。例如,在字節碼中對一個類的引用可能是通過類的全限定名這種符號引用表示的,在解析階段會將其轉換為指向該類在內存中實際位置的直接引用。
  5. 初始化:初始化階段是類加載過程的最后一步,在這個階段,JVM會執行類的靜態代碼塊和對靜態變量的賦值操作。例如,對于public static int num = 10;,會在這個階段將num賦值為10,同時靜態代碼塊中的代碼也會被執行。

3.2 程序執行階段

當類加載完成后,JVM就可以開始執行程序了。

  1. 創建線程:JVM會根據程序的入口點(如main方法所在的類)創建主線程。在Java程序中,除了主線程外,還可以創建多個子線程來實現多線程編程。每個線程都有自己獨立的Java棧、本地方法棧和程序計數器。
  2. 方法調用與執行:當主線程開始執行時,會從main方法開始調用。在方法調用過程中,會在Java棧中創建相應的棧幀。棧幀中包含了局部變量表(用于存儲方法中的局部變量)、操作數棧(用于執行字節碼指令時的操作數存儲和計算)、動態鏈接(用于將符號引用轉換為直接引用,實現方法調用的動態綁定)和方法返回地址(用于記錄方法執行完畢后返回的位置)。執行引擎會按照字節碼指令的順序,從方法的第一條字節碼指令開始執行,通過解釋器或即時編譯器將字節碼轉換為機器碼并執行。在執行過程中,可能會涉及到對其他方法的調用,此時會重復上述過程,在Java棧中創建新的棧幀。
  3. 內存管理與垃圾回收:在程序執行過程中,會不斷地創建對象并分配內存,這些對象主要存放在堆中。隨著程序的運行,堆中的對象數量會不斷增加,當堆空間不足時,垃圾回收器會被觸發,對堆中不再使用的對象進行回收,釋放內存空間,以保證程序能夠繼續正常運行。垃圾回收器會根據一定的算法(如標記 - 清除算法、標記 - 整理算法、復制算法等)來判斷對象是否存活,并進行相應的回收操作。
  4. 線程結束:當所有線程都執行完畢(例如主線程執行完main方法中的所有代碼,子線程也都完成了各自的任務),JVM會進行一些清理工作,然后退出程序。

四、程序計數器詳解

4.1 程序計數器的定義與作用

程序計數器是JVM運行時數據區中的一個較小的內存區域,它是線程私有的。其主要作用是記錄當前線程所執行的字節碼指令的地址(行號)。在Java程序執行過程中,字節碼指令是按照順序依次執行的,程序計數器就像是一個指針,指向當前正在執行的字節碼指令的位置。當一條指令執行完畢后,程序計數器會指向下一條要執行的指令。

在多線程環境下,程序計數器的作用尤為重要。由于多個線程是并發執行的,CPU會在不同線程之間進行切換。當一個線程被暫停,另一個線程開始執行時,每個線程都需要能夠記住自己上次執行到的位置,以便在下次被調度執行時能夠繼續正確地執行。程序計數器就為每個線程提供了這樣一個記錄執行位置的功能,保證了線程切換后能夠繼續從正確的位置開始執行字節碼指令。

4.2 程序計數器與字節碼執行

在JVM執行字節碼指令的過程中,程序計數器始終與執行過程緊密配合。當JVM加載一個類并開始執行其方法時,首先會將程序計數器設置為方法字節碼的起始位置。然后,執行引擎會根據程序計數器所指向的位置,讀取相應的字節碼指令,并進行解釋或編譯執行。在執行過程中,每執行完一條字節碼指令,程序計數器會自動遞增,指向下一條字節碼指令的位置。

例如,對于以下簡單的Java代碼:

public class Test {public static void main(String[] args) {int a = 10;int b = 20;int c = a + b;System.out.println(c);}
}

在編譯后的字節碼中,會有一系列的指令來實現變量的賦值、加法運算和輸出操作。程序計數器會從字節碼的第一條指令開始,依次指向每條指令,執行引擎根據程序計數器的指示來執行相應的操作。當執行完變量a的賦值指令后,程序計數器會指向下一條關于變量b賦值的指令,以此類推,直到整個main方法執行完畢。

4.3 程序計數器與異常處理

在Java程序中,異常處理也是與程序計數器密切相關的一個重要方面。當程序在執行過程中遇到異常時,JVM會根據異常的類型和處理機制進行相應的操作。在異常發生時,程序計數器所指向的位置會被記錄下來,以便在異常處理完畢后能夠正確地恢復程序的執行。

例如,當使用try - catch語句塊來捕獲異常時,如果在try塊中發生了異常,JVM會暫停當前字節碼指令的執行,根據異常類型查找對應的catch塊。在找到合適的catch塊后,程序計數器會被設置為catch塊中第一條字節碼指令的位置,開始執行異常處理代碼。當異常處理完畢后,如果需要繼續執行后續代碼,程序計數器會根據異常處理的結果和程序的邏輯,被設置為合適的位置,繼續執行字節碼指令。

4.4 程序計數器的特點與限制

程序計數器是一塊非常小的內存區域,它的生命周期與線程相同。當線程創建時,程序計數器也會被創建并初始化;當線程結束時,程序計數器也會隨之銷毀。由于程序計數器只是記錄字節碼指令的地址,所以它所占用的內存空間非常小,對JVM的整體性能影響幾乎可以忽略不計。

需要注意的是,程序計數器只能記錄字節碼指令的地址,對于Native方法,由于其不是由字節碼指令組成,所以程序計數器無法記錄Native方法的執行位置。在執行Native方法時,程序計數器的值通常為空。

五、總結

JVM作為Java語言的核心運行環境,其組成部分和運行流程涵蓋了類加載、內存管理、指令執行等多個方面。類加載子系統負責將字節碼文件加載到JVM中并進行初始化;運行時數據區管理著程序運行時的內存分配和使用;執行引擎負責執行字節碼指令;本地接口則實現了Java程序與本地代碼的交互。而程序計數器作為運行時數據區中一個看似微小卻不可或缺的部分,在保證線程正確執行字節碼指令方面發揮著關鍵作用。

深入理解JVM的這些知識,不僅有助于我們編寫出更高效、更穩定的Java程序,還能在遇到性能問題、內存泄漏等故障時,從JVM的底層原理出發進行分析和解決。隨著Java技術的不斷發展,JVM也在持續演進和優化,我們需要不斷學習和關注JVM的新特性和新變化,以更好地適應和應用Java技術。希望通過本文的介紹,讀者能夠對JVM有一個更加全面和深入的認識。

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

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

相關文章

elementui的默認樣式修改

今天用element ui ,做了個消息提示,發現提示的位置總是在上面,如圖: 可是我想讓提示的位置到下面來,該怎么辦? 最后還是看了官方的api 原來有個自定義樣式屬性 customClass 設置下就好了 js代碼 css代碼 效…

游戲引擎學習第204天

回顧并為今天的內容做鋪墊 好,現在開始這一集。今天我們將進行一些用戶界面編程,覺得這是一個展示如何編寫這類代碼的好時機。很多人對如何做用戶界面代碼都很好奇,所以展示一下如何編寫是非常有意義的。 我之所以在現在的這個地方做這些工…

我的世界1.20.1forge模組開發進階教程——TerraBlender

TerraBlender介紹 從模組開發者的視角來看,TerraBlender為Minecraft生物群系類模組的開發提供了全方位的技術支持,顯著降低了開發門檻并提升了模組的質量與擴展性: 跨平臺兼容性架構支持Forge/Fabric/Quilt/NeoForge四大主流加載器,開發者無需為不同平臺單獨適配代碼客戶端…

借助mcpo在open-webui中使用mcp

open-webui前幾天發布了0.6版本,我立即進行了升級。新版本中一個重要功能是通過mcpo方式支持了mcp server。本文將介紹mcpo是什么,以及如何在open-webui中使用它。同時,我也會分享幾個在接入過程中遇到的問題及解決方案。 首先來介紹mcpo&…

安裝gpu版本的dgl

1.先去網址,找到對應版本的dgl,然后下載到本地。 dgl-whl下載地址 我的是python 3.8 ,cuda 11.6. windows 2.在虛擬環境里 輸入 pip install E:\dgl-1.0.2cu116-cp38-cp38-win_amd64.whl (因為我下載到E盤里了) 這樣GPU版本的d…

PyTorch使用(7)-張量常見運算函數

1. 基本數學運算 1.1 平方根和冪運算 import torchx torch.tensor([4.0, 9.0, 16.0])# 平方根 sqrt_x torch.sqrt(x) # tensor([2., 3., 4.])# 平方 square_x torch.square(x) # tensor([16., 81., 256.])# 任意冪次 pow_x torch.pow(x, 3) # tensor([64., 729., 4096…

Nginx功能及應用全解:從負載均衡到反向代理的全面剖析

Nginx作為一款開源的高性能HTTP服務器和反向代理服務器,憑借其高效的資源利用率和靈活的配置方式,已成為互聯網領域中最受歡迎的Web服務器之一。無論是作為HTTP服務器、負載均衡器,還是作為反向代理和緩存服務器,Nginx的多種功能廣…

安徽京準:NTP時間同步服務器操作使用說明

安徽京準:NTP時間同步服務器操作使用說明 3.1 連接天線 天線連接到“ANT”口。 3.2 連接電源 將220V電源線連到AC220V座上或將電源適配器(7.5V~12V)接到DC口上。也可以同時接上,提高供電可靠性。 3.3 LAN網口 網線連接到NTP…

Java項目之基于ssm的懷舊唱片售賣系統(源碼+文檔)

項目簡介 懷舊唱片售賣系統實現了以下功能: 用戶信息管理: 用戶信息新增:添加新用戶的信息。 用戶信息修改:對現有用戶信息進行修改。 商品信息管理: 商品信息添加:增加新的商品(唱片&#x…

基于 Python 的自然語言處理系列(70):檢索增強生成(RAG)

1. 什么是 RAG? 在許多大模型(LLM)應用場景中,我們需要使用特定的用戶數據,而這些數據并未包含在模型的訓練集中。檢索增強生成(Retrieval Augmented Generation,RAG)是一種有效的解…

CAD插件實現:所有文字顯示到列表、縮放、編輯——CAD-c#二次開發

當圖中有大量文字,需要全部顯示到一個列表時并縮放到需要的文字時,可采用插件實現,效果如下: 附部分代碼如下: private void BtnSelectText_Click(object sender, EventArgs e){var doc Application.DocumentManager.…

Systemd構建自動化備份服務與外部存儲管理

實訓背景 你是一家數據公司的系統管理員,需設計一套自動化備份系統,滿足以下需求: 定期備份:每周日凌晨1點將 /data 目錄壓縮備份到 /backups。外部存儲掛載:插入USB設備時自動掛載到 /mnt/usb,并觸發增量…

PostgreSQL中根據另一表的值來更新一個字段

UPDATE table1 SET value t2.new_value FROM table2 t2 WHERE table1.id t2.reference_id; 解釋 UPDATE table1:指定要更新的表,不要用別名。 SET value t2.new_value:設置要更新的字段及其新值,這里新值來自 table2。也可更…

#SVA語法滴水穿石# (000)斷言基本概念和背景

一、前言 隨著數字電路規模越來越大、設計越來越復雜,使得對設計的功能驗證越來越重要。首先,我們要明白為什么要對設計進行驗證?驗證有什么作用?例如,在用FPGA進行設計時,我們并不能確保設計出來的東西沒有功能上的漏洞,因此在設計后我們都會對其進行驗證仿真。換句話說…

Git 從入門到精通(開源協作特別版)

🧠 Git 從入門到精通(開源協作特別版) ? 基礎命令 🧰 高級用法 🛠? 開源實戰技巧 🌍 GitHub 社區協作 適合:從0開始 → 熟練開發者 → 參與/維護開源項目 🔰 第1章:…

【SQL】取消sql某一列的唯一值key值的方法

在插入數據到sql時,遇到了這個問題: Duplicate entry ‘XXX’ for key 起因是: 我之前設計表的時候,手動給product_title 這個列加了一個key, key 是這個字段的唯一鍵約束,就不能重復在這一列存入重復的數…

【小沐學Web3D】three.js 加載三維模型(React Three Fiber)

文章目錄 1、簡介1.1 Three.js1.2 React Three Fiber 2、測試2.1 初始化環境2.2 app.js修改(顯示內置立方體)2.3 app.js修改(顯示內置球體)2.4 app.js修改(顯示自定義立方體)2.5 app.js修改(顯示…

本地部署 Firecrawl 爬蟲讓 AI 知識庫更豐滿

https://www.firecrawl.dev/ firecrawl-logo-with-fire.png 什么是Firecrawl Firecrawl 是一款 可以將網站轉換為 便于AI處理的Markdown 格式的爬蟲工具 ,主要 提供 API 服務 ,無需站點地圖,只需要接收一個 URL 地址就可以爬取網站及網站下可…

純個人整理,藍橋杯使用的算法模板day2(0-1背包問題),手打個人理解注釋,超全面,且均已驗證成功(附帶詳細手寫“模擬流程圖”,全網首個

算法索引 01背包優化前空間優化版(使用一維數組)優化后的模擬流程圖為何優化后,j不能使用正序遍歷模擬流程圖 代碼對應實現案例 01背包 優化前 /*** 0-1背包問題解法(與下方代碼表格示例對應,已模擬驗證)*…

APang網聯科技項目報告【服務器篇】

APang網聯科技:連接未來,智能領航 公司簡介 APang網聯科技成立于 [2005年],總部位于 [廣東深圳],是一家集網絡技術研發、系統集成、項目實施與運維服務為一體的高新技術企業。我們致力于為客戶提供全方位、定制化的網絡部署解決…