Tip-關於JVM和Hotspot,你也許有這麼幾個容易暈的問題

1.JVM的結構到底有哪些?

       快速過一遍JVM的內存結構,JVM中的內存分爲5個虛擬的區域:
javascript



▪ 你的Java程序中所分配的每個對象都須要存儲在內存裏。堆是這些實例化的對象所存儲的地方。是的——都怪new操做符,是它把你的Java堆都佔滿了的!
▪ 它由全部線程共享
▪ 當堆耗盡的時候,JVM會拋出java.lang.OutOfMemoryError 異常
▪ 堆的大小能夠經過JVM選項-Xms和-Xmx來進行調整java

堆被分爲:

▪ Eden區 —— 新對象或者生命週期很短的對象會存儲在這個區域中,這個區的大小能夠經過-XX:NewSize和-XX:MaxNewSize參數來調整。新生代GC(垃圾回收器)會清理這一區域。
▪ Survivor區 —— 那些歷經了Eden區的垃圾回收仍能存活下來的依舊存在引用的對象會待在這個區域。這個區的大小能夠由JVM參數-XX:SurvivorRatio來進行調節。
▪ 老年代 —— 那些在歷經了Eden區和Survivor區的屢次GC後仍然存活下來的對象(固然了,是拜那些揮之不去的引用所賜)會存儲在這個區裏。這個區會由一個特殊的垃圾回收器來負責。年老代中的對象的回收是由老年代的GC(major GC)來進行的。
方法區
▪ 也被稱爲非堆區域(在HotSpot JVM的實現當中)
▪ 它被分爲兩個主要的子區域
持久代
       這個區域會 存儲包括類定義,結構,字段,方法(數據及代碼)以及常量在內的類相關數據。它能夠經過-XX:PermSize及 -XX:MaxPermSize來進行調節。若是它的空間用完了,會致使java.lang.OutOfMemoryError: PermGen space的異常。
代碼緩存
       這個緩存區域是用來存儲編譯後的代碼。編譯後的代碼就是本地代碼(硬件相關的),它是由JIT(Just In Time)編譯器生成的,這個編譯器是Oracle HotSpot JVM所特有的。
JVM棧
▪ 和Java類中的方法密切相關
▪ 它會存儲局部變量以及方法調用的中間結果及返回值
▪ Java中的每一個線程都有本身專屬的棧,這個棧是別的線程沒法訪問的。
▪ 能夠經過JVM選項-Xss來進行調整
本地棧
▪ 用於本地方法(非Java代碼)
▪ 按線程分配
PC寄存器
▪ 特定線程的程序計數器
▪ 包含JVM正在執行的指令的地址(若是是本地方法的話它的值則未定義)程序員

2.爲何JVM規範裏面歷來沒有出現過永久代這個詞?

        根據 JVM 規範,JVM 內存共分爲虛擬機棧、堆、方法區、程序計數器、本地方法棧五個部分:
算法

        絕大部分 Java 程序員應該都見過 "java.lang.OutOfMemoryError: PermGen space "這個異常。這裏的 「PermGen space」其實指的就是方法區。前者是 JVM 的規範,然後者則是 JVM 規範的一種實現,而且只有 HotSpot 纔有 「PermGen space」,而對於其餘類型的虛擬機,如 JRockit(Oracle)、J9(IBM) 並無「PermGen space」。因爲方法區主要存儲類的相關信息,因此對於動態生成類的狀況比較容易出現永久代的內存溢出。最典型的場景就是,在 jsp 頁面比較多或者加載的包較多的狀況,容易出現永久代內存溢出。所以,你會看到Java8虛擬機規範和Java7並無什麼不一樣,可是全部人都在告訴你:Hotspot虛擬機捨棄了永久代。編程

3.JDK7中的永久代會回收嗎?它回收哪些東西?

       通常的垃圾回收不會發生在永久代,若是永久代滿了或者是超過了臨界值,會觸發徹底垃圾回收(FullGC)。若是仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。那麼永久代能回收什麼呢?一,廢棄的字符串常量,二,再也不被引用的class對象。正確的設置永久代大小對避免FullGC是很是重要的緣由。因此引發full GC的緣由不光是老年代滿了或者超過臨界值,也有多是永久代滿了或者超過臨界值。緩存

4.永久代已經沒了。那麼JVM規範中的方法區HotSpot是如何實現的?

       JDK8的Hotspot使用Metaspace(元空間)代替了永久代。相比較永久代,有以下的一些改變。這個改變在以下文章中有描述:
blogs.oracle.com/poonam/entr…oracle

       其中有以下一段話:jvm

元空間被直接接分配在本地內存。默認的元數據空間分配只受到本地內存的限制。咱們可使用一個新的MaxMetaspaceSize選項來設置元空間佔用本地內存的大小。這個選項與MaxPermSize相似。當元空間使用量MetaspaceSize設置的值(32位的client模式默認是12M,32位的server模式是16M,64位的server模式則會更多)達到了垃圾收集器會收集那些再也不使用的classloader和class會被回收。全部設置一個較大的MetaspaceSize值會延遲垃圾收集發生的時間。在觸發一次垃圾收集之後和下一次垃圾收集以前,元空間的使用值值會隨着使用不斷增長。jsp

       若是要想知道關於元空間更多的內容,能夠訪問這個連接,這個連接專門講述了關於元空間一些更多的細節。編程語言

5.Hotpsot垃圾回收的原理是什麼?

       hotspot的垃圾收集的斷定主要是使用可達性分析算法。在目前主流的編程語言(java,C#等)的主流實現中,都是稱經過可達性分析(Reachability Analysis)來斷定對象是否存活的。這個算法的基本思路就是經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來講,就是從GC Roots到這個對象不可達)時,則證實此對象是不可用的。以下圖所示,對象object 五、object 六、object 7雖然互相有關聯,可是它們到GC Roots是不可達的,因此它們將會被斷定爲是可回收的對象。

僅僅是瞭解這些是不夠的。在個人博客中,有一篇JAVA內存白皮書的翻譯java內存管理白皮書,很是經典。

6.被斷定爲垃圾地對象必定會被回收嗎?

       即便在可達性分析算法中不可達的對象,也並不是是「非死不可」的,這時候它們暫時處於「緩刑」階段,要真正宣告一個對象死亡,至少要經歷兩次標記過程:
       若是對象在進行可達性分析後發現沒有與GC Roots相鏈接的引用鏈,那它將會被第一次標記而且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,虛擬機將這兩種狀況都視爲「沒有必要執行」。(即意味着直接回收)

       若是這個對象被斷定爲有必要執行finalize()方法,那麼這個對象將會放置在一個叫作F-Queue的隊列之中,並在稍後由一個由虛擬機自動創建的、低優先級的Finalizer線程去執行它。這裏所謂的「執行」是指虛擬機會觸發這個方法,但並不承諾會等待它運行結束,這樣作的緣由是,若是一個對象在finalize()方法中執行緩慢,或者發生了死循環(更極端的狀況),將極可能會致使F-Queue隊列中其餘對象永久處於等待,甚至致使整個內存回收系統崩潰。
finalize()方法是對象逃脫死亡命運的最後一次機會,稍後GC將對F-Queue中的對象進行第二次小規模的標記,若是對象要在finalize()中成功拯救本身——只要從新與引用鏈上的任何一個對象創建關聯便可,譬如把本身(this關鍵字)賦值給某個類變量或者對象的成員變量,那在第二次標記時它將被移除出「即將回收」的集合;若是對象這時候尚未逃脫,那基本上它就真的被回收了。
代碼示例:

public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("yes,i am still alive:)");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize mehtod executed!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws Throwable {
        SAVE_HOOK = new FinalizeEscapeGC();
        // 對象第一次成功拯救本身
        SAVE_HOOK = null;
        System.gc();
        // 由於finalize方法優先級很低,因此暫停0.5秒以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no,i am dead:(");
        }
        // 下面這段代碼與上面的徹底相同,可是此次自救卻失敗了
        SAVE_HOOK = null;
        System.gc();
        // 由於finalize方法優先級很低,因此暫停0.5秒以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no,i am dead:(");
        }
    }
}複製代碼

運行結果:

finalize mehtod executed!
yes,i am still alive:)
no,i am dead:(複製代碼

       SAVE_HOOK對象的finalize()方法確實被GC收集器觸發過,而且在被收集前成功逃脫了。另一個值得注意的地方是,代碼中有兩段徹底同樣的代碼片斷,執行結果倒是一次逃脫成功,一次失敗,這是由於任何一個對象的finalize()方法都只會被系統自動調用一次,若是對象面臨下一次回收,它的finalize()方法不會被再次執行,所以第二段代碼的自救行動失敗了。由於finalize()方法已經被虛擬機調用過,虛擬機都視爲「沒有必要執行」(即意味着直接回收)。

相關文章
相關標籤/搜索