1. 程式人生 > >深入淺出JVM(jvm記憶體結構,類載入器圖,雙親委託模式,堆記憶體,GC解析,GC演算法)

深入淺出JVM(jvm記憶體結構,類載入器圖,雙親委託模式,堆記憶體,GC解析,GC演算法)

目錄

Java虛擬機器的記憶體結構:

 類載入器圖:

雙親委託模式:

堆記憶體:

GC解析圖:

GC演算法


Java虛擬機器的記憶體結構:

 類載入器圖:

 

雙親委託模式:

Java允許建立和JDK自帶類庫相同名稱類
但是JVM不會載入我們自己定義的類,而是載入JDK提供的類
JVM的實現方式為:雙親委派機制
    1) 如果應用類載入器載入一個類,不是馬上載入
    2)委託父載入器(擴充套件類載入器)載入
    3)擴充套件類載入器載入類的時候,也不是馬上載入
    4) 委託父載入器(啟動類載入器)載入
    5)啟動類載入器開始載入這個類,如果載入成功,那麼直接使用。
    6)如果載入失敗,那麼會返回到擴充套件類載入器,由擴充套件類載入器載入這個類
    7) 擴充套件類載入器開始載入這個類,如果載入成功,那麼直接使用。
    8)如果載入失敗,會丟擲異常給子載入器(應用類載入器)
    9)應用類載入器捕捉異常後開始載入這個類,如果載入成功,那麼直接使用。
    10)如果載入失敗,會將異常丟擲給JVM

JVM自身提供了3個類載入器,每一個類載入器會載入不同位置的類
 1)啟動類載入器:載入JDK核心類庫,由C++語言實現,
      載入位置: $JRE_HOME/lib
 2)擴充套件類載入器:是java類,可以載入JDK擴充套件類庫
      載入位置: $JRE_HOME/lib/ext
 3)應用類載入器:是java類,可以載入環境變數classpath中的類
      載入位置: $classpath
    JAVA_HOME, PATH, CLASSPATH

堆記憶體:

 

 

 

 

GC解析圖:

GC演算法

 記憶體效率:複製演算法>標記清除演算法>標記整理演算法(此處的效率只是簡單的對比時間複雜度,實際情況不一定如此)。 記憶體整齊度:複製演算法=標記整理演算法>標記清除演算法。 
記憶體利用率:標記整理演算法=標記清除演算法>複製演算法。 
 
可以看出,效率上來說,複製演算法是當之無愧的老大,但是卻浪費了太多記憶體,而為了儘量兼顧上面所提到的三個指標,標記/整理演算法相對來說更平滑一些,但效率上依然不盡如人意,它比複製演算法多了一個標記的階段,又比標記/清除多了一個整理記憶體的過程

年輕代(Young Gen)  ----->複製演算法的
 
年輕代特點是區域相對老年代較小,物件存活率低。這種情況複製演算法的回收整理,速度是最快的。複製演算法的效率只和當前存活對像大小有關,因而很適用於年輕代的回收。而複製演算法記憶體利用率不高的問題,通過hotspot中的兩個survivor的設計得到緩解。
 
老年代(Tenure Gen) ----> 標記清除或者是標記清除與標記整理的混合實現
 
老年代的特點是區域較大,物件存活率高。這種情況,存在大量存活率高的對像,複製演算法明顯變得不合適。一般是由標記清除或者是標記清除與標記整理的混合實現。
 
Mark階段(標記)

的開銷與存活對像的數量成正比,這點上說來,對於老年代,標記清除或者標記整理有一些不符,但可以通過多核/執行緒利用,對併發、並行的形式提標記效率。
 
Sweep階段(清除)的開銷與所管理區域的大小形正相關,但Sweep“就地處決”的特點,回收的過程沒有對像的移動。使其相對其它有對像移動步驟的回收演算法,仍然是效率最好的。但是需要解決記憶體碎片問題。
 
Compact階段(整理)的開銷與存活對像的資料成開比,如上一條所描述,對於大量對像的移動是很大開銷的,做為老年代的第一選擇並不合適。
 
以hotspot中的CMS回收器為例,CMS是基於Mark-Sweep實現的,對於對像的回收效率很高,而對於碎片問題,CMS採用基於Mark-Compact演算法的Serial Old回收器做為補償措施:當記憶體回收不佳(碎片導致的Concurrent Mode Failure時),將採用Serial Old執行Full GC以達到對老年代記憶體的整理。 從JDK1.9後不再推薦使用CMS垃圾回收器, 推薦採用 Garbage-First(G1)垃圾回收器, Garbage-First(G1)是專門針對多核處理器的,雖然是整理,但是非常是多核完成的,因此較快。

一個case(說明引用計數法存在的問題):

package hello_java;
public class Test4 {
    public static void main(String[] args) {
        test();
        System.gc();// full gc
        System.out.println( "main finish" );
    }
    public static void test() {
        A a = new A();
        B b = new B();
        a.b = b;
        b.a = a;
    }
}
class B {
    public A a= null;
    @Override
    protected void finalize() throws Throwable {
        System.out.println(  "B被回收了");
    }
}
class A {
    public B b = null;
    @Override
    protected void finalize() throws Throwable {
        System.out.println(  "A被回收了");
    }

}

列印結果:

main finish
B被回收了
A被回收了

解釋:

Test方法出棧,gc回收、

稍作修改:

package hello_java;
public class Test4 {
    public static void main(String[] args) {
        A a = test();
        System.gc();// full gc
        System.out.println( "main finish" );
    }
    public static A test() {
        A a = new A();
        B b = new B();
        a.b = b;
        b.a = a;
        return a;
    }
}
class B {
    public A a= null;
    @Override
    protected void finalize() throws Throwable {
        System.out.println(  "B被回收了");
    }
}
class A {
    public B b = null;
    @Override
    protected void finalize() throws Throwable {
        System.out.println(  "A被回收了");
    }

}

main finish

解釋:

main方法中有指向物件A的,不會被回收。