1. JVM體系結構概述


一、 JVM的位置

    JVM是運行在操作系統之上的,與硬件沒有直接的交互,但是可以調用底層的硬件,用JIN (Java本地接口調用底層硬件接口,了解下就好,已經過時了)

二、JVM體系結構概覽

1. 類裝載器ClassLoader ** (4個知識點,①概念,②種類(3種系統自帶,1種自定義),③雙親委派機制,④沙箱安全機制)

1.1 類裝載器的概念
        負責加載class文件,class文件 ** 在文件開頭有特定的文件標識 ** ,將class文件字節碼內容加載到內存中,並將這些內容轉換成方法區中的運行時數據結構並且ClassLoader只負責class文件的加載,至於它是否可以運行,則由Execution Engine決定。  

    解釋:Car.class  是由 .java 文件 經過編譯而得來的 .class文件,存在本地磁盤  
            
            ClassLoader: 類轉載器,作用就是加載並初始化 .class文件 ,得到真正的 Class 類,即模板  (此處不明白則帶着疑問繼續往下看,為什么叫模板)

            Car Class : 由 Car.class 字節碼文件,通過ClassLoader 加載並初始化而得,那么此時 這個 Car 就是當前類的模板,這個Car Class 模板就存在 【方法區】

            car1,car2,car3 : 是由Car模板經過實例化而得,即 new出來的 --> Car car1 = new Car() , Car car2 = new Car() ,Car car3 = new Car() , 因此可知,由一個模板,可以得到多個實例對象,即模板一個,實例多個  
            所以,拿car1舉例,car1.getClass 可以得到其模板Car 類,Car.getClassLoader() 可得到其裝載器  
1.2 類裝載器的種類
    1.2.1 虛擬機自帶的加載器  
        ①  啟動類加載器 也叫根加載器 (Bootstrap)  ,由C++編寫  ,程序中自帶的類, 存儲在$JAVAHOME/jre/lib/rt.jar中,如object類等
        ②  擴展類加載器 (Extension)  ,Java 編寫 ,在我們平時看到的類路徑中,凡是以javax 開頭的,都是拓展包,存儲在$JAVAHOME/jre/lib/ext/*.jar 中 
        ③  應用程序類加載器  (AppClassLoader),即平時程序中自定義的類  new出來的  

        Java也叫系統類加載器,加載當前應用的classpath的所有的類

    1.2.2 用戶自定義加載器  
        Java.lang.ClassLoader的子類,用戶可以定制類的加載方式,即如果你的程序有特殊的需求,你也可以自定義你的類加載器的加載方式 ,進入ClassLoader的源碼,其為抽象類,因此在你定制化開發的時候,需要你定義自己的加載器類來繼承ClassLoader抽象類即可,即 MyClassLoader extends ClassLoader

    所以,Java 的類的加載機制,永遠是從 啟動類加載器 -->  拓展類加載器  --> 應用程序類加載器  這樣的一個順序進行加載
1.3 類裝載器的雙親委派機制
      先舉一個栗子,來說明下啥叫雙親委派,比如 有一個類叫 A.java  ,當要使用A類時,類加載器要先去 啟動類加載器(Bootstrap)中去找,如果找到就使用啟動類加載器中的A類,不繼續往下執行,但是如果找不到,則依次下放,去 拓展類加載器 中找,同理找到就用,找不到就繼續下放,再去 應用程序類加載器中找,找到就用,此時找不到就會報classNotFund Exception的異常。 

    概念:
        當一個類收到類加載請求,它首先不會嘗試自己去加載這個類,而是先把這個請求委派給父類去完成,每一個層次類加載器都是如此,因此所有的類加載請求都是應該傳到啟動類加載器中的,只有當其父類加載器自己無法完成這個請求的時候(在他的加載路徑下沒有找到所需加載的Class),子類加載器才會嘗試自己去加載。

    采用雙親委派的一個好處就是比如加載位於rt.jar包中的類java.lang.Object,不管是哪個加載器加載這個類,最終都是會委托給頂層的啟動類加載器進行加載,這樣就保證了使用不同的類加載器最終得到的都是同一個Object對象。
1.3 類裝載器沙箱安全機制
       通過雙親委派機制,類的加載永遠都是從 啟動類加載器開始,依次下放,保證你所寫的代碼,不會污染Java自帶的源代碼,所以出現了雙親委派機制,保證了沙箱安全

Execution Engine 執行引擎負責解釋命令,提交操作系統執行。

2. Native Interface 本地接口

    native :在Java中是一個關鍵字,有聲明,無實現。
    
    以線程為例,不要以為線程是屬於Java的一個東西,其實它是屬於操作系統底層的,Java中通過Thread類的start() 類啟動一個線程,

進入Thread的start()的源碼,你會看到雖然調用的是start(),但其實調用的start0()這個方法, 最終是由 private native void start0(); 這段代碼去跟底層做了交互實現,有聲明,無實現,Java到此交由系統去處理了。  

    這里引申個題外知識哈,即我new 一個線程,當我執行 thread.start() ;這個方法之后,是不是會立即執行這個線程呢?  
            
            答案就是不一定,因為當你創建一個線程,調用start()方法后,是將線程從初始化狀態變為就緒狀態,而真正的執行,是要等cpu來進行調度,你才能執行,否則你就跟那就緒着,千萬別信誓旦旦的說,肯定會立即執行,還是建議看一下 操作系統的課本。


    本地接口的作用是融合不同的編程語言為Java所用,它的初衷是想融合C/C++ 程序,Java誕生之初,正式C/C++ 盛行之時,因此,Java要想立足,則必須要調用C/C++程序(打個比方:微某信和抖某音,起初阿抖想分享視頻給阿音某好友,直接點分享就過去了,但是后來被阿音給禁止了,當初Java也是一樣,你要想使用我的某些東西,那你就得給我整點“保護費”啊),於是在內存中專門開辟了一塊區域處理標記為native的代碼,它的具體做法就是 Native Method Stack 中登記 native 方法,在 Exection Engine 執行時加載 native libraies。  
    目前該方法使用的越來越少,除非是與硬件有關的應用,比如通過Java程序驅動打印機或者Java系統管理的生產設備,在企業級應用中比較少見,因為現在的異構領域間的通信很發達,比如可以使用Socket通訊,也可以使用Web Service等等,不多做介紹。。。。  

3. Native Method Stack 本地方法棧

    它的具體做法是 Native Method Stack 中登記 native 方法,在 Execution Engine 執行時加載本地方法庫。

4. 程序計數器 (PC寄存器)

    記錄了方法之間的調用和執行情況,類似班級的值日表,用來存儲指向下一條指令的地址,也即將要執行的指令代碼

     每個線程都有一個程序計數器,是線程私有的,就是一個指針,指向方法區中的方法字節碼(用來存儲指向下一條指令的地址,也即將要執行的指令代碼。建議看一下【計算機操作系統】這本書,其實不光有pc,還有時間片的輪轉,這里不多做介紹),有執行引擎讀取下一條指令,是一個非常小的內存空間,幾乎可以忽略不計。

    這塊存儲區域很小,他是當前線程所執行的字節碼的行號指示器,字節碼解釋器通過改變這個計數器的值來選取下一條需要執行的字節碼指令。

    如果執行的是一個Native方法,那這個計數器就是空的,因為native已經不屬於Java的范疇了 。

    用以完成分支、循環、跳轉、異常處理、線程恢復等基礎功能。不會發生內存溢出(OutOfMemory = OOM)錯誤。  

5. 方法區 Method Area

    5.1 供各線程 共享 的運行時內存區域。----存儲了每一個類的結構信息---- (即上面所提到的模板)。

            例如:運行時常量池(Runtime Constant Pool)、字段 和 方法數據、構造函數和普通方法的字節碼內容。

    5.2 方法區就是一個規范,在不同的虛擬機里實現是不一樣的,最典型的就是 永久代(PermGen space) 和 元空間 (Metaspace)  
            永久代:JDK1.7
            元空間:JDK1.8
            但是兩者都不存在方法區中,就像上面所講,方法區存的是模板,而永久代和元空間是這套模板的實例,后面在堆中討論  
        所以,實例變量存儲在堆內存中,和方法區無關。

6. 棧 **

    6.1 記住 : 棧管運行,堆管存儲
    
    6.2 棧也叫棧內存,主管Java程序的運行,是在線程創建時創建,他的生命周期是跟隨線程的生命周期,線程結束那么棧內存也就隨之釋放, 對於棧來說不存在垃圾回收問題 ,只要線程已結束該棧就over了,是線程私有的。8種基本類型的變量 + 對象的引用變量 + 實例方法都是在函數的占內存中分配。

    6.3 棧存儲什么?
            棧幀中主要保存 3 類數據 : (何為棧幀:即Java中的方法,只是在jvm中叫做棧幀)
            - ① 本地變量 (Local Variables) : 入參和出參 以及方法內的變量;
            - ② 棧操作 (Operand Stack) : 記錄出棧 和 入棧的操作;(可理解為pc寄存器的指針)
            - ③ 棧幀數據 (Frame Data) : 包括類文件、方法等。

    棧的運行原理:
            棧中的數據都是以棧幀 (Stack Frame) 的格式存在,棧幀是一個內存區塊,是一個有關方法和運行期數據的數據集,

            當一個方法A被調用時就產生了棧幀 F1,並被壓入到棧中,
            A方法又調用 B方法,於是產生棧幀F2 ,也被壓入棧,
            B方法又調用 C方法, 於是產生棧幀F3,也被壓入棧 
            ……
            執行完畢后,先彈出F3棧幀,再彈出 F2棧幀,再彈出 F1棧幀 …… 

        遵循 “先進后出” / “后進先出” 原則。

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

        6.4 StackOverflowError  即SOF
            比如:Exception in thread "main" java.lang.StackOverflowError  

            原因: 是因為方法的加載深度的調用后,將棧撐爆了, 即棧溢出

            那么 StackOverflowError 屬於 異常 還是屬於錯誤 ?答案是屬於 Error級別的錯誤 ,看下圖 

7. 堆、棧、方法區 三者的關系

      HotSpot是使用指針的方式來訪問對象 :
      Java堆中存放訪問類元數據的地址(即模板的地址)
      reference存儲的是直接對象的地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM