深刻理解Java虛擬機(一)

1、運行時數據區域

一、程序計數器:

  • 當前線程執行字節碼的行號指示器(經過改變計數器的值來選擇下條須要執行的字節碼指令)
  • 每一個線程有獨立的程序計數器(線程私有,爲了切換線程時能恢復到掙錢的執行位置)
  • 若是執行java方法,計數器記錄正在執行的字節碼指令地址。若是執行的是Native方法,計數器爲空。
  • 惟一沒規定任何OutOfMemoryError狀況的區域。

二、虛擬機棧

  • 爲執行Java方法服務
  • 線程私有,聲明週期跟線程一致
  • 一個Java方法執行到結束的過程:棧幀從入棧到出棧的過程
  • 棧幀存儲局部變量表(包括基本數據類型和對象的引用類型)、操做棧、動態連接、方法出口等信息
  • 異常:線程請求的棧深度大於虛擬機容許的深度,拋出StackOverflowError。虛擬機動態擴展過程當中沒法申請到足夠的內存,會拋出OutOfMemoryError異常。

三、本地方法棧

  • 爲虛擬機用到的Native方法服務
  • 也會拋出StackOverflowError和OutOfMemoryError的異常

四、Java堆

  • 用來存儲對象的實例
  • 全部線程共享的一塊內存區域
  • 從內存回收的角度能夠分爲新生代和老年代

五、方法區

  • 存放被虛擬機加載的類信息、常量、靜態變量等
  • 線程共享

六、運行時常量池

  • 方法區的一部分
  • 存放編譯期生成的各類字面量和符號引用

2、垃圾回收(GC)

  • 哪些內存須要回收
  • 何時回收
  • 怎麼回收

一、判斷對象是否存活

一、引用計數法:java

  • 給Java對象添加一個引用計數器,每當有一個地方引用它時,計數器+1;引用失效則-1。當計算器不爲0時,判斷對象存活
  • 缺點:若是兩個對象相互循環引用時,由於計算器不爲0,不能被回收。實際上對象應該被回收。

二、可達性分析算法:算法

(1)原理:
把"GC Roots"的對象做爲起點,而後向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,即該對象不可達,也就說明此對象是不可用的。

(2)可做爲GC Root對象:bash

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區常量引用的對象
  • 本地方法棧中JNI引用的鍍錫

三、判斷一個類可回收:數據結構

  • 該類全部實例對象被回收
  • 加載該類的ClassLoader已經被回收
  • 該類對應的Class對象沒被引用,沒法經過反射訪問該類的方法

二、引用類型

經過引用的強度分爲強、軟、弱、虛四種引用類型ui

  • 強應用:通常Object b=new Object()這類引用,只要強引用存在,GC就不會回收被引用的對象。
  • 軟引用:系統在發生內存溢出以前,會將這些對象二次回收。若是還沒足夠內存,纔會拋出內存溢出異常。經過SoftReference來實現。
  • 弱引用:GC回收時,不管內存是否足夠,都會收會被弱引用關聯的對象。經過WeakReference來實現。
  • 虛引用:做用是在對象被回收時收到一個系統通知。經過PhantomReference來實現。

三、垃圾收集算法

一、標記-清除算法:標記出全部須要回收的對象,標記完統一清除被標記的對象。spa

缺點:線程

  • 標記和清理的效率不高
  • 標記清除後會產生大量不連續的內存碎片

二、複製算法:將內存分爲大小相等的兩塊,每次只用一塊,當這一塊內存用完,將存活對象複製到另外一塊,將使用過的內存一次清理。指針

優缺點:code

  • 不會產生內存碎片的問題
  • 缺點是將內存縮小到了以前的一半
  • 在對象存活率高時進行屢次複製操做,效率會低。

三、標記-整理算法:標記須要回收的對象,將存活對象向一端移動(整理),清理掉可回收的對象。cdn

四、分代收集算法:根據對象存活週期不一樣,將Java堆內存分爲新生代和老年代。

  • 新生代:只有少許對象存活,使用複製算法。
  • 老年代:大量對象存活,使用標記清除或者標記整理算法。

3、類加載機制

一、類加載時機

一、定義:
把Class文件加載到內存中,並對數據進行校驗、解析和初始化,行成可被虛擬機直接使用的Java類型。類從被加載到虛擬機內存中開始,到卸載出內存結束。

二、生命週期:

  • 加載
  • 驗證
  • 準備
  • 解析
  • 初始化
  • 使用
  • 卸載 加載、驗證、準備、初始化、卸載的順序肯定。

三、須要對類進行初始化的場景

  • new實例化對象、讀取或設置類的靜態字段,調用類的靜態方法(被final修飾,已在編譯期將結果放入常量池的靜態字段除外)
  • 對類進行反射
  • 初始化一個類,若父類還沒初始化,先觸發父類的初始化
  • 需指定一個執行的主類(包含main方法的類),虛擬機先初始化該類
  • JDK1.7動態語言,MethodHandle實例解析結果REF_getStatis、REF_putStatis、REF_invokeStatis的方法句柄

四、不會方法初始化的場景:
全部引用類的方式不會觸發初始化,例如子類引用父類的靜態字段,只觸發父類初始化。

五、接口初始化和類初始化的區別:
接口初始化時,不要求其父類接口所有完成初始化。

二、類加載過程

包括加載、驗證、準備、解析、初始化5步

一、加載:

  • 經過類全限定名獲取定義該類的二進制字節流
  • 將字節流的靜態存儲結構轉換爲方法區的運行時數據結構
  • 內存中生成該類的Class對象,做爲訪問該類數據的入口

二、驗證:

  • 文件格式驗證,驗證字節流是否符合Class文件格式規範
  • 元數據驗證,對字節碼描述的信息進行語義分析
  • 字節碼驗證,肯定語義合法
  • 符號引用驗證,對常量池符號引用校驗

三、準備: 爲類變量(static修飾的變量)分配內存並設置變量初始值

四、解析: 將常量池符號引用替換爲直接引用(直接指向目標的指針)的過程

五、初始化: 開始執行類中定義的Java代碼

三、類加載器

同一個Class文件,被兩個不一樣的類加載器加載,這兩個類不相等。相等包括equals、instanceOf、isInstance方法返回的結果。

一、類別:

  • 啓動類加載器(Bootstrap ClassLoader):加載<JAVA_HOME>\lib目標,或者被-Xbootclasspath參數指定的路徑,可被虛擬機識別的類庫
  • 擴展類加載器(Extension ClassLoader):加載<JAVA_HOME>\lib\ext目錄,或被java.ext.dirs系統變量指定的路徑的類庫
  • 應用類加載器(Application ClassLoader):加載ClassPath上指定的類庫

二、雙親委託機制
除了頂層的類加載器外,其餘的類加載器都有本身的父類加載器。父子之間經過組合來複用父加載器代碼。
雙親委託機制的工做流程:一個類加載器收到類加載的請求,首先將請求委託給父類加載器去完成,最終全部加載請求都會傳遞給頂層的啓動加載器中。當父加載器發現未找到所需的類而沒法完成加載請求時,子加載器才嘗試去加載。

ClassLoader

protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
    //檢查請求的類是否已經被加載
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
                //讓父類加載器去嘗試加載
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {
            //父類加載器拋異常
        }

        if (c == null) {
            //而後調用自身的findClass方法來進行類加載
            c = findClass(name);
        }
    }
    return c;
}
複製代碼
  • 先檢查是否被加載過,若是沒有則調用父加載類去加載
  • 父加載器爲空,則調用啓動類加載器
  • 父加載器加載失敗,則拋出ClassNotFoundException異常
  • 而後去調用自身的findClass方法去進行類加載
相關文章
相關標籤/搜索