JVM底層原理介紹

JVM結構圖

在這裏插入圖片描述
黃色的是所有線程共享數據,存在垃圾回收。
灰色的是線程之間數據私有,不存在垃圾回收。

類的加載時機

  1. 創建類的實例。例如:new Person()
  2. 使用到類的靜態成員時。
  3. 使用反射方式來強制創建某個類或接口對應的java.lang.Class對象。
  4. 初始化某個類的子類時,new子類先加載父類。
  5. 直接使用java.exe命令來運行某個主類。
    以上五種情況的任何一種,都可以導致JVM將一個類加載到方法區。

類加載器

作用:將class文件加載進內存。

虛擬機自帶的類加載器:

  • 啓動類加載器(Bootstrap ClassLoader):用於加載系統類庫<JAVA_HOME>\bin\rt.jar目錄下的class。 (C++編寫的)
  • 擴展類加載器(Extension ClassLoader):用於加載擴展類<JAVA_HOME>\lib\ext目錄下的class。 (Java編寫的)
  • 應用程序類加載器(Application ClassLoader):也叫系統類加載器,用於加載自定義的類和第三方的jar。

自定義的類加載器:

  • Java.long.ClassLoader的子類用戶可自定義加載方式,例如:(Custom ClassLoader)。

三個類加載器的關係:

  • 雖然AppClassLoader的父加載器是ExtClassLoader
  • ExtClassLoader的父加載器是BootstrapClassLoader
  • 但是他們沒有子父類繼承關係,他們有一個共同的爹–>ClassLoader。
public class Demo {
    public static void main(String[] args) {
        //獲取AppClassLoader
        ClassLoader classLoader = Demo.class.getClassLoader();
        System.out.println(classLoader);

        //獲取ExtClassLoader
        ClassLoader classLoader1 = Demo.class.getClassLoader().getParent();
        System.out.println(classLoader1);

        //獲取BootstrapClassLoader
        ClassLoader classLoader2 = Demo.class.getClassLoader().getParent().getParent();
        System.out.println(classLoader2);
    }
}

雙親委派機制

下圖展示了"類加載器"的層次關係,這種關係稱爲類加載器的"雙親委派模型"。
在這裏插入圖片描述

  • “雙親委派模型"中,除了頂層的啓動類加載器外,其餘的類加載器都應當有自己的"父級類加載器”。
  • 這種關係不是通過"繼承"實現的,通常是通過"組合"實現的。通過"組合"來表示父級類加載器。
  • "雙親委派模型"的工作過程:
    • 某個"類加載器"收到類加載的請求,它首先不會嘗試自己去加載這個類,而是把求交給父級類加載器。
    • 因此,所有的類加載的請求最終都會傳送到頂層的"啓動類加載器"中。
    • 如果"父級類加載器"無法加載這個類,然後子級類加載器再去加載。

雙親委派機制的好處

雙親委派機制的一個顯而易見的好處是:Java的類隨着它的類加載器一起具備了一種帶有優先級的層次關係。例如:java.lang.Object。它存放在rt.jar中。無論哪一個類加載器要加載這個類,最終都是委派給處於頂端的"啓動類加載器"進行加載,因此java.lang.Object類在程序的各種類加載器環境中都是同一個類。
相反,如果沒有"雙親委派機制",如果用戶自己編寫了一個java.lang.Object,那麼當我們編寫其它類時,這種隱式的繼承使用的將會是用戶自己編寫的java.lang.Object類,那將變得一片混亂。

做一個演示:
這裏我編寫了一個類,包名是java.lang,類名是String。
但是看到異常中的錯誤提示說的是找不到main方法,分析一下原因:
圖中的String是我們自定義的類,加載時會從AppClassloader往上找,先會詢問ExtClassLoader中有沒有這個類,顯然它沒有String這個類,又會向上找父級加載器BootStrapClassLoader,顯然這裏面可以加載java.lang.String,但是它裏面的這個String是官方提供的類,裏面沒有main方法,所以會報這個異常。
在這裏插入圖片描述
總結:每個自定義的類在加載時,都會一級一級向上詢問父加載器中有沒有這個類,父類加載器中有的話就用父類加載器中的類,如果實在找不到就用你自定義的這個類,這樣就保證了Class只會加載一次,防止了我們的代碼與源代碼衝突,這就是雙親委派機制。

沙箱安全機制(瞭解)

參考(改變ing)的博客:
https://blog.csdn.net/qq_30336433/article/details/83268945

本地方法棧

用於運行帶有native的方法。

帶有native的方法只有聲明,沒有實現,它的實現是C語言編寫的。

帶有native的方法不是java官方編寫的,是C語言編寫的代碼,我們可以簡單的理解爲,帶有native的方法就是調用了第三方函數庫或者叫做調用了C語言函數庫來實現功能的。

PC寄存器(程序計數器)

每個線程都有一個程序計數器,是線程私有的,就是一個指針,指向方法區中的方法字節碼(用來存儲指向下一條指令的地址),由執行引擎讀取下一條命令,佔內存空間非常小,可以忽略不計。
它是當前線程所執行的字節碼的行號指示器,通過不斷的改變這個計數器的值來選取下一條要執行的字節碼指令。
如果執行的是native的方法,那麼這個計數器是空的。

總結:類似於指針,意思就是當前方法執行完,下一個該執行那個方法,就需要用pc寄存器來做標記,實質上pc寄存器存儲的就是下一個要運行的方法的地址。

方法區

供各個線程運行時的內存區域,它存儲了每個類的結構信息,例如運行時期的常量池、方法數據、構造函數、普通方法等這些的字節碼內容,方法區就是存儲了類的結構信息,也可以稱爲模板信息。

注意:方法區只是一個規範,它在不同的虛擬機裏面實現是不一樣的,最經典的就是永久代(PermGen space)和元空間(Meta space)。
實例變量在堆內存中,與方法區無關。

總結:方法區存儲了一個類的結構信息,也就是Class(大Class),就是類的模板信息。

堆和棧

先了解幾個概念:
堆管存儲,棧管運行。
程序 = 框架 + 業務邏輯。
隊列:先進先出,後進後出。
棧:先進後出,後進先出。

棧內存

棧中存儲着:8中基本數據類型 + 對象的引用變量(等號左邊的對象引用) + 方法都是在棧內存中分配着。

方法在棧內存中叫棧幀
在java層面就是叫方法

StackOverflowError 棧內存溢出(錯誤)

堆內存

凡是new出來的都在堆內存。

OutOfMemoryError (Java heap space)堆內存溢出

堆的結構和GC以及GC算法請參考上篇文章:
https://blog.csdn.net/weixin_45216092/article/details/105196959

剛剛學習沒多久,有不對的地方,還望各位指點!

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