JVM面試專題及答案

1.內存模型以及分區,需要詳細到每個區放什麼。

JVM 分爲堆區和棧區,還有方法區,初始化的對象放在堆裏面,引用放在棧裏面, class 類信息常量池(static 常量和 static 變量)等放在方法區
方法區:主要是存儲類信息,常量池(static 常量和 static 變量),編譯後的代碼(字 節碼)等數據 
:初始化的對象,成員變量 (那種非 static 的變量),所有的對象實例和數組都要 在堆上分配 
:棧的結構是棧幀組成的,調用一個方法就壓入一幀,幀上面存儲局部變量表,操 作數棧,方法出口等信息,局部變量表存放的是 8 大基礎類型加上一個應用類型,所 以還是一個指向地址的指針 
本地方法棧:主要爲 Native 方法服務 
程序計數器:記錄當前線程執行的行號

2.堆裏面的分區:Eden,survival (from+ to),老年代,各自的特點。

堆裏面分爲新生代和老生代(java8 取消了永久代,採用了 Metaspace),新生代包 含 Eden+Survivor 區,survivor 區裏面分爲 from 和 to 區,內存回收時,如果用的是復 制算法,從 from 複製到 to,當經過一次或者多次 GC 之後,存活下來的對象會被移動 到老年區,當 JVM 內存不夠用的時候,會觸發 Full GC,清理 JVM 老年區 當新生區滿了之後會觸發 YGC,先把存活的對象放到其中一個 Survice 區,然後進行垃圾清理。因爲如果僅僅清理需要刪除的對象,這樣會導致內存碎 片,因此一般會把 Eden 進行完全的清理,然後整理內存。那麼下次 GC 的時候, 就會使用下一個 Survive,這樣循環使用。如果有特別大的對象,新生代放不下, 就會使用老年代的擔保,直接放到老年代裏面。因爲 JVM 認爲,一般大對象的存 活時間一般比較久遠。
3.對象創建方法,對象的內存分配,對象的訪問定位。
new 一個對象

4.GC 的兩種判定方法:

引用計數法:指的是如果某個地方引用了這個對象就+1,如果失效了就-1,當爲 0 就 會回收但是 JVM 沒有用這種方式,因爲無法判定相互循環引用(A 引用 B,B 引用 A) 的情況
引用鏈法: 通過一種 GC ROOT 的對象(方法區中靜態變量引用的對象等-static 變 量)來判斷,如果有一條鏈能夠到達 GC ROOT 就說明,不能到達 GC ROOT 就說明 可以回收

5.SafePoint 是什麼

比如 GC 的時候必須要等到 Java 線程都進入到 safepoint 的時候 VMThread 才能開始 執行 GC,
1.循環的末尾 (防止大循環的時候一直不進入 safepoint,而其他線程在等待它進入 safepoint)
2.方法返回前
3.調用方法的 call 之後
4. 拋出異常的位置
6.GC 的三種收集方法:標記清除、標記整理、複製算法的原理與特點,分別用 在什麼地方,如果讓你優化收集方法,有什麼思路?
先標記,標記完畢之後再清除,效率不高,會產生碎片
複製算法:分爲 8:1 的 Eden 區和 survivor 區,就是上面談到的 YGC
標記整理:標記完畢之後,讓所有存活的對象向一端移動

7.GC 收集器有哪些?CMS 收集器與 G1 收集器的特點。

並行收集器:串行收集器使用一個單獨的線程進行收集,GC 時服務有停頓時間
串行收集器:次要回收中使用多線程來執行
CMS 收集器是基於“標記—清除”算法實現的,經過多次標記纔會被清除
G1 從整體來看是基於“標記—整理”算法實現的收集器,從局部(兩個 Region 之間) 上來看是基於“複製”算法實現的
8.Minor GC 與 Full GC 分別在什麼時候發生?
新生代內存不夠用時候發生 MGC 也叫 YGC,JVM 內存不夠的時候發生 FGC
9.幾種常用的內存調試工具:jmap、jstack、jconsole、jhat
jstack 可以看當前棧的情況,jmap 查看內存,jhat 進行 dump 堆的信息 mat(eclipse 的也要了解一下)
10. 類加載的幾個過程:
加載、驗證、準備、解析、初始化。然後是使用和卸載了
通過全限定名來加載生成 class 對象到內存中,然後進行驗證這個 class 文件,包括文 件格式校驗、元數據驗證,字節碼校驗等。準備是對這個對象分配內存。解析是將符 號引用轉化爲直接引用(指針引用),初始化就是開始執行構造器的代碼

11.JVM內存分哪幾個區,每個區的作用是什麼?

java虛擬機主要分爲以下一個區:
方法區:

  1. 有時候也成爲永久代,在該區內很少發生垃圾回收,但是並不代表不發生GC,在這裏
    進行的GC主要是對方法區裏的常量池和對類型的卸載
  2. 方法區主要用來存儲已被虛擬機加載的類的信息、常量、靜態變量和即時編譯器編譯後
    的代碼等數據。
  3. 該區域是被線程共享的。
  4. 方法區裏有一個運行時常量池,用於存放靜態編譯產生的字面量和符號引用。該常量池具有動態性,也就是說常量並不一定是編譯時確定,運行時生成的常量也會存在這個常量池中。

虛擬機棧:

  1. 虛擬機棧也就是我們平常所稱的棧內存,它爲java方法服務,每個方法在執行的時候都會創建一個棧幀,用於存儲局部變量表、操作數棧、動態鏈接和方法出口等信息。
  2. 虛擬機棧是線程私有的,它的生命週期與線程相同。
  3. 局部變量表裏存儲的是基本數據類型、returnAddress類型(指向一條字節碼指令的地址)和對象引用,這個對象引用有可能是指向對象起始地址的一個指針,也有可能是代表對象的句柄或者與對象相關聯的位置。局部變量所需的內存空間在編譯器間確定
    4.操作數棧的作用主要用來存儲運算結果以及運算的操作數,它不同於局部變量表通過索引來訪問,而是壓棧和出棧的方式
    5.每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程中的動態連接.動態鏈接就是將常量池中的符號引用在運行期轉化爲直接引用。

本地方法棧
本地方法棧和虛擬機棧類似,只不過本地方法棧爲Native方法服務。

java堆是所有線程所共享的一塊內存,在虛擬機啓動時創建,幾乎所有的對象實例都在這裏創建,因此該區域經常發生垃圾回收操作。
程序計數器
內存空間小,字節碼解釋器工作時通過改變這個計數值可以選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理和線程恢復等功能都需要依賴這個計數器完成。該內存區域是唯一一個java虛擬機規範沒有規定任何OOM情況的區域。

12.如和判斷一個對象是否存活?(或者GC對象的判定方 法)

判斷一個對象是否存活有兩種方法:

  • 引用計數法
    所謂引用計數法就是給每一個對象設置一個引用計數器,每當有一個地方引用這個對象時,就將計數器加一,引用失效時,計數器就減一。當一個對象的引用計數器爲零時,說明此對象沒有被引用,也就是“死對象”,將會被垃圾回收.
    引用計數法有一個缺陷就是無法解決循環引用問題,也就是說當對象A引用對象B,對象B又引用者對象A,那麼此時A,B對象的引用計數器都不爲零,也就造成無法完成垃圾回收,所以主流的虛擬機都沒有采用這種算法。
    2.可達性算法(引用鏈法)
    該算法的思想是:從一個被稱爲GC Roots的對象開始向下搜索,如果一個對象到GC
    Roots沒有任何引用鏈相連時,則說明此對象不可用。
    在java中可以作爲GC Roots的對象有以下幾種:

    • 虛擬機棧中引用的對象
    • 方法區類靜態屬性引用的對象
    • 方法區常量池引用的對象
    • 本地方法棧JNI引用的對象

雖然這些算法可以判定一個對象是否能被回收,但是當滿足上述條件時,一個對象比不一定會被回收。當一個對象不可達GC Root時,這個對象並 不會立馬被回收,而是出於一個死緩的階段,若要被真正的回收需要經歷兩次標記
如果對象在可達性分析中沒有與GC Root的引用鏈,那麼此時就會被第一次標記並且進行一次篩選,篩選的條件是是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法或者已被虛擬機調用過,那麼就認爲是沒必要的。
如果該對象有必要執行finalize()方法,那麼這個對象將會放在一個稱爲F-Queue的對隊列中,虛擬機會觸發一個Finalize()線程去執行,此線程是低優先級的,並且虛擬機不會承諾一直等待它運行完,這是因爲如果finalize()執行緩慢或者發生了死鎖,那麼就會造成FQueue隊列一直等待,造成了內存回收系統的崩潰。GC對處於F-Queue中的對象進行第二次被標記,這時,該對象將被移除”即將回收”集合,等待回收。

13.簡述java垃圾回收機制?

在java中,程序員是不需要顯示的去釋放一個對象的內存的,而是由虛擬機自行執行。在JVM中,有一個垃圾回收線程,它是低優先級的,在正常情況下是不會執行的,只有在虛擬機空閒或者當前堆內存不足時,纔會觸發執行,掃面那些沒有被任何引用的對象,並將它們添加到要回收的集合中,進行回收。

14.java中垃圾收集的方法有哪些?

  1. 標記-清除:
    這是垃圾收集算法中最基礎的,根據名字就可以知道,它的思想就是標記哪些要被回收的對象,然後統一回收。這種方法很簡單,但是會有兩個主要問題:1.效率不高,標記和清除的效率都很低;2.會產生大量不連續的內存碎片,導致以後程序在分配較大的對象時,由於沒有充足的連續內存而提前觸發一次GC動作。
  2. 複製算法:
    爲了解決效率問題,複製算法將可用內存按容量劃分爲相等的兩部分,然後每次只使用其中的一塊,當一塊內存用完時,就將還存活的對象複製到第二塊內存上,然後一次性清楚完第一塊內存,再將第二塊上的對象複製到第一塊。但是這種方式,內存的代價太高,每次基本上都要浪費一般的內存。
    於是將該算法進行了改進,內存區域不再是按照1:1去劃分,而是將內存劃分爲8:1:1三部分,較大那份內存交Eden區,其餘是兩塊較小的內存區叫Survior區。
    每次都會優先使用Eden區,若Eden區滿,就將對象複製到第二塊內存區上,然後清除Eden區,如果此時存活的對象太多,以至於Survivor不夠時,會將這些對
    象通過分配擔保機制複製到老年代中。(java堆又分爲新生代和老年代)
  3. 標記-整理
    該算法主要是爲了解決標記-清除,產生大量內存碎片的問題;當對象存活率較高
    時,也解決了複製算法的效率問題。它的不同之處就是在清除對象的時候現將可回收對象移動到一端,然後清除掉端邊界以外的對象,這樣就不會產生內存碎片了。
  4. 分代收集
    現在的虛擬機垃圾收集大多采用這種方式,它根據對象的生存週期,將堆分爲新生代和老年代。在新生代中,由於對象生存期短,每次回收都會有大量對象死去,那麼這時就採用複製算法。老年代裏的對象存活率較高,沒有額外的空間進行分配擔保,所以可以使用標記-整理 或者 標記-清除。

15.java內存模型

java內存模型(JMM)是線程間通信的控制機制.JMM定義了主內存和線程之間抽象關係。
線程之間的共享變量存儲在主內存(main memory)中,每個線程都有一個私有的本地
內存(local memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存,寫緩衝區,寄存器以及其他的硬件和編譯器優化。
線程A與線程B之間如要通信的話,必須要經歷下面2個步驟:

  1. 首先,線程A把本地內存A中更新過的共享變量刷新到主內存中去。
  2. 然後,線程B到主內存中去讀取線程A之前已更新過的共享變量。

16.java類加載過程?

java類加載需要經歷一下7個過程:
加載
加載時類加載的第一個過程,在這個階段,將完成一下三件事情:

  1. 通過一個類的全限定名獲取該類的二進制流。
  2. 將該二進制流中的靜態存儲結構轉化爲方法去運行時數據結構。
  3. 在內存中生成該類的Class對象,作爲該類的數據訪問入口。

驗證
驗證的目的是爲了確保Class文件的字節流中的信息不回危害到虛擬機.在該階段主要完成以下四鍾驗證:

  1. 文件格式驗證:驗證字節流是否符合Class文件的規範,如主次版本號是否在當前虛擬機範圍內,常量池中的常量是否有不被支持的類型.
  2. 元數據驗證:對字節碼描述的信息進行語義分析,如這個類是否有父類,是否集成了不被繼承的類等。
  3. 字節碼驗證:是整個驗證過程中最複雜的一個階段,通過驗證數據流和控制流的分析,確定程序語義是否正確,主要針對方法體的驗證。如:方法中的類型轉換是否正確,跳轉指令是否正確等。
  4. 符號引用驗證:這個動作在後面的解析過程中發生,主要是爲了確保解析動作能正確執行。

準備
準備階段是爲類的靜態變量分配內存並將其初始化爲默認值,這些內存都將在方法區中進行分配。準備階段不分配類中的實例變量的內存,實例變量將會在對象實例化時隨着對象一起分配在Java堆中。
public static int value=123; // 在準備階段 value 初始值爲 0 。在初始化階段纔會變
爲 123 。

解析
該階段主要完成符號引用到直接引用的轉換動作。解析動作並不一定在初始化動作完成之前,也有可能在初始化之後。
初始化
初始化時類加載的最後一步,前面的類加載過程,除了在加載階段用戶應用程序可以通過自定義類加載器參與之外,其餘動作完全由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的Java程序代碼。

17. 簡述java類加載機制?

虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗,解析和初始化,最終形成可以被虛擬機直接使用的java類型。

18. 類加載器雙親委派模型機制?

當一個類收到了類加載請求時,不會自己先去加載這個類,而是將其委派給父類,由父類去加載,如果此時父類不能加載,反饋給子類,由子類去完成類的加載。

19.什麼是類加載器,類加載器有哪些?

實現通過類的權限定名獲取該類的二進制字節流的代碼塊叫做類加載器。
主要有一下四種類加載器:

  1. 啓動類加載器(Bootstrap ClassLoader)用來加載java核心類庫,無法被java程序直接引用。
  2. 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。
  3. 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader()來獲取它。
  4. 用戶自定義類加載器,通過繼承 java.lang.ClassLoader類的方式實現。

20.簡述java內存分配與回收策率以及Minor GC和 Major GC

  1. 對象優先在堆的Eden區分配。
  2. 大對象直接進入老年代.
  3. 長期存活的對象將直接進入老年代.
    當Eden區沒有足夠的空間進行分配時,虛擬機會執行一次Minor GC.Minor Gc通
    常發生在新生代的Eden區,在這個區的對象生存期短,往往發生Gc的頻率較高,回收速度比較快;Full Gc/Major GC 發生在老年代,一般情況下,觸發老年代GC的時候不會觸發Minor GC,但是通過配置,可以在Full GC之前進行一次Minor
    GC這樣可以加快老年代的回收速度。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章