jvm面試題


https://blog.csdn.net/yanpenglei/article/details/119406377 參考

https://www.cnblogs.com/dolphin0520/p/3613043.html JVM的內存區域划分 寫的很好

 

 

 

 

 

1.  JVM 默認使用的java 虛擬機是HotSpot。

 

2. Java的雙親委托機制是什么?

 

它的意思是,除了頂層的啟動類加載器以外,其余的類加載器,在加載之前,都會委派給它的父加載器進行加載。這樣一層層向上傳遞,直到祖先們都無法勝任,它才會真正的加載。

 

Java默認是這種行為。當然Java中也有很多打破雙親行為的騷操作,比如SPI(JDBC驅動加載),OSGI等。

優點:

避免類的重復加載, 確保一個類的全局唯一性

Java 類隨着它的類加載器一起具備了一種帶有優先級的層級關系, 通過這種
層級關系可以避免類的重復加載, 當父親已經加載了該類時, 就沒有必要子
ClassLoader 再加載一次

 

2.JVM使用“類”的生命周期是:

裝載、鏈接、初始化、使用、卸載
1.裝載:
定要找到Class文件所在的全路徑,然后裝載到內存中J,ava又是一門面向對象的開發語這個過程用到了類加載器 ClassLoader。

2.連接 該階段又包含了驗證、准備和解析3個過程,如下。

(1)驗證

   校驗.class文件的正確性。

(2)准備

     2.1給static靜態變量分配內存,並初始化static的默認值。

    private static int a=10; 此時a變量會被賦值為默認值
     2.2 符號引用和直接引用
   符號引用:4f62 6a65 6374 ----->loading 二進制流是沒有辦法被cpu直接解析的 (.class  變量 方法等等里面全是二進制的)
  直接應用:0x00ab---0x00ba 物理內存
3.初始化:給static變量 賦予實際的值
private static int a=10; a=10;
4.使用:對象的初始化、對象的垃圾回收、對象的銷毀.

3、GC Roots 有哪些?

1、 GC Roots 是一組必須活躍的引用。用通俗的話來說,就是程序接下來通過直接引用或者間接引用,能夠訪問到的潛在被使用的對象。

2、 GC Roots 包括:Java 線程中,當前所有正在被調用的方法的引用類型參數、局部變量、臨時值等。也就是與我們棧幀相關的各種引用。所有當前被加載的 Java 類。Java 類的引用類型靜態變量。運行時常量池里的引用類型常量(String 或 Class 類型)。JVM 內部數據結構的一些引用,比如 sun.jvm.hotspot.memory.Universe 類。用於同步的監控對象,比如調用了對象的 wait() 方法。JNI handles,包括 global handles 和 local handles。

3、 這些 GC Roots 大體可以分為三大類,下面這種說法更加好記一些:活動線程相關的各種引用。類的靜態變量的引用。JNI 引用。

4、 有兩個注意點:我們這里說的是活躍的引用,而不是對象,對象是不能作為 GC Roots 的。GC 過程是找出所有活對象,並把其余空間認定為“無用”;而不是找出所有死掉的對象,並回收它們占用的空間。所以,哪怕 JVM 的堆非常的大,基於 tracing 的 GC 方式,回收速度也會非常快。

4.jvm怎么判斷哪些對象應該回收

 

  1 確定垃圾對象

 

引用計數算法和可達性分析算法。

因為引用計數法的算法是這樣的:在對象中添加一個引用計數器,每當一個地方引用它時,計數器就加一;當引用失效時,計數器值就減一;任何時刻計數器為零的對象就是不可能再被使用的。

但是這樣的算法有個問題,是什么呢?

不經意間來一波自問自答。讓面試官聽的一愣一愣的。

就是不能解決循環依賴的問題。

並拿着自己准備的紙和筆快速的畫出下面這樣的圖:

 

 

Object 1和Object 2其實都可以被回收,但是它們之間還有相互引用,所以它們各自的計數器為1,則還是不會被回收。

所以,Java虛擬機沒有采用引用計數法。它采用的是可達性分析算法。

可達性分析算法的思路就是通過一系列的“GC Roots”,也就是根對象作為起始節點集合,從根節點開始,根據引用關系向下搜索,搜索過程所走過的路徑稱為引用鏈,如果某個對象到GC Roots間沒有任何引用鏈相連。

用圖論的話來說就是從GC Roots到這個對象不可達時,則證明此對象是不可能再被使用的。所以此對象就是可以被回收的對象。

 

 

  2。垃圾收集算法進行垃圾收集

 

標記-清除算法

分為標記和清除階段,首先從每個 GC Roots 出發依次標記有引用關系的對象,最后清除沒有標記的對象。

執行效率不穩定,如果堆包含大量對象且大部分需要回收,必須進行大量標記清除,導致效率隨對象數量增長而降低。

存在內存空間碎片化問題,會產生大量不連續的內存碎片,導致以后需要分配大對象時容易觸發 Full GC。

標記-復制算法

為了解決內存碎片問題,將可用內存按容量划分為大小相等的兩塊,每次只使用其中一塊。當使用的這塊空間用完了,就將存活對象復制到另一塊,再把已使用過的內存空間一次清理掉。主要用於進行新生代。

實現簡單、運行高效,解決了內存碎片問題。代價是可用內存縮小為原來的一半,浪費空間。

HotSpot 把新生代划分為一塊較大的 Eden 和兩塊較小的 Survivor(s0,s1),每次分配內存只使用 Eden 和其中一塊 Survivor(s0和s1 永遠只有一個有值)。垃圾收集時將 Eden 和 Survivor 中仍然存活的對象一次性復制到另一塊 Survivor 上,然后直接清理掉 Eden 和已用過的那塊 Survivor。HotSpot 默認Eden 和 Survivor 的大小比例是 8:1,即每次新生代中可用空間為整個新生代的 90%。

標記-整理算法

標記-復制算法在對象存活率高時要進行較多復制操作,效率低。如果不想浪費空間,就需要有額外空間分配擔保,應對被使用內存中所有對象都存活的極端情況,所以老年代一般不使用此算法。

老年代使用標記-整理算法,標記過程與標記-清除算法一樣,但不直接清理可回收對象,而是讓所有存活對象都向內存空間一端移動,然后清理掉邊界以外的內存。

標記-清除與標記-整理的差異在於前者是一種非移動式算法而后者是移動式的。如果移動存活對象,尤其是在老年代這種每次回收都有大量對象存活的區域,是一種極為負重的操作,而且移動必須全程暫停用戶線程。如果不移動對象就會導致空間碎片問題,只能依賴更復雜的內存分配器和訪問器解決。

 

5. GC 的回收流程是怎么樣的。

 

 

6、說說Java 垃圾回收機制

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

7、分代收集算法

當前主流 VM 垃圾收集都采用”分代收集” (Generational Collection)算法, 這種算法會根據對象存活周期的不同將內存划分為幾塊, 如 JVM 中的新生代、老年代、永久代, 這樣就可以根據各年代特點分別采用最適當的 GC 算法。 不同分區,采用不同的GC算法。 具體算法看6.

7、JVM 內存區域

JVM 內存區域主要分為線程私有區域【程序計數器、虛擬機棧、本地方法區】、線程共享區域【JAVA 堆、方法區】、直接內存。

線程私有數據區域生命周期與線程相同, 依賴用戶線程的啟動/結束 而 創建/銷毀(在 Hotspot VM 內, 每個線程都與操作系統的本地線程直接映射, 因此這部分內存區域的存/否跟隨本地線程的生/死對應)。

線程共享區域隨虛擬機的啟動/關閉而創建/銷毀。

直接內存並不是 JVM 運行時數據區的一部分, 但也會被頻繁的使用: 在 JDK 1.4 引入的 NIO 提供了基於Channel與 Buffer的IO方式, 它可以使用Native函數庫直接分配堆外內存, 然后使用DirectByteBuffer 對象作為這塊內存的引用進行操作(詳見: Java I/O 擴展), 這樣就避免了在 Java堆和 Native 堆中來回復制數據, 因此在一些場景中可以顯著提高性能。

8、MinorGC,MajorGC、FullGC都什么時候發生?

MinorGC在年輕代空間不足的時候發生,MajorGC指的是老年代的GC,出現MajorGC一般經常伴有MinorGC。

FullGC有三種情況。

1、 當老年代無法再分配內存的時候

2、 元空間不足的時候

3、 顯示調用System.gc的時候。另外,像CMS一類的垃圾回收器,在MinorGC出現promotion failure的時候也會發生FullGC

9、垃圾收集器對比

1.Serial GC  Serial 即串行的意思,也就是說它以串行的方式執行,它是單線程的收集器,只會使用一個線程進行垃圾收集工作,GC 線程工作時,其它所有線程都將停止工作會產生 stop the world  。

  stop the world  停頓時間會相對比較長。

  結果:對於客戶端而言,響應時間變慢了。
新老代之分:
Serial(new) 標記復制算法  適用於新生代
Serial(Old)   標記整理算法、適用於老年代。
2. Parallel收集器  
  同樣是多線程的收集器,其它收集器目標是盡可能縮短垃圾收集時用戶線程的停頓時間,而它的目標是提高吞吐量(吞吐量 = 運行用戶程序的時間 / (運行用戶程序的時間 + 垃圾收集的時間))產生 stop the world。
Parallel(new) 標記復制算法  適用於新生代
Parallel(Old)   標記整理算法、適用於老年代。

3.CMS 收集器(只適用老年代)
CMS(Concurrent Mark Sweep),收集器幾乎占據着 JVM 老年代收集器的半壁江山,它划時代的意義就在於垃圾回收線程幾乎能做到與用戶線程同時工作。

使用標記-清除算法收集老年代垃圾。

工作流程主要有如下 4 個步驟:

初始標記: 僅僅只是標記一下 GC Roots 能直接關聯到的對象,速度很快,需要停頓(Stop-the-world)
並發標記: 進行 GC Roots Tracing 的過程,它在整個回收過程中耗時最長,不需要停頓
重新標記: 為了修正並發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,需要停頓(Stop-the-world)
並發清除: 清理垃圾,不需要停頓
在整個過程中耗時最長的並發標記和並發清除過程中,收集器線程都可以與用戶線程一起工作,不需要進行停頓。只有在初始標記,重新標記的時候會進行短暫的停頓。

但 CMS 收集器也有如下缺點:

吞吐量低
無法處理浮動垃圾
標記 - 清除算法帶來的內存空間碎片問題
顯式的使用該垃圾收集器作為老年代垃圾收集器的方式:-XX:+UseConcMarkSweepGC

4.G1垃圾收集器(適用新老年代)

https://blog.csdn.net/iva_brother/article/details/87886525

       把G1單獨拿出來的原因是其比較復雜,在JDK 1.7確立是項目目標,在JDK 7u2版本之后發布,並在JDK 9中成為了默認的垃圾回收器。通過“-XX:+UseG1GC”啟動參數即可指定使用G1 GC。

G1從整體看還是基於標記-清除算法的,但是局部上是基於復制算法的。這樣就意味者它空間整合做的比較好,因為不會產生空間碎片。G1還是並發與並行的,它能夠充分利用多CPU、多核的硬件環境來縮短“stop the world”的時間。G1還是分代收集的,但是G1不再像上文所述的垃圾收集器,需要分代配合不同的垃圾收集器,因為G1中的垃圾收集區域是“分區”(Region)的,不同的Region配置不同的垃圾回收算法。G1的分代收集和以上垃圾收集器不同的就是除了有年輕代的ygc,全堆掃描的full GC外,還有包含所有年輕代以及部分老年代Region的Mixed GC。G1還可預測停頓,通過調整參數,制定垃圾收集的最大停頓時間。

1分區的概念
G1的堆區在分代的基礎上,引入分區的概念。G1將堆分成了若干Region,以下和”分區”代表同一概念。(這些分區不要求是連續的內存空間)Region的大小可以通過G1HeapRegionSize參數進行設置,其必須是2的冪,范圍允許為1Mb到32Mb。 JVM的會基於堆內存的初始值和最大值的平均數計算分區的尺寸,平均的堆尺寸會分出約2000個Region。分區大小一旦設置,則啟動之后不會再變化。如下圖簡單畫了下G1分區模型。

 

 

JVM常見面試題
(1)內存泄漏與內存溢出的區別

內存溢出(out of memory),是指程序在申請內存時,沒有足夠的內存空間供其使用,出現out of memory;比如申請了一個integer,但給它存了long才能存下的數,那就是內存溢出。

內存泄露(memory leak),是指程序在申請內存后,無法釋放已申請的內存空間,一次內存泄露危害可以忽略,但內存泄露堆積后果很嚴重,無論多少內存,遲早會被占光。

memory leak會最終會導致out of memory!

(2)Young GC會有stw(stop‐the‐world 程序停止

)嗎?
不管什么GC,都會發送 stop‐the‐world,區別是發生的時間長短,主要取決於不同的垃圾收集器。
(3)Major gc和Full gc的區別
Major GC在很多參考資料中是等價於Full GC 的,我們也可以發現很多性能監測工具中只有Minor GC 和Full GC。一般情況下,一次 Full GC
將會對年輕代、老年代、元空間以及堆外內存進行垃圾回收。
觸發Full GC的原因其實有很多:
當年輕代晉升到老年代的對象大小,並比目前老年代剩余的空間大小還要大時,會觸發 Full GC;當老年代的空間使用率超過某閾值時,會觸發 Fu
ll GC;當元空間不足時(JDK1.7永久代不足),也會觸發 Full GC;當調用 System.gc() 也會安排一次 Full GC。
(4)判斷垃圾的方式
引用計數
可達性分析
(5)為什么要區分新生代和老年代
當前虛擬機的垃圾收集都采用分代收集算法,這種算法沒有什么新的思想,只是根據對象存活周期的不同將內存分為幾塊。
一般將 java 堆分為新生代和老年代,這樣我們就可以根據各個年代的特點選擇合適的垃圾收集算法。
比如在新生代中,每次收集都會有大量對象死去,所以可以選擇復制算法,只需要付出少量對象的復制成本就可以完成每次垃圾收集。
而老年代的對象存活幾率是比較高的,而且沒有額外的空間對它進行分配擔保,所以我們必須選擇“標記‐清除”或“標記‐整理”算法進行垃圾收集。
(6)方法區中的類信息會被回收嗎?
方法區主要回收的是無用的類,那么如何判斷一個類是無用的類的呢?判定一個常量是否是“廢棄常量”比較簡單,而要判定一個類是否是“無用的
類”的條件則相對苛刻許多。類需要同時滿足下面 3 個條件才能算是 “無用的類”
a‐該類所有的實例都已經被回收,也就是 Java 堆中不存在該類的任何實例。
b‐加載該類的 ClassLoader 已經被回收。
c‐該類對應的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
(7)CMS與G1的區別
CMS 主要集中在老年代的回收,而 G1 集中在分代回收,包括了年輕代的 Young GC 以及老年代的 Mixed GC。
G1 使用了 Region 方式對堆內存進行了划分,且基於標記整理算法實現,整體減少了垃圾碎片的產生。
在初始化標記階段,搜索可達對象使用到的 Card Table,其實現方式不一樣。
G1可以設置一個期望的停頓時間。
(8)為什么需要Survivor區
如果沒有Survivor,Eden區每進行一次Minor GC,存活的對象就會被送到老年代。
這樣一來,老年代很快被填滿,觸發Major GC(因為Major GC一般伴隨着Minor GC,也可以看做觸發了Full GC)。老年代的內存空間遠大於新生代,進行一次Full GC消耗的時間比Minor GC長得多。執行時間長有什么壞處?頻發的Full GC消耗的時間很長,會影響
大型程序的執行和響應速度。
(9)為什么需要兩個Survivor區
最大的好處就是解決了碎片化。也就是說為什么一個Survivor區不行?第一部分中,我們知道了必須設置Survivor區。
假設現在只有一個Survivor區,我們來模擬一下流程:
剛剛新建的對象在Eden中,一旦Eden滿了,觸發一次Minor GC,Eden中的存活對象就會被移動到Survivor區。
這樣繼續循環下去,下一次Eden滿了的時候,問題來了,此時進行Minor GC,Eden和Survivor各有一些存活對象,
如果此時把Eden區的存活對象硬放到Survivor區,很明顯這兩部分對象所占有的內存是不連續的,也就導致了內存碎片化。
永遠有一個Survivor是空的,另一個非空的Survivor無碎片。
(10)為什么Eden:S1:S2=8:1:1
新生代中的對象大多數是“朝生夕死”的
(11)堆內存中的對象都是所有線程共享的嗎?
JVM默認為每個線程在Eden上開辟一個buffer區域,用來加速對象的分配,稱之為TLAB,全稱:Thread Local Allocation Buffer。
對象優先會在TLAB上分配,但是TLAB空間通常會比較小,如果對象比較大,那么還是在共享區域分配。
(12)Java虛擬機棧的深度越大越好嗎?
線程棧的大小是個雙刃劍,如果設置過小,可能會出現棧溢出,特別是在該線程內有遞歸、大的循環時出現溢出的可能性更大,如果該值設置過大,
就有影響到創建棧的數量,如果是多線程的應用,就會出現內存溢出的錯誤。
(13)垃圾收集器一般如何選擇
a‐用默認的
b‐關注吞吐量:使用Parallel
c‐關注停頓時間:使用CMS、G1
d‐內存超過8GB:使用G1
e‐內存很大或希望停頓時間幾毫秒級別:使用ZGC
(14)什么是方法內聯?
正常調用方法時,會不斷地往Java虛擬機棧中存放新的棧幀,這樣開銷比較大,其實jvm虛擬機內部為了節省這樣開銷,可以把一些方法放到同一個
棧幀中執行。
(15)什么樣的情況下對象會進入Old區?
a‐大對象
b‐到了GC年齡閾值
c‐擔保機制
d‐動態對象年齡判斷
(16)聊聊Minor GC、Major GC、Full GC發生的時機
Minor GC:Eden或S區空間不足或發生了Major GC
Major GC:Old區空間不足
Full GC:Old空間不足,元空間不足,手動調用System.gc())
(17) 什么時候會發生垃圾收集
GC是由JVM自動完成的,根據JVM系統環境而定,所以時機是不確定的。
當然,我們可以手動進行垃圾回收,比如調用System.gc()方法通知JVM進行一次垃圾回收,但是具體什么時刻運行也
無法控制。
也就是說System.gc()只是通知要回收,什么時候回收由JVM決定。但是不建議手動調用該方法,因為GC消耗的資源
比較大。
1)當Eden區或者S區不夠用了: Young GC 或 Minor GC
2)老年代空間不夠用了: Old GC 或 Major GC
3)方法區空間不夠用了: Metaspace GC
4)System.gc()
Full GC=Young GC+Old GC+Metaspace GC
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM