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使用“類”的生命周期是:
2.連接 該階段又包含了驗證、准備和解析3個過程,如下。
(1)驗證
校驗.class文件的正確性。
(2)准備
2.1給static靜態變量分配內存,並初始化static的默認值。
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怎么判斷哪些對象應該回收
引用計數算法和可達性分析算法。
因為引用計數法的算法是這樣的:在對象中添加一個引用計數器,每當一個地方引用它時,計數器就加一;當引用失效時,計數器值就減一;任何時刻計數器為零的對象就是不可能再被使用的。
但是這樣的算法有個問題,是什么呢?
不經意間來一波自問自答。讓面試官聽的一愣一愣的。
就是不能解決循環依賴的問題。
並拿着自己准備的紙和筆快速的畫出下面這樣的圖:
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 停頓時間會相對比較長。
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分區模型。
內存溢出(out of memory),是指程序在申請內存時,沒有足夠的內存空間供其使用,出現out of memory;比如申請了一個integer,但給它存了long才能存下的數,那就是內存溢出。
內存泄露(memory leak),是指程序在申請內存后,無法釋放已申請的內存空間,一次內存泄露危害可以忽略,但內存泄露堆積后果很嚴重,無論多少內存,遲早會被占光。
memory leak會最終會導致out of memory!
(2)Young GC會有stw(stop‐the‐world 程序停止