一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - JAVA JVM面試題總結

JAVA JVM面試題總結

2021-12-01 13:26程序員cxuan Java教程

JVM 可以屏蔽與具體操作系統平臺相關的信息,使 Java 程序只需生成在 Java 虛擬機上運行的目標代碼,就可以在不同的平臺上運行。這篇文章主要介紹了JAVA JVM面試題總結,大家可以參考一下

JVM 的主要作用是什么?

JVM 就是 Java Virtual Machine(Java虛擬機)的縮寫,JVM 屏蔽了與具體操作系統平臺相關的信息,使 Java 程序只需生成在 Java 虛擬機上運行的目標代碼 (字節碼),就可以在不同的平臺上運行。

請你描述一下 Java 的內存區域?

JVM 在執行 Java 程序的過程中會把它管理的內存分為若干個不同的區域,這些組成部分有些是線程私有的,有些則是線程共享的,Java 內存區域也叫做運行時數據區,它的具體劃分如下:

JAVA JVM面試題總結

  • 虛擬機棧 : Java 虛擬機棧是線程私有的數據區,Java 虛擬機棧的生命周期與線程相同,虛擬機棧也是局部變量的存儲位置。方法在執行過程中,會在虛擬機棧中創建一個 棧幀(stack frame)。每個方法執行的過程就對應了一個入棧和出棧的過程。

JAVA JVM面試題總結

  • 本地方法棧: 本地方法棧也是線程私有的數據區,本地方法棧存儲的區域主要是 Java 中使用 native 關鍵字修飾的方法所存儲的區域。
  • 程序計數器:程序計數器也是線程私有的數據區,這部分區域用于存儲線程的指令地址,用于判斷線程的分支、循環、跳轉、異常、線程切換和恢復等功能,這些都通過程序計數器來完成。
  • 方法區:方法區是各個線程共享的內存區域,它用于存儲虛擬機加載的 類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。
  • 堆:堆是線程共享的數據區,堆是 JVM 中最大的一塊存儲區域,所有的對象實例都會分配在堆上。JDK 1.7后,字符串常量池從永久代中剝離出來,存放在堆中。

堆空間的內存分配(默認情況下):

老年代 : 三分之二的堆空間

年輕代 : 三分之一的堆空間

eden 區: 8/10 的年輕代空間

survivor 0 : 1/10 的年輕代空間

survivor 1 : 1/10 的年輕代空間

命令行上執行如下命令,會查看默認的 JVM 參數。

?
1
java -XX:+PrintFlagsFinal -version

輸出的內容非常多,但是只有兩行能夠反映出上面的內存分配結果

JAVA JVM面試題總結

JAVA JVM面試題總結

JAVA JVM面試題總結

  • 運行時常量池:運行時常量池又被稱為 Runtime Constant Pool,這塊區域是方法區的一部分,它的名字非常有意思,通常被稱為 非堆。它并不要求常量一定只有在編譯期才能產生,也就是并非編譯期間將常量放在常量池中,運行期間也可以將新的常量放入常量池中,String 的 intern 方法就是一個典型的例子。

請你描述一下 Java 中的類加載機制?

Java 虛擬機負責把描述類的數據從 Class 文件加載到系統內存中,并對類的數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的 Java 類型,這個過程被稱之為 Java 的類加載機制。

一個類從被加載到虛擬機內存開始,到卸載出內存為止,一共會經歷下面這些過程。

JAVA JVM面試題總結

類加載機制一共有五個步驟,分別是加載、鏈接、初始化、使用和卸載階段,這五個階段的順序是確定的。

其中鏈接階段會細分成三個階段,分別是驗證、準備、解析階段,這三個階段的順序是不確定的,這三個階段通常交互進行。解析階段通常會在初始化之后再開始,這是為了支持 Java 語言的運行時綁定特性(也被稱為動態綁定)。

下面我們就來聊一下這幾個過程。

加載

關于什么時候開始加載這個過程,《Java 虛擬機規范》并沒有強制約束,所以這一點我們可以自由實現。加載是整個類加載過程的第一個階段,在這個階段,Java 虛擬機需要完成三件事情:

  • 通過一個類的全限定名來獲取定義此類的二進制字節流。
  • 將這個字節流表示的一種存儲結構轉換為運行時數據區中方法區的數據結構。
  • 在內存中生成一個 Class 對象,這個對象就代表了這個數據結構的訪問入口。

《Java 虛擬機規范》并未規定全限定名是如何獲取的,所以現在業界有很多獲取全限定名的方式:

  • 從 ZIP 包中讀取,最終會改變為 JAR、EAR、WAR 格式。
  • 從網絡中獲取,最常見的應用就是 Web Applet。
  • 運行時動態生成,使用最多的就是動態代理技術。
  • 由其他文件生成,比如 JSP 應用場景,由 JSP 文件生成對應的 Class 文件。
  • 從數據庫中讀取,這種場景就比較小了。
  • 可以從加密文件中獲取,這是典型的防止 Class 文件被反編譯的保護措施。

加載階段既可以使用虛擬機內置的引導類加載器來完成,也可以使用用戶自定義的類加載器來完成。程序員可以通過自己定義類加載器來控制字節流的訪問方式。

數組的加載不需要通過類加載器來創建,它是直接在內存中分配,但是數組的元素類型(數組去掉所有維度的類型)最終還是要靠類加載器來完成加載。

驗證

加載過后的下一個階段就是驗證,因為我們上一步講到在內存中生成了一個 Class 對象,這個對象是訪問其代表數據結構的入口,所以這一步驗證的工作就是確保 Class 文件的字節流中的內容符合《Java 虛擬機規范》中的要求,保證這些信息被當作代碼運行后,它不會威脅到虛擬機的安全。

驗證階段主要分為四個階段的檢驗:

文件格式驗證。

元數據驗證。

字節碼驗證。

符號引用驗證。

文件格式驗證

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

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

實際上驗證點遠遠不止有這些,上面這些只是從 HotSpot 源碼中摘抄的一小段內容。

元數據驗證

這一階段主要是對字節碼描述的信息進行語義分析,以確保描述的信息符合《Java 語言規范》,驗證點包括

  • 驗證的類是否有父類(除了 Object 類之外,所有的類都應該有父類)。
  • 要驗證類的父類是否繼承了不允許繼承的類。
  • 如果這個類不是抽象類,那么這個類是否實現了父類或者接口中要求的所有方法。
  • 是否覆蓋了 final 字段,是否出現了不符合規定的重載等。

需要記住這一階段只是對《Java 語言規范》的驗證。

字節碼驗證

字節碼驗證階段是最復雜的一個階段,這個階段主要是確定程序語意是否合法、是否是符合邏輯的。這個階段主要是對類的方法體(Class 文件中的 Code 屬性)進行校驗分析。這部分驗證包括

  • 確保操作數棧的數據類型和實際執行時的數據類型是否一致。
  • 保證任何跳轉指令不會跳出到方法體外的字節碼指令上。
  • 保證方法體中的類型轉換是有效的,例如可以把一個子類對象賦值給父類數據類型,但是不能把父類數據類型賦值給子類等諸如此不安全的類型轉換。
  • 其他驗證。

如果沒有通過字節碼驗證,就說明驗證出問題。但是不一定通過了字節碼驗證,就能保證程序是安全的。

符號引用驗證

最后一個階段的校驗行為發生在虛擬機將符號引用轉換為直接引用的時候,這個轉化將在連接的第三個階段,即解析階段中發生。符號引用驗證可以看作是對類自身以外的各類信息進行匹配性校驗,這個驗證主要包括

  • 符號引用中的字符串全限定名是否能找到對應的類。
  • 指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段。
  • 符號引用的類、字段方法的可訪問性是否可被當前類所訪問。
  • 其他驗證。

這一階段主要是確保解析行為能否正常執行,如果無法通過符號引用驗證,就會出現類似 IllegalAccessErrorNoSuchFieldErrorNoSuchMethodError 等錯誤。

驗證階段對于虛擬機來說非常重要,如果能通過驗證,就說明你的程序在運行時不會產生任何影響。

準備

準備階段是為類中的變量分配內存并設置其初始值的階段,這些變量所使用的內存都應當在方法區中進行分配,在 JDK 7 之前,HotSpot 使用永久代來實現方法區,是符合這種邏輯概念的。而在 JDK 8 之后,變量則會隨著 Class 對象一起存放在 Java 堆中。

下面通常情況下的基本類型和引用類型的初始值

JAVA JVM面試題總結

除了"通常情況"下,還有一些"例外情況",如果類字段屬性中存在 ConstantValue 屬性,那就這個變量值在初始階段就會初始化為 ConstantValue 屬性所指定的初始值,比如

?
1
public static final int value = "666";

編譯時就會把 value 的值設置為 666。

解析

解析階段是 Java 虛擬機將常量池內的符號引用替換為直接引用的過程。

  • 符號引用:符號引用以一組符號來描述所引用的目標。符號引用可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可,符號引用和虛擬機的布局無關。
  • 直接引用:直接引用可以直接指向目標的指針、相對便宜量或者一個能間接定位到目標的句柄。直接引用和虛擬機的布局是相關的,不同的虛擬機對于相同的符號引用所翻譯出來的直接引用一般是不同的。如果有了直接引用,那么直接引用的目標一定被加載到了內存中。

這樣說你可能還有點不明白,我再換一種說法:

在編譯的時候一個每個 Java 類都會被編譯成一個 class 文件,但在編譯的時候虛擬機并不知道所引用類的地址,所以就用符號引用來代替,而在這個解析階段就是為了把這個符號引用轉化成為真正的地址的階段。

《Java 虛擬機規范》并未規定解析階段發生的時間,只要求了在 anewarray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、ldc2_w、multianewarray、new、putfield 和 putstatic 這 17 個用于操作符號引用的字節碼指令之前,先對所使用的符號引用進行解析。

解析也分為四個步驟

類或接口的解析

字段解析

方法解析

接口方法解析

初始化

初始化是類加載過程的最后一個步驟,在之前的階段中,都是由 Java 虛擬機占主導作用,但是到了這一步,卻把主動權移交給應用程序。

對于初始化階段,《Java 虛擬機規范》嚴格規定了只有下面這六種情況下才會觸發類的初始化。

  • 在遇到 new、getstatic、putstatic 或者 invokestatic 這四條字節碼指令時,如果沒有進行過初始化,那么首先觸發初始化。通過這四個字節碼的名稱可以判斷,這四條字節碼其實就兩個場景,調用 new 關鍵字的時候進行初始化、讀取或者設置一個靜態字段的時候、調用靜態方法的時候。
  • 在初始化類的時候,如果父類還沒有初始化,那么就需要先對父類進行初始化。
  • 在使用 java.lang.reflect 包的方法進行反射調用的時候。
  • 當虛擬機啟動時,用戶需要指定執行主類的時候,說白了就是虛擬機會先初始化 main 方法這個類。
  • 在使用 JDK 7 新加入的動態語言支持時,如果一個 jafva.lang.invoke.MethodHandle 實例最后的解析結果為 REF_getstatic、REF_putstatic、REF_invokeStatic、REF_newInvokeSpecial 四種類型的方法句柄,并且這個方法句柄對應的類沒有進行過初始化,需要先對其進行初始化。
  • 當一個接口中定義了 JDK 8 新加入的默認方法(被 default 關鍵字修飾的接口方法)時,如果有這個借口的實現類發生了初始化,那該接口要在其之前被初始化。

其實上面只有前四個大家需要知道就好了,后面兩個比較冷門。

如果說要答類加載的話,其實聊到這里已經可以了,但是為了完整性,我們索性把后面兩個過程也來聊一聊。

使用

這個階段沒什么可說的,就是初始化之后的代碼由 JVM 來動態調用執行。

卸載

當代表一個類的 Class 對象不再被引用,那么 Class 對象的生命周期就結束了,對應的在方法區中的數據也會被卸載。

??但是需要注意一點:JVM 自帶的類加載器裝載的類,是不會卸載的,由用戶自定義的類加載器加載的類是可以卸載的。

在 JVM 中,對象是如何創建的?

如果要回答對象是怎么創建的,我們一般想到的回答是直接 new 出來就行了,這個回答不僅局限于編程中,也融入在我們生活中的方方面面。

但是遇到面試的時候你只回答一個"new 出來就行了"顯然是不行的,因為面試更趨向于讓你解釋當程序執行到 new 這條指令時,它的背后發生了什么。

所以你需要從 JVM 的角度來解釋這件事情。

當虛擬機遇到一個 new 指令時(其實就是字節碼),首先會去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用所代表的類是否已經被加載、解析和初始化。

因為此時很可能不知道具體的類是什么,所以這里使用的是符號引用。

如果發現這個類沒有經過上面類加載的過程,那么就執行相應的類加載過程。

類檢查完成后,接下來虛擬機將會為新生對象分配內存,對象所需的大小在類加載完成后便可確定(我會在下面的面試題中介紹)。

分配內存相當于是把一塊固定的內存塊從堆中劃分出來。劃分出來之后,虛擬機會將分配到的內存空間都初始化為零值,如果使用了 TLAB(本地線程分配緩沖),這一項初始化工作可以提前在 TLAB 分配時進行。這一步操作保證了對象實例字段在 Java 代碼中可以不賦值就能直接使用。

接下來,Java 虛擬機還會對對象進行必要的設置,比如確定對象是哪個類的實例、對象的 hashcode、對象的 gc 分代年齡信息。這些信息存放在對象的對象頭(Object Header)中。

如果上面的工作都做完后,從虛擬機的角度來說,一個新的對象就創建完畢了;但是對于程序員來說,對象創建才剛剛開始,因為構造函數,即 Class 文件中的 <init>() 方法還沒有執行,所有字段都為默認的零值。new 指令之后才會執行 <init>() 方法,然后按照程序員的意愿對對象進行初始化,這樣一個對象才可能被完整的構造出來。

內存分配方式有哪些呢?

在類加載完成后,虛擬機需要為新生對象分配內存,為對象分配內存相當于是把一塊確定的區域從堆中劃分出來,這就涉及到一個問題,要劃分的堆區是否規整

假設 Java 堆中內存是規整的,所有使用過的內存放在一邊,未使用的內存放在一邊,中間放著一個指針,這個指針為分界指示器。那么為新對象分配內存空間就相當于是把指針向空閑的空間挪動對象大小相等的距離,這種內存分配方式叫做指針碰撞(Bump The Pointer)

如果 Java 堆中的內存并不是規整的,已經被使用的內存和未被使用的內存相互交錯在一起,這種情況下就沒有辦法使用指針碰撞,這里就要使用另外一種記錄內存使用的方式:空閑列表(Free List),空閑列表維護了一個列表,這個列表記錄了哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,并更新列表上的記錄。

所以,上述兩種分配方式選擇哪個,取決于 Java 堆是否規整來決定。在一些垃圾收集器的實現中,Serial、ParNew 等帶壓縮整理過程的收集器,使用的是指針碰撞;而使用 CMS 這種基于清除算法的收集器時,使用的是空閑列表,具體的垃圾收集器我們后面會聊到。

請你說一下對象的內存布局?

hotspot 虛擬機中,對象在內存中的布局分為三塊區域:

對象頭(Header)

實例數據(Instance Data)

對齊填充(Padding)

這三塊區域的內存分布如下圖所示

JAVA JVM面試題總結

我們來詳細介紹一下上面對象中的內容。

對象頭 Header

對象頭 Header 主要包含 MarkWord 和對象指針 Klass Pointer,如果是數組的話,還要包含數組的長度。

JAVA JVM面試題總結

在 32 位的虛擬機中 MarkWord ,Klass Pointer 和數組長度分別占用 32 位,也就是 4 字節。

如果是 64 位虛擬機的話,MarkWord ,Klass Pointer 和數組長度分別占用 64 位,也就是 8 字節。

在 32 位虛擬機和 64 位虛擬機的 Mark Word 所占用的字節大小不一樣,32 位虛擬機的 Mark Word 和 Klass Pointer 分別占用 32 bits 的字節,而 64 位虛擬機的 Mark Word 和 Klass Pointer 占用了64 bits 的字節,下面我們以 32 位虛擬機為例,來看一下其 Mark Word 的字節具體是如何分配的。

JAVA JVM面試題總結

用中文翻譯過來就是

JAVA JVM面試題總結

  • 無狀態也就是無鎖的時候,對象頭開辟 25 bit 的空間用來存儲對象的 hashcode ,4 bit 用于存放分代年齡,1 bit 用來存放是否偏向鎖的標識位,2 bit 用來存放鎖標識位為 01。
  • 偏向鎖 中劃分更細,還是開辟 25 bit 的空間,其中 23 bit 用來存放線程ID,2bit 用來存放 epoch,4bit 存放分代年齡,1 bit 存放是否偏向鎖標識, 0 表示無鎖,1 表示偏向鎖,鎖的標識位還是 01。
  • 輕量級鎖中直接開辟 30 bit 的空間存放指向棧中鎖記錄的指針,2bit 存放鎖的標志位,其標志位為 00。
  • 重量級鎖中和輕量級鎖一樣,30 bit 的空間用來存放指向重量級鎖的指針,2 bit 存放鎖的標識位,為 11。
  • GC標記開辟 30 bit 的內存空間卻沒有占用,2 bit 空間存放鎖標志位為 11。

其中無鎖和偏向鎖的鎖標志位都是 01,只是在前面的 1 bit 區分了這是無鎖狀態還是偏向鎖狀態。

關于為什么這么分配的內存,我們可以從 OpenJDK 中的markOop.hpp類中的枚舉窺出端倪

JAVA JVM面試題總結

來解釋一下

  • age_bits 就是我們說的分代回收的標識,占用4字節
  • lock_bits 是鎖的標志位,占用2個字節
  • biased_lock_bits 是是否偏向鎖的標識,占用1個字節。
  • max_hash_bits 是針對無鎖計算的 hashcode 占用字節數量,如果是 32 位虛擬機,就是 32 - 4 - 2 -1 = 25 byte,如果是 64 位虛擬機,64 - 4 - 2 - 1 = 57 byte,但是會有 25 字節未使用,所以 64 位的hashcode 占用 31 byte。
  • hash_bits 是針對 64 位虛擬機來說,如果最大字節數大于 31,則取 31,否則取真實的字節數
  • cms_bits 我覺得應該是不是 64 位虛擬機就占用 0 byte,是 64 位就占用 1byteepoch_bits 就是
  • epoch 所占用的字節大小,2 字節。

在上面的虛擬機對象頭分配表中,我們可以看到有幾種鎖的狀態:無鎖(無狀態),偏向鎖,輕量級鎖,重量級鎖,其中輕量級鎖和偏向鎖是 JDK1.6 中對 synchronized 鎖進行優化后新增加的,其目的就是為了大大優化鎖的性能,所以在 JDK 1.6 中,使用 synchronized 的開銷也沒那么大了。其實從鎖有無鎖定來講,還是只有無鎖和重量級鎖,偏向鎖和輕量級鎖的出現就是增加了鎖的獲取性能而已,并沒有出現新的鎖。

所以我們的重點放在對 synchronized 重量級鎖的研究上,當 monitor 被某個線程持有后,它就會處于鎖定狀態。在 HotSpot 虛擬機中,monitor 的底層代碼是由 ObjectMonitor 實現的,其主要數據結構如下(位于 HotSpot 虛擬機源碼 ObjectMonitor.hpp 文件,C++ 實現的)

JAVA JVM面試題總結

這段 C++ 中需要注意幾個屬性:_WaitSet 、 _EntryList 和 _Owner,每個等待獲取鎖的線程都會被封裝稱為 ObjectWaiter 對象。

JAVA JVM面試題總結

_Owner 是指向了 ObjectMonitor 對象的線程,而 _WaitSet 和 _EntryList 就是用來保存每個線程的列表。

那么這兩個列表有什么區別呢?這個問題我和你聊一下鎖的獲取流程你就清楚了。

鎖的兩個列表

當多個線程同時訪問某段同步代碼時,首先會進入 _EntryList 集合,當線程獲取到對象的 monitor 之后,就會進入 _Owner 區域,并把 ObjectMonitor 對象的 _Owner 指向為當前線程,并使 _count + 1,如果調用了釋放鎖(比如 wait)的操作,就會釋放當前持有的 monitor ,owner = null, _count - 1,同時這個線程會進入到 _WaitSet 列表中等待被喚醒。如果當前線程執行完畢后也會釋放 monitor 鎖,只不過此時不會進入 _WaitSet 列表了,而是直接復位 _count 的值。

JAVA JVM面試題總結

Klass Pointer 表示的是類型指針,也就是對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

你可能不是很理解指針是個什么概念,你可以簡單理解為指針就是指向某個數據的地址。

JAVA JVM面試題總結

實例數據 Instance Data

實例數據部分是對象真正存儲的有效信息,也是代碼中定義的各個字段的字節大小,比如一個 byte 占 1 個字節,一個 int 占用 4 個字節。

對齊 Padding

對齊不是必須存在的,它只起到了占位符(%d, %c 等)的作用。這就是 JVM 的要求了,因為 HotSpot JVM 要求對象的起始地址必須是 8 字節的整數倍,也就是說對象的字節大小是 8 的整數倍,不夠的需要使用 Padding 補全。

對象訪問定位的方式有哪些?

我們創建一個對象的目的當然就是為了使用它,但是,一個對象被創建出來之后,在 JVM 中是如何訪問這個對象的呢?一般有兩種方式:通過句柄訪問和 通過直接指針訪問。

  • 如果使用句柄訪問方式的話,Java 堆中可能會劃分出一塊內存作為句柄池,引用(reference)中存儲的是對象的句柄地址,而句柄中包含了對象的實例數據與類型數據各自具體的地址信息。如下圖所示。

JAVA JVM面試題總結

  • 如果使用直接指針訪問的話,Java 堆中對象的內存布局就會有所區別,棧區引用指示的是堆中的實例數據的地址,如果只是訪問對象本身的話,就不會多一次直接訪問的開銷,而對象類型數據的指針是存在于方法區中,如果定位的話,需要多一次直接定位開銷。如下圖所示

JAVA JVM面試題總結

這兩種對象訪問方式各有各的優勢,使用句柄最大的好處就是引用中存儲的是句柄地址,對象移動時只需改變句柄的地址就可以,而無需改變對象本身。

使用直接指針來訪問速度更快,它節省了一次指針定位的時間開銷,由于對象訪問在 Java 中非常頻繁,因為這類的開銷也是值得優化的地方。

上面聊到了對象的兩種數據,一種是對象的實例數據,這沒什么好說的,就是對象實例字段的數據,一種是對象的類型數據,這個數據說的是對象的類型、父類、實現的接口和方法等。

如何判斷對象已經死亡?

我們大家知道,基本上所有的對象都在堆中分布,當我們不再使用對象的時候,垃圾收集器會對無用對象進行回收??,那么 JVM 是如何判斷哪些對象已經是"無用對象"的呢?

這里有兩種判斷方式,首先我們先來說第一種:引用計數法

引用計數法的判斷標準是這樣的:在對象中添加一個引用計數器,每當有一個地方引用它時,計數器的值就會加一;當引用失效時,計數器的值就會減一;只要任何時刻計數器為零的對象就是不會再被使用的對象。雖然這種判斷方式非常簡單粗暴,但是往往很有用,不過,在 Java 領域,主流的 Hotspot 虛擬機實現并沒有采用這種方式,因為引用計數法不能解決對象之間的循環引用問題。

循環引用問題簡單來講就是兩個對象之間互相依賴著對方,除此之外,再無其他引用,這樣虛擬機無法判斷引用是否為零從而進行垃圾回收操作。

還有一種判斷對象無用的方法就是可達性分析算法

當前主流的 JVM 都采用了可達性分析算法來進行判斷,這個算法的基本思路就是通過一系列被稱為GC Roots的根對象作為起始節點集,從這些節點開始,根據引用關系向下搜索,搜索過程走過的路徑被稱為引用鏈(Reference Chain),如果某個對象到 GC Roots 之間沒有任何引用鏈相連接,或者說從 GC Roots 到這個對象不可達時,則證明此這個對象是無用對象,需要被垃圾回收。

這種引用方式如下

JAVA JVM面試題總結

如上圖所示,從枚舉根節點 GC Roots 開始進行遍歷,object 1 、2、3、4 是存在引用關系的對象,而 object 5、6、7 之間雖然有關聯,但是它們到 GC Roots 之間是不可大的,所以被認為是可以回收的對象。

  • 在 Java 技術體系中,可以作為 GC Roots 進行檢索的對象主要有
  • 在虛擬機棧(棧幀中的本地變量表)中引用的對象。
  • 方法區中類靜態屬性引用的對象,比如 Java 類的引用類型靜態變量。
  • 方法區中常量引用的對象,比如字符串常量池中的引用。
  • 在本地方法棧中 JNI 引用的對象。
  • JVM 內部的引用,比如基本數據類型對應的 Class 對象,一些異常對象比如 NullPointerException、OutOfMemoryError 等,還有系統類加載器。
  • 所有被 synchronized 持有的對象。
  • 還有一些 JVM 內部的比如 JMXBean、JVMTI 中注冊的回調,本地代碼緩存等。
  • 根據用戶所選的垃圾收集器以及當前回收的內存區域的不同,還可能會有一些對象臨時加入,共同構成 GC Roots 集合。

雖然我們上面提到了兩種判斷對象回收的方法,但無論是引用計數法還是判斷 GC Roots 都離不開引用這一層關系。

這里涉及到到強引用、軟引用、弱引用、虛引用的引用關系,你可以閱讀作者的這一篇文章

小心點,別被當成垃圾回收了。

如何判斷一個不再使用的類?

判斷一個類型屬于"不再使用的類"需要滿足下面這三個條件

  • 這個類所有的實例已經被回收,也就是 Java 堆中不存在該類及其任何這個類字累的實例
  • 加載這個類的類加載器已經被回收,但是類加載器一般很難會被回收,除非這個類加載器是為了這個目的設計的,比如 OSGI、JSP 的重加載等,否則通常很難達成。
  • 這個類對應的 Class 對象沒有任何地方被引用,無法在任何時刻通過反射訪問這個類的屬性和方法。

虛擬機允許對滿足上面這三個條件的無用類進行回收操作。

到此這篇關于JAVA JVM面試題總結的文章就介紹到這了,更多相關JAVA JVM面試題內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://www.cnblogs.com/cxuanBlog/p/15178626.html

延伸 · 閱讀

精彩推薦
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
主站蜘蛛池模板: 日韩欧美亚洲一区精选 | 富士av105| 成人高清视频在线观看 | 欧美精品亚洲精品日韩专区va | 日本春菜花在线中文字幕 | 成人小视频在线免费观看 | 精品国产免费久久久久久 | 国内会所按摩推拿国产 | 亚洲成av人片在线观看天堂无码 | 精品无人区乱码1区2区3区免费 | 9久热久爱免费精品视频在线观看 | 国产码一区二区三区 | 亚洲系列第一页 | 黄在线观看www免费看 | 亚洲欧美日韩高清 | 国产精品亚洲va在线观看 | 国产无限免费观看黄网站 | 女人张开腿让男人做爽爽 | 青草福利在线 | 5151hh四虎国产精品 | 经典千人斩一区二区视频 | 免费国产白棉袜踩踏区域 | 亚洲精品国产精品国自产观看 | 高h折磨调教古代 | 日韩一区二区三区不卡视频 | 天天操天天干天天做 | 亚洲日本va午夜中文字幕 | 国产精品青青青高清在线观看 | 国产99视频精品免视看7 | 亚洲国产无线码在线观看 | 三级黄色片在线免费观看 | 国产成人精品曰本亚洲78 | 欧美一区二区三区在线观看不卡 | 娇喘高潮教室h | 国产大片51精品免费观看 | 亚洲男人天堂网址 | 日本另类z0zx高清 | 1024免费永久福利视频 | 成人网欧美亚洲影视图片 | 男女18一级大黄毛片免 | 美女扒开尿口让男生添 漫画 |