JVM入門學習(一)

JVM體系結構概覽

在這裏插入圖片描述

暗色區域表示線程獨佔的,亮色區域表示線程共享,存在垃圾回收。

類裝載器ClassLoader

1.作用: 負責加載class文件,class文件開頭有特定的文件標示,將class文件字節碼內容加載到內存中,並將這些內容轉換成方法區中的運行時的數據結構並且ClassLoader只負責class文件的加載,至於它是否可以運行,由Execution Engine決定。

注意:方法區裏放的不是方法,而是類的模板(類的描述信息)。

在這裏插入圖片描述

Car的多個實例屬性方法等一樣,是因爲它們都是由同一個模板(Class)生成的。

2.類加載器的種類

以下是類加載器的繼承關係圖:

在這裏插入圖片描述

  • 虛擬機自帶的加載器
    • 啓動類加載器(Bootstrap)C++ 加載Java中的原始對象(Object,String,List等)
    • 擴展類記載器(Extension)Java 加載Java中的擴展類。
    • 應用程序類加載器(AppClassLoader)
  • 用戶自定義加載器 用戶定製類的加載方式

查看加載器:對象.getClass().getClassLoader()

public class Test01 {
    public static void main(String[] args) {
        Object object = new Object();
        // 報錯
        System.out.println(object.getClass().getClassLoader().getParent().getParent()); 
        // 報錯
        System.out.println(object.getClass().getClassLoader().getParent()); 
        // Bootstrap Loader:輸出null
        System.out.println(object.getClass().getClassLoader());

        Test01 test01 = new Test01();
         // Bootstrap Loader: 輸出null
        System.out.println(test01.getClass().getClassLoader().getParent().getParent());
        // Extension Loader: 輸出 sun.misc.Launcher$ExtClassLoader@1b6d3586
        System.out.println(test01.getClass().getClassLoader().getParent());
        // AppClassLoader: 輸出 sun.misc.Launcher$AppClassLoader@18b4aac2
        System.out.println(test01.getClass().getClassLoader());
    }
}

3.雙親委派機制

在這裏插入圖片描述

4.沙箱安全機制

雙親委派機制防止惡意代碼污染java源代碼(雙親委派機制保證了同包名的類總是先找到系統自帶的)。以下代碼編譯通過,但運行不了。

package java.lang
public Class String {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

本地方法接口

1.native: 表示java調用的是與java無關的底層的第三方的操作系統庫或C。

Thread類的start方法如下,從代碼中可以看出start()方法底層調用的是C/C++。

 public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

本地接口的作用是融合不同的編程語言爲 Java 所用,它的初衷是融合 C/C++程序,Java 誕生的時候是 C/C++橫行的時候,要想立足,必須有調用 C/C++程序,於是就在內存中專門開闢了一塊區域處理標記爲native的代碼,它的具體做法是 Native Method Stack中登記 native方法,在Execution Engine 執行時加載native libraies。

程序計數器(PC寄存器)

作用: 一個指針,指向下一條將要執行的指令。

方法區

作用: 存儲每一個類的結構信息,如 常量池字段和方法數據構造函數普通方法的字節碼內容

在不同虛擬機實現不一樣,如jdk7的永久代(PermGen space)和 jdk8的元空間(Metaspace)。

stack | heap

棧管運行,堆管存儲。

棧也叫棧內存,主管Java程序的運行,是在線程創建時創建,它的生命期是跟隨線程的生命期,線程結束棧內存也就釋放,對於棧來說不存在垃圾回收問題,只要線程一結束該棧就Over,生命週期和線程一致,是線程私有的。8種基本類型的變量+對象的引用變量+實例方法都是在函數的棧內存中分配。

每個方法執行的同時都會創建一個棧幀,用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息,每一個方法從調用直至執行完畢的過程,就對應着一個棧幀在虛擬機中入棧到出棧的過程。棧的大小和具體JVM的實現有關,通常在256K~756K之間,與等於1Mb左右。

棧幀中主要保存3 類數據:

  1. 本地變量(Local Variables):輸入參數和輸出參數以及方法內的變量;
  2. 棧操作(Operand Stack):記錄出棧、入棧的操作;
  3. 棧幀數據(Frame Data):包括類文件、方法等等。

在這裏插入圖片描述

堆體系結構概述

Heap堆(Java7之前)

​ 一個JVM實例只存在一個堆內存,堆內存的大小是可以調節的。類加載器讀取了類文件後,需要把類、方法、常變量放到堆內存中,保存所有引用類型的真實信息,以方便執行器執行。
堆內存邏輯上分爲三部分:新生+養老+永久。但在物理上只有新生+養老。

在這裏插入圖片描述

其中永久存儲區在jdk7及以前叫永久代,jdk8開始叫元空間。永久代和元空間是不用版本jdk對方法區的實現!

GC的大致流程

新生區是類的誕生、成長、消亡的區域,一個類在這裏產生,應用,最後被垃圾回收器收集,結束生命。新生區又分爲兩部分: 伊甸區(Eden space)和倖存者區(Survivor pace) ,所有的類都是在伊甸區被new出來的。倖存區有兩個: 0區(Survivor 0 space)和1區(Survivor 1 space)。當伊甸園的空間用完時,程序又需要創建對象,JVM的垃圾回收器將對伊甸園區進行垃圾回收(Minor GC),將伊甸園區中的不再被其他對象所引用的對象進行銷燬。然後將伊甸園中的剩餘對象移動到倖存 0區。若倖存 0區也滿了,再對該區進行垃圾回收,然後移動到 1 區。那如果1 區也滿了呢?再移動到養老區。若養老區也滿了,那麼這個時候將產生MajorGC(FullGC),進行養老區的內存清理。若養老區執行了Full GC之後發現依然無法進行對象的保存,就會產生OOM異常“OutOfMemoryError”。

如果出現java.lang.OutOfMemoryError: Java heap space異常,說明Java虛擬機的堆內存不夠。原因有二:
(1)Java虛擬機的堆內存設置不夠,可以通過參數-Xms、-Xmx來調整。
(2)代碼中創建了大量大對象,並且長時間不能被垃圾收集器收集(存在被引用)。

Minor GC

在這裏插入圖片描述

MinorGC的過程(複製->清空->互換)

1:eden、SurvivorFrom 複製到 SurvivorTo,年齡+1
首先,當Eden區滿的時候會觸發第一次GC,把還活着的對象拷貝到SurvivorFrom區,當Eden區再次觸發GC的時候會掃描Eden區和From區域,對這兩個區域進行垃圾回收,經過這次回收後還存活的對象,則直接複製到To區域(如果有對象的年齡已經達到了老年的標準,則賦值到老年代區),同時把這些對象的年齡+1

2:清空 eden、SurvivorFrom
然後,清空Eden和SurvivorFrom中的對象,也即複製之後有交換,誰空誰是to

3:SurvivorTo和 SurvivorFrom 互換
最後,SurvivorTo和SurvivorFrom互換,原SurvivorTo成爲下一次GC時的SurvivorFrom區。部分對象會在From和To區域中複製來複制去,如此交換15次(由JVM參數MaxTenuringThreshold決定,這個參數默認是15),最終如果還是存活,就存入到老年代

生產環境:Xmx 和 Xms 設置成一樣大。避免GC和應用程序爭搶內存,理論值的峯值和峯谷忽高忽低。

堆參數調優入門

Java7 與 Java8 的區別

jdk7:

在這裏插入圖片描述
jdk8:
在這裏插入圖片描述

  • java8中永久代移除,由元空間替代

  • 永久代使用的JVM的堆內存,但是java8以後的元空間並不在虛擬機中而是使用本機物理內存。

    因此,默認情況下,元空間的大小僅受本地內存限制。 類的元數據放入 native memory, 字符串池和類的靜態變量放入 java 堆中,這樣可以加載多少類的元數據就不再由MaxPermSize 控制, 而由系統的實際可用空間來控制。

堆內存調優

在這裏插入圖片描述
可以採用以下程序驗證:其中的Runtime是對jvm數據區的抽象,可以查看相關參數。

public static void main(String[] args){
	long maxMemory = Runtime.getRuntime().maxMemory() ;     // 返回 Java 虛擬機試圖使用的最大內存量。
    long totalMemory = Runtime.getRuntime().totalMemory() ; // 返回 Java 虛擬機中的內存總量。
    System.out.println("MAX_MEMORY = " + maxMemory + "(字節)、" + (maxMemory / (double)1024 / 1024) + "MB");
	System.out.println("TOTAL_MEMORY = " + totalMemory + "(字節)、" + (totalMemory / (double)1024 / 1024) + "MB");
}

指定參數(idea):

-Xms1024m -Xmx1024m -XX:+PrintGCDetails

算法

判斷對象是否已經死亡的算法:引用計數算法,可達性分析算法;

四個垃圾收集算法:標記清除算法,複製算法,標記整理算法,分代收集算法;

七個垃圾收集器:Serial,SerialOld,ParNew,Parallel Scavenge,Parallel。Old,CMS,G1.

以上內容整理自:https://www.bilibili.com/video/BV1vE411D7KE?from=search&seid=14558761714549494791

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章