91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JVM的基礎知識總結

發布時間:2021-10-25 16:50:35 來源:億速云 閱讀:208 作者:iii 欄目:編程語言

本篇內容主要講解“JVM的基礎知識總結”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“JVM的基礎知識總結”吧!

1. JDK、JRE、JVM的關系

1.1 JDK

JDK(Java Development Kit) 是用于開發 Java 應用程序的軟件開發工具集合,包括 了 Java 運行時的環境(JRE)、解釋器(Java)、編譯器(javac)、Java 歸檔 (jar)、文檔生成器(Javadoc)等工具。簡單的說我們要開發Java程序,就需要安裝某個版本的JDK工具包。

1.2 JRE

JRE(Java Runtime Enviroment )提供 Java 應用程序執行時所需的環境,由 Java 虛擬機(JVM)、核心類、支持文件等組成。簡單的說,我們要是想在某個機器上運 行Java程序,可以安裝JDK,也可以只安裝JRE,后者體積比較小。

1.3 JVM

Java Virtual Machine(Java 虛擬機)有三層含義,分別是:

JVM規范要求

滿足 JVM 規范要求的一種具體實現(一種計算機程序)

一個 JVM 運行實例,在命令提示符下編寫 Java 命令以運行 Java 類時,都會創建一 個 JVM 實例,我們下面如果只記到JVM則指的是這個含義;如果我們帶上了某種JVM 的名稱,比如說是Zing JVM,則表示上面第二種含義

1.4 JDK 與 JRE、JVM 之間的關系

就范圍來說,JDK > JRE > JVM:

  • JDK = JRE + 開發工具

  • JRE = JVM + 類庫

JVM的基礎知識總結

JVM的基礎知識總結

Java程序的開發運行過程為:

我們利用 JDK (調用 Java API)開發Java程序,編譯成字節碼或者打包程序 然后可以用 JRE 則啟動一個JVM實例,加載、驗證、執行 Java 字節碼以及依賴庫, 運行Java程序。

而JVM 將程序和依賴庫的Java字節碼解析并變成本地代碼執行,產生結果 。

1.5 如果不知道自動安裝/別人安裝的JDK在哪個目錄怎么辦?

最簡單/最麻煩的查詢方式是詢問相關人員。

查找的方式很多,比如,可以使用 which , whereis , ls ‐l 跟蹤軟連接, 或者 find 命令全局查找(可能需要sudo權限), 例如:

  • jps ‐v

  • whereis javac

  • ls ‐l /usr/bin/javac

  • find / ‐name javac

2. 常用性能指標

> 沒有量化就沒有改進

  • 分析系統性能問題: 比如是不是達到了我們預期性能指標,判斷資源層面有沒有問題,JVM層面有沒有問題,系統的關鍵處理流程有沒有問題,業務流程是否需要優化

  • 通過工具收集系統的狀態,日志,包括打點做內部的指標收集,監控并得出關鍵性能指標數據,也包括進行壓測,得到一些相關的壓測數據和性能內部分析數據

  • 根據分析結果和性能指標,進行資源配置調整,并持續進行監控和分析,以優化性能,直到滿足系統要求,達到系統的最佳性能狀態

2.1 計算機系統中,性能相關的資源主要分為這幾類:

  • CPU:CPU是系統最關鍵的計算資源,在單位時間內有限,也是比較容易由于業務邏輯處理不合理而出現瓶頸的地方,浪費了CPU資源和過渡消耗CPU資源都不 是理想狀態,我們需要監控相關指標;

  • 內存:內存則對應程序運行時直接可使用的數據快速暫存空間,也是有限的,使用過程隨著時間的不斷的申請內存又釋放內存,好在JVM的GC幫我們處理了這些事情,但是如果GC配置的不合理,一樣會在一定的時間后,產生包括OOM宕 機之類的各種問題,所以內存指標也需要關注;

  • IO(存儲+網絡):CPU在內存中把業務邏輯計算以后,為了長期保存,就必須通過磁盤存儲介質持久化,如果多機環境、分布式部署、對外提供網絡服務能 力,那么很多功能還需要直接使用網絡,這兩塊的IO都會比CPU和內存速度更慢,所以也是我們關注的重點。

2.2 性能優化中常見的套路

性能優化一般要存在瓶頸問題,而瓶頸問題都遵循80/20原則。既我們把所有的整個處理過程中比較慢的因素都列一個清單,并按照對性能的影響排序,那么前20%的瓶頸問題,至少會對性能的影響占到80%比重。換句話說,我們優先解決了最重要的幾個問題,那么性能就能好一大半。

我們一般先排查基礎資源是否成為瓶頸。看資源夠不夠,只要成本允許,加配置可能是最快速的解決方案,還可能是最劃算,最有效的解決方案。 與JVM有關的系統資源,主要是 CPU 和 內存 這兩部分。 如果發生資源告警/不足, 就需要評估系統容量,分析原因。

一般衡量系統性能的維度有3個:

  • 延遲(Latency): 一般衡量的是響應時間(Response Time),比如平均響應時間。 但是有時候響應時間抖動的特別厲害,也就是說有部分用戶的響應時間特別高, 這時我們一般假設我們要保障95%的用戶在可接受的范圍內響應,從而提供絕大多數用戶具有良好的用戶體驗,這就是延遲的95線(P95,平均100個用戶請求中95個已經響應的時間),同理還有99線,最大響應時間等(95線和99線比較常用;用戶訪問量大的時候,對網絡有任何抖動都可能會導致最大響應時間變得非常大,最大響應時間這個指標不可控,一般不用)。

  • 吞吐量(Throughput): 一般對于交易類的系統我們使用每秒處理的事務數(TPS) 來衡量吞吐能力,對于查詢搜索類的系統我們也可以使用每秒處理的請求數 (QPS)。

  • 系統容量(Capacity): 也叫做設計容量,可以理解為硬件配置,成本約束。

性能指標還可分為兩類:

  • 業務需求指標:如吞吐量(QPS、TPS)、響應時間(RT)、并發數、業務成功率等。

  • 資源約束指標:如CPU、內存、I/O等資源的消耗情況。

2.3性能調優總結

JVM的基礎知識總結

性能調優的第一步是制定指標,收集數據,第二步是找瓶頸,然后分析解決瓶頸問題。通過這些手段,找當前的性能極限值。壓測調優到不能再優化了的 TPS和QPS, 就是極限值。知道了極限值,我們就可以按業務發展測算流量和系統壓力,以此做容量規劃,準備機器資源和預期的擴容計劃。最后在系統的日常運行過程中,持續觀察,逐步重做和調整以上步驟,長期改善改進系統性能。

我們經常說“ 脫離場景談性能都是耍流氓 ”,實際的性能分析調優過程中,我們需要根據具體的業務場景,綜合考慮成本和性能,使用最合適的辦法去處理。系統的性能優化到3000TPS如果已經可以在成本可以承受的范圍內滿足業務發展的需求,那么再花幾個人月優化到3100TPS就沒有什么意義,同樣地如果花一倍成本去優化到5000TPS 也沒有意義。

Donald Knuth曾說過“ 過早的優化是萬惡之源 ”,我們需要考慮在恰當的時機去優化系統。在業務發展的早期,量不大,性能沒那么重要。我們做一個新系統,先考慮整體設計是不是OK,功能實現是不是OK,然后基本的功能都做得差不多的時候(當然整體的框架是不是滿足性能基準,可能需要在做項目的準備階段就通過POC(概念證明)階段驗證。),最后再考慮性能的優化工作。因為如果一開始就考慮優化,就可 能要想太多導致過度設計了。而且主體框架和功能完成之前,可能會有比較大的改動,一旦提前做了優化,可能這些改動導致原來的優化都失效了,又要重新優化,多做了很多無用功。

3. JVM基礎知識

3.1 常見的編程語言類型

首先,我們可以把形形色色的編程從底向上劃分為最基本的三大類:機器語言、匯編 語言、高級語言。

JVM的基礎知識總結

按《計算機編程語言的發展與應用》一文里的定義:計算機編程語言能夠實現人與機器之間的交流和溝通,而計算機編程語言主要包括匯編語言、機器語言以及高級語言,具體內容如下:

  • 機器語言:這種語言主要是利用二進制編碼進行指令的發送,能夠被計算機快速地識別,其靈活性相對較高,且執行速度較為可觀,機器語言與匯編語言之間的相似性較高,但由于具有局限性,所以在使用上存在一定的約束性。

  • 匯編語言:該語言主要是以縮寫英文作為標符進行編寫的,運用匯編語言進行編 寫的一般都是較為簡練的小程序,其在執行方面較為便利,但匯編語言在程序方面較為冗長,所以具有較高的出錯率。

  • 高級語言:所謂的高級語言,其實是由多種編程語言結合之后的總稱,其可以對多條指令進行整合,將其變為單條指令完成輸送,其在操作細節指令以及中間過 程等方面都得到了適當的簡化,所以,整個程序更為簡便,具有較強的操作性, 而這種編碼方式的簡化,使得計算機編程對于相關工作人員的專業水平要求不斷放寬。

3.2 高級語言分類

  • 如果按照有沒有虛擬機來劃分,高級編程語言可分為兩類:

  • 有虛擬機:Java,Lua,Ruby,部分JavaScript的實現等等

  • 無虛擬機:C,C++,C#,Golang,以及大部分常見的編程語言

  • 如果按照變量是不是有確定的類型,還是類型可以隨意變化來劃分,高級編程語言可 以分為:

  • 靜態類型:Java,C,C++等等

  • 動態類型:所有腳本類型的語言

  • 如果按照是編譯執行,還是解釋執行,可以分為:

  • 編譯執行:C,C++,Golang,Rust,C#,Java,Scala,Clojure,Kotlin, Swift...等等

  • 解釋執行:JavaScript的部分實現和NodeJS,Python,Perl,Ruby...等等

  • 此外,我們還可以按照語言特點分類:

  • 面向過程:C,Basic,Pascal,Fortran等等

  • 面向對象:C++,Java,Ruby,Smalltalk等等

  • 函數式編程:LISP、Haskell、Erlang、OCaml、Clojure、F#等等

有的甚至可以劃分為純面向對象語言,例如Ruby,所有的東西都是對象(Java不是所有東西都是對象,比如基本類型 int 、 long 等等,就不是對象,但是它們的包裝 類 Integer 、 Long 則是對象)。 還有既可以當做編譯語言又可以當做腳本語言的,例如Groovy等語言。

3.3 關于跨平臺

現在我們聊聊跨平臺,為什么要跨平臺,因為我們希望所編寫的代碼和程序,在源代 碼級別或者編譯后,可以運行在多種不同的系統平臺上,而不需要為了各個平臺的不 同點而去實現兩套代碼。典型地,我們編寫一個web程序,自然希望可以把它部署到 Windows平臺上,也可以部署到Linux平臺上,甚至是MacOS系統上。 這就是跨平臺的能力,極大地節省了開發和維護成本,贏得了商業市場上的一致好評。

這樣來看,一般來說解釋型語言都是跨平臺的,同一份腳本代碼,可以由不同平臺上的解釋器解釋執行。但是對于編譯型語言,存在兩種級別的跨平臺: 源碼跨平臺和二進制跨平臺。

1、典型的源碼跨平臺(C++):

JVM的基礎知識總結

2、典型的二進制跨平臺(Java字節碼):

JVM的基礎知識總結

可以看到,C++里我們需要把一份源碼,在不同平臺上分別編譯,生成這個平臺相關的二進制可執行文件,然后才能在相應的平臺上運行。 這樣就需要在各個平臺都有開發工具和編譯器,而且在各個平臺所依賴的開發庫都需要是一致或兼容的。 這一點在過去的年代里非常痛苦,被戲稱為 “依賴地獄”。 C++的口號是“一次編寫,到處(不同平臺)編譯”,但實際情況上是一編譯就報錯,變 成了 “一次編寫,到處調試,到處找依賴、改配置”。 大家可以想象,你編譯一份代 碼,發現缺了幾十個依賴,到處找還找不到,或者找到了又跟本地已有的版本不兼 容,這是一件怎樣令人絕望的事情。

而Java語言通過虛擬機技術率先解決了這個難題。 源碼只需要編譯一次,然后把編譯 后的class文件或jar包,部署到不同平臺,就可以直接通過安裝在這些系統中的JVM上 面執行。 同時可以把依賴庫(jar文件)一起復制到目標機器,慢慢地又有了可以在各個平臺都直接使用的Maven中央庫(類似于linux里的yum或apt-get源,macos里的 homebrew,現代的各種編程語言一般都有了這種包依賴管理機制:python的pip, dotnet的nuget,NodeJS的npm,golang的dep,rust的cargo等等)。這樣就實現了 讓同一個應用程序在不同的平臺上直接運行的能力。

總結一下跨平臺:

  • 腳本語言直接使用不同平臺的解釋器執行,稱之為腳本跨平臺,平臺間的差異由 不同平臺上的解釋器去解決。這樣的話代碼很通用,但是需要解釋和翻譯,效率較低。

  • 編譯型語言的代碼跨平臺,同一份代碼,需要被不同平臺的編譯器編譯成相應的二進制文件,然后再去分發和執行,不同平臺間的差異由編譯器去解決。編譯產 生的文件是直接針對平臺的可執行指令,運行效率很高。但是在不同平臺上編譯 復雜軟件,依賴配置可能會產生很多環境方面問題,導致開發和維護的成本較 高。

  • 編譯型語言的二進制跨平臺,同一份代碼,先編譯成一份通用的二進制文件,然后分發到不同平臺,由虛擬機運行時來加載和執行,這樣就會綜合另外兩種跨平臺語言的優勢,方便快捷地運行于各種平臺,雖然運行效率可能比起本地編譯類 型語言要稍低一點。 而這些優缺點也是Java虛擬機的優缺點。

3.4 關于運行時(Runtime)與虛擬機(VM)

我們前面提到了很多次 Java運行時 和 JVM虛擬機 ,簡單的說JRE就是Java的運行 時,包括虛擬機和相關的庫等資源。 可以說運行時提供了程序運行的基本環境,JVM在啟動時需要加載所有運行時的核心庫等資源,然后再加載我們的應用程序字節碼,才能讓應用程序字節碼運行在JVM這 個容器里。

但也有一些語言是沒有虛擬機的,編譯打包時就把依賴的核心庫和其他特性支持,一 起靜態打包或動態鏈接到程序中,比如Golang和Rust,C#等。 這樣運行時就和程序指令組合在一起,成為了一個完整的應用程序,好處就是不需要虛擬機環境,壞處是編譯后的二進制文件沒法直接跨平臺了。

3.5 關于內存管理和垃圾回收(GC)

內存管理就是內存的生命周期管理,包括內存的申請、壓縮、回收等操作。 Java的內存管理就是GC,JVM的GC模塊不僅管理內存的回收,也負責內存的分配和壓縮整理。

4. Java字節碼

Java中的字節碼,英文名為 bytecode , 是Java代碼編譯后的中間代碼格式。JVM需要讀取并解析字節碼才能執行相應的任務。 由單字節( byte )的指令組成, 理論上最多支持 256 個操作碼(opcode)。實際上Java只使用了200左右的操作碼, 還有一些操作碼則保留給調試操作。

操作碼, 下面稱為指令 , 主要由類型前綴和操作名稱兩部分組成。

> 例如,' i ' 前綴代表 ‘ integer ’,所以,' iadd ' 很容易理解, 表示對整數執行加法運算。

4.1 根據指令的性質,主要分為四個大類:

  • 棧操作指令,包括與局部變量交互的指令

  • 程序流程控制指令

  • 對象操作指令,包括方法調用指令

  • 算數運算以及類型轉換指令

此外還有一些執行專門任務的指令,比如同步(synchronization)指令,以及拋出異常相關的指令等等

4.2 對象初始化指令:new指令, init 以及 clinit 簡介

我們都知道 new 是Java編程語言中的一個關鍵字, 但其實在字節碼中,也有一個指令叫做 new 。 當我們創建類的實例時, 編譯器會生成類似下面這樣的操作碼:

```
0: new #2 // class demo/jvm0104/HelloByteCode 
3: dup
4: invokespecial #3 // Method "<init>":()V
```

當你同時看到 new, dup 和 invokespecial 指令在一起時,那么一定是在創建類的實例對象! 為什么是三條指令而不是一條呢?這是因為:

  • new 指令只是創建對象,但沒有調用構造函數。

  • invokespecial 指令用來調用某些特殊方法的, 當然這里調用的是構造函數。

  • dup 指令用于復制棧頂的值。

  • 由于構造函數調用不會返回值,所以如果沒有dup指令, 在對象上調用方法并初始化之后,操作數棧就會是空的,在初始化之后就會出問題, 接下來的代碼就無法對其進行處理。

在調用構造函數的時候,其實還會執行另一個類似的方法 <init> ,甚至在執行構造函數之前就執行了。還有一個可能執行的方法是該類的靜態初始化方法 <clinit> ,但 <clinit> 并不能被直接調用,而是由這些指令觸發的: new , getstatic , putstatic or invokestatic。

4.3 棧內存操作指令

有很多指令可以操作方法棧。 前面也提到過一些基本的棧操作指令: 他們將值壓入棧,或者從棧中獲取值。 除了這些基礎操作之外也還有一些指令可以操作棧內存; 比如 swap 指令用來交換棧頂兩個元素的值。下面是一些示例:

最基礎的是 dup 和 pop 指令。

  • dup 指令復制棧頂元素的值。

  • pop 指令則從棧中刪除最頂部的值。

還有復雜一點的指令:比如, swap , dup_x1 和 dup2_x1 。

  • 顧名思義, swap 指令可交換棧頂兩個元素的值,例如A和B交換位置(圖中示例 4);

  • dup_x1 將復制棧頂元素的值,并在插入在最上面兩個值后(圖中示例5);

  • dup2_x1 則復制棧頂兩個元素的值,并插入最上面三個值后(圖中示例6)。

JVM的基礎知識總結

dup , dup_x1 , dup2_x1 指令補充說明 :

  • dup 指令:官方說明是,復制棧頂的值, 并將復制的值壓入棧.

  • dup_x1 指令 : 官方說明是,復制棧頂的值, 并將復制的值插入到最上面2個值的下方。

  • dup2_x1 指令: 官方說明是,復制棧頂 1個64位/或2個32位的值, 并將復制的值按照原始順序,插入原始值下面一個32位值的下方。

5. 算術運算指令與類型轉換指令

Java字節碼中有許多指令可以執行算術運算。實際上,指令集中有很大一部分表示都是關于數學運算的。對于所有數值類型( int , long , double , float ),都有加, 減,乘,除,取反的指令。 那么 byte 和 char , boolean 呢? JVM 是當做 int 來處理的。另外還有部分指令用于數據類型之間的轉換。

JVM的基礎知識總結

當我們想將 int 類型的值賦值給 long 類型的變量時,就會發生類型轉換。

JVM的基礎知識總結

6. 方法調用指令和參數傳遞

  • invokestatic ,顧名思義,這個指令用于調用某個類的靜態方法,這也是方法調用指令中最快的一個。

  • invokespecial , 我們已經學過了, invokespecial 指令用來調用構造函數, 但也可以用于調用同一個類中的 private 方法, 以及可見的超類方法。

  • invokevirtual ,如果是具體類型的目標對象, invokevirtual 用于調用公共,受保護和打包私有方法。

  • invokeinterface ,當要調用的方法屬于某個接口時,將使用invokeinterface 指令。

> 那么 invokevirtual 和 invokeinterface 有什么區別呢?這確實是個好問 題。 為什么需要 invokevirtual 和 invokeinterface 這兩種指令呢? 畢竟 所有的接口方法都是公共方法, 直接使用 invokevirtual 不就可以了嗎? 這么做是源于對方法調用的優化。JVM必須先解析該方法,然后才能調用它

  • 使用 invokestatic 指令,JVM就確切地知道要調用的是哪個方法:因為調用的是靜態方法,只能屬于一個類。

  • 使用 invokespecial 時, 查找的數量也很少, 解析也更加容易,那么運行時就能更快地找到所需的方法。

  • ava虛擬機的字節碼指令集在JDK7之前一直就只有前面提到的4種指令 (invokestatic,invokespecial,invokevirtual,invokeinterface)。隨著JDK 7的發 布,字節碼指令集新增了 invokedynamic 指令。這條新增加的指令是實現“動態類型 語言”(Dynamically Typed Language)支持而進行的改進之一,同時也是JDK 8以后 支持的lambda表達式的實現基礎。

7. Java類加載器

7.1 類的生命周期和加載過程

JVM的基礎知識總結

一個類在JVM里的生命周期有7個階段,分別是加載(Loading)、驗證 (Verification)、準備(Preparation)、解析(Resolution)、初始化 (Initialization)、使用(Using)、卸載(Unloading)。 其中前五個部分(加載,驗證,準備,解析,初始化)統稱為類加載,下面我們就分 別來說一下這五個過程。

7.1.1 加載

加載階段也可以稱為“裝載”階段。 這個階段主要的操作是: 根據明確知道的class完全限定名, 來獲取二進制classfile格式的字節流,簡單點說就是 找到文件系統中/jar包中/或存在于任何地方的“ class文件 ”。 如果找不到二進制表示形式,則會拋出NoClassDefFound 錯誤。裝載階段并不會檢查 classfile 的語法和格式。類加載的整個過程主要由JVM和Java 的類加載系統共同完成, 當然具體到loading 階 段則是由JVM與具體的某一個類加載器(java.lang.classLoader)協作完成的。

7.1.2 校驗

鏈接過程的第一個階段是校驗 ,確保class文件里的字節流信息符合當前虛擬機的要求,不會危害虛擬機的安全。校驗過程檢classfile 的語義,判斷常量池中的符號,并執行類型檢查, 主要目的是判斷字節碼的合法性,比如 magic number, 對版本號進行驗證。 這些檢查 過程中可能會拋出 VerifyError , ClassFormatError 或 UnsupportedClassVersionError 。 因為classfile的驗證屬是鏈接階段的一部分,所以這個過程中可能需要加載其他類, 在某個類的加載過程中,JVM必須加載其所有的超類和接口。 如果類層次結構有問題(例如,該類是自己的超類或接口,死循環了),則JVM將拋出 ClassCircularityError 。 而如果實現的接口并不是一個 interface,或者聲明的超類是一個 interface,也會拋出 IncompatibleClassChangeError 。

7.1.3 準備

然后進入準備階段,這個階段將會創建靜態字段, 并將其初始化為標準默認值(比如 null 或者 0值 ),并分配方法表,即在方法區中分配這些變量所使用的內存空間。 請注意,準備階段并未執行任何Java代碼。

例如:

public static int i = 1;

在準備階段 i 的值會被初始化為0,后面在類初始化階段才會執行賦值為1; 但是下面如果使用final作為靜態常量,某些JVM的行為就不一樣了:

public static final int i = 1;

對應常量i,在準備階段就會被賦值1,其實這樣還是比較puzzle,例如其他語言 (C#)有直接的常量關鍵字const,讓告訴編譯器在編譯階段就替換成常量,類似 于宏指令,更簡單。

7.1.4 解析

然后進入可選的解析符號引用階段。 也就是解析常量池,主要有以下四種:類或接口的解析、字段解析、類方法解析、接 口方法解析。

簡單的來說就是我們編寫的代碼中,當一個變量引用某個對象的時候,這個引用在 .class 文件中是以符號引用來存儲的(相當于做了一個索引記錄)。 在解析階段就需要將其解析并鏈接為直接引用(相當于指向實際對象)。如果有了直 接引用,那引用的目標必定在堆中存在。加載一個class時, 需要加載所有的super類和super接口。

7.1.5 初始化

JVM規范明確規定, 必須在類的首次“主動使用”時才能執行類初始化。 初始化的過程包括執行:

  • 類構造器方法

  • static靜態變量賦值語句

  • static靜態代碼塊

如果是一個子類進行初始化會先對其父類進行初始化,保證其父類在子類之前進行初 始化。所以其實在java中初始化一個類,那么必然先初始化過 java.lang.Object 類,因為所有的java類都繼承自java.lang.Object。

7.2 類加載時機

了解了類的加載過程,我們再看看類的初始化何時會被觸發呢?JVM 規范枚舉了下述多種觸發情況:

  • 當虛擬機啟動時,初始化用戶指定的主類,就是啟動執行的 main方法所在的類;

  • 當遇到用以新建目標類實例的 new 指令時,初始化 new 指令的目標類,就是 new一個類的時候要初始化

  • 當遇到調用靜態方法的指令時,初始化該靜態方法所在的類;

  • 當遇到訪問靜態字段的指令時,初始化該靜態字段所在的類;

  • 子類的初始化會觸發父類的初始化;

  • 如果一個接口定義了 default 方法,那么直接實現或者間接實現該接口的類的初始化,會觸發該接口的初始化;

  • 使用反射 API 對某個類進行反射調用時,初始化這個類,其實跟前面一樣,反射調用要么是已經有實例了,要么是靜態方法,都需要初始化;

  • 當初次調用 MethodHandle 實例時,初始化該 MethodHandle 指向的方法所在的類。

同時以下幾種情況不會執行類初始化:

  • 通過子類引用父類的靜態字段,只會觸發父類的初始化,而不會觸發子類的初始化。

  • 定義對象數組,不會觸發該類的初始化。

  • 常量在編譯期間會存入調用類的常量池中,本質上并沒有直接引用定義常量的類,不會觸發定義常量所在的類。

  • 通過類名獲取Class對象,不會觸發類的初始化,Hello.class不會讓Hello類初始化。

  • 通過Class.forName加載指定類時,如果指定參數initialize為false時,也不會觸發類初始化,其實這個參數是告訴虛擬機,是否要對類進行初始化。 Class.forName(“jvm.Hello”)默認會加載Hello類。

  • 通過ClassLoader默認的loadClass方法,也不會觸發初始化動作(加載了,但是不初始化)。

7.3 類加載機制

類加載過程可以描述為“通過一個類的全限定名a.b.c.XXClass來獲取描述此類的Class 對象”,這個過程由“類加載器(ClassLoader)”來完成。這樣的好處在于,子類加載器可以復用父加載器加載的類。系統自帶的類加載器分為三種 :

JVM的基礎知識總結

  • 啟動類加載器(BootstrapClassLoader)

啟動類加載器(bootstrap class loader): 它用來加載 Java 的核心類,是用原生 C++代碼來實現的,并不繼承自

java.lang.ClassLoader(負責加載JDK中 jre/lib/rt.jar里所有的class)。它可以看做是JVM自帶的,我們再代碼層面無法直接獲取到

啟動類加載器的引用,所以不允許直接操作它, 如果打印出來就是個 null 。舉例來說,java.lang.String是由啟動類加載器加載

的,所以 String.class.getClassLoader()就會返回null。但是后面可以看到可以通過命令行 參數影響它加載什么。

  • 擴展類加載器(ExtClassLoader)

  • 擴展類加載器(extensions class loader):它負責加載JRE的擴展目錄,lib/ext 或者由java.ext.dirs系統屬性指定的目錄中的JAR包的類,代碼里直接獲取它的父 類加載器為null(因為無法拿到啟動類加載器)。

  • 應用類加載器(AppClassLoader)

  • 應用類加載器(app class loader):它負責在JVM啟動時加載來自Java命令的-classpath或者-cp選項、java.class.path系統屬性指定的jar包和類路徑。在應用程序代碼里可以通過ClassLoader的靜態方法getSystemClassLoader()來獲取應用類加載器。如果沒有特別指定,則在沒有使用自定義類加載器情況下,用戶自定義的類都由此加載器加載。

類加載機制有三個特點:

  • 雙親委托:當一個自定義類加載器需要加載一個類,比如java.lang.String,它很懶,不會一上來就直接試圖加載它,而是先委托自己的父加載器去加載,父加載 器如果發現自己還有父加載器,會一直往前找,這樣只要上級加載器,比如啟動類加載器已經加載了某個類比如java.lang.String,所有的子加載器都不需要自己加載了。如果幾個類加載器都沒有加載到指定名稱的類,那么會拋出 ClassNotFountException異常。

  • 負責依賴:如果一個加載器在加載某個類的時候,發現這個類依賴于另外幾個類或接口,也會去嘗試加載這些依賴項。

  • 緩存加載:為了提升加載效率,消除重復加載,一旦某個類被一個類加載器加載,那么它會緩存這個加載結果,不會重復加載。

到此,相信大家對“JVM的基礎知識總結”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

jvm
AI

九龙坡区| 武夷山市| 新泰市| 北海市| 南宁市| 云梦县| 乌鲁木齐市| 涿州市| 香港| 民县| 永胜县| 惠东县| 阿克苏市| 仁化县| 行唐县| 安仁县| 安远县| 汉源县| 金门县| 富源县| 房山区| 行唐县| 凉山| 乐亭县| 太和县| 景德镇市| 武隆县| 海晏县| 巴东县| 三穗县| 阳东县| 榆树市| 台中县| 南昌市| 务川| 含山县| 澜沧| 宁国市| 哈密市| 苗栗县| 鄂州市|