JVM探秘之旅(壹)

本文準(zhǔn)備從以下幾個(gè)方面去講解JVM:
1)JVM內(nèi)存結(jié)構(gòu)解析
2)JVM的類加載機(jī)制剖析
3)GC垃圾回收機(jī)制

JVM內(nèi)存結(jié)構(gòu)解析

一張圖可以看出jvm的內(nèi)存結(jié)構(gòu)


內(nèi)存結(jié)構(gòu)1.png

java代碼片段

/**
 * @author :huin
 * @date :Created in 2019/10/11 10:03
 * @description:jvm分析過程
 */
public class DemoTest {

    public static int compute(){
        int a = 1;
        int b = 2;
        int c = (a+b)*10;
        return c;
    }
    public static void main(String[] args) {
        int math = compute();
        System.out.println(math);
    }
}
編譯3.png
反匯編指令4.png

看著圖來分析代碼在虛擬機(jī)中執(zhí)行過程:
·1) .java文件經(jīng)過編譯器編譯成.class文件,通過類加載子系統(tǒng)加載到運(yùn)行時(shí)數(shù)據(jù)區(qū),然后java文件中的對象放到堆內(nèi)存中,類中的常量和靜態(tài)變量和放入方法區(qū)中,數(shù)據(jù)區(qū)中的棧,本地方法棧和程序計(jì)數(shù)器時(shí)線程私有的,堆和方法區(qū) (1.8之后變成了元空間)。
·2) 程序中main線程和compute()線程都是在棧內(nèi)存中,然后把圖2的代碼先編譯成.class文件命令是:javac DemoTest.java,然后用 javap -c DemoTest.class >demoTest.txt 反匯編并追加到txt文件中。
·3) 根據(jù)JVM指令手冊:http://www.itdecent.cn/p/d62b9ccb80b6 可以找到對應(yīng)的指令。
·4) compute()棧幀中的局部變量存變量值,abc,操作數(shù)棧式變量之間的運(yùn)算并壓入局部變量表中。
·5) 程序執(zhí)行到方法出口compute()運(yùn)行完返回出來,然后最終結(jié)構(gòu)在main主線程中打印出來。程序計(jì)數(shù)器就是程序每執(zhí)行一步,計(jì)數(shù)器加1。(程序計(jì)數(shù)器還可以用作線程之間來回切換的記錄,一個(gè)線程執(zhí)行的時(shí)候被切換到另外一個(gè)線程。當(dāng)切回來的時(shí)候,根據(jù)程序計(jì)數(shù)器的記錄能恢復(fù)到第一個(gè)線程執(zhí)行到的位置繼續(xù)執(zhí)行)最終的結(jié)果壓入堆中,程序結(jié)束。

JVM的類加載過程

1、類加載過程多個(gè)java文件經(jīng)過編譯打包生成可運(yùn)行jar包,最終由java命令運(yùn)行某個(gè)主類的main函數(shù)啟動(dòng)程序,這里首先需要通過類加載器把主類加載到JVM。主類在運(yùn)行過程中如果使用到其它類,會(huì)逐步加載這些類。注意,jar包里的類不是一次性全部加載的,是使用到時(shí)才加載。

類加載到使用整個(gè)過程有如下幾步:加載 >> 驗(yàn)證 >> 準(zhǔn)備 >> 解析 >> 初始化 >> 使用 >> 卸載

·1)加載:在硬盤上查找并通過IO讀入字節(jié)碼文件,使用到類時(shí)才會(huì)加載,例如調(diào)用類的main()方法,new對象等等
·2)驗(yàn)證:校驗(yàn)字節(jié)碼文件的正確性
·3)準(zhǔn)備:給類的靜態(tài)變量分配內(nèi)存,并賦予默認(rèn)值
·4)解析:將符號(hào)引用替換為直接引用,該階段會(huì)把一些靜態(tài)方法(符號(hào)引用,比如main()方法)替換為指向數(shù)據(jù)所存內(nèi)存的指針或句柄等(直接引用),這是所謂的靜態(tài)鏈接過程(類加載期間完成),動(dòng)態(tài)鏈接是在程序運(yùn)行期 間完成的將符號(hào)引用替換為直接引用。
·5)初始化:對類的靜態(tài)變量初始化為指定的值,執(zhí)行靜態(tài)代碼塊


image.png

2、類加載器和雙親委派機(jī)制(還有一種是全局委托機(jī)制:一個(gè)類被委托了,其他與之關(guān)聯(lián)的類都被一個(gè)加載器加載)上面的類加載過程主要是通過類加載器來實(shí)現(xiàn)的,Java里有如下幾種類加載器
·啟動(dòng)類加載器(bootstrap classLoader):負(fù)責(zé)加載支撐JVM運(yùn)行的位于JRE的lib目錄下的核心類庫,比如rt.jar、charsets.jar等
·擴(kuò)展類加載器(ext classLoader):負(fù)責(zé)加載支撐JVM運(yùn)行的位于JRE的lib目錄下的ext擴(kuò)展目錄中的JAR類包
·應(yīng)用程序類加載器(app classLoader):負(fù)責(zé)加載ClassPath路徑下的類包,主要就是加載你自己寫的那些類
·自定義加載器(customize classLoader):負(fù)責(zé)加載用戶自定義路徑下的類包

看一個(gè)類加載器示例:
/**
 * @author :huin
 * @date :Created in 2019/10/12 8:59
 * @description:dd
 */
public class TestJDKClassLoader {
    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());
        System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
        System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
        System.out.println(ClassLoader.getSystemClassLoader().getClass().getName());
    }
}
運(yùn)行結(jié)果:
null //啟動(dòng)類加載器是C++語言實(shí)現(xiàn),所以打印不出來
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$AppClassLoader

Process finished with exit code 0

雙親委派機(jī)制JVM類加載器是有親子層級結(jié)構(gòu)的,如下圖


image.png

這里類加載其實(shí)就有一個(gè)雙親委派機(jī)制,加載某個(gè)類時(shí)會(huì)先委托父加載器尋找目標(biāo)類,找不到再委托上層父加載器加載,如果所有父加載器在自己的加載類路徑下都找不到目標(biāo)類,則在自己的類加載路徑中查找并載入目標(biāo)類。比如我們的Math類,最先會(huì)找應(yīng)用程序類加載器加載,應(yīng)用程序類加載器會(huì)先委托擴(kuò)展類加載器加載,擴(kuò)展類加載器再委托啟動(dòng)類加載器,頂層啟動(dòng)類加載器在自己的類加載路徑里找了半天沒找到Math類,則向下退回加載Math類的請求,擴(kuò)展類加載器收到回復(fù)就自己加載,在自己的類加載路徑里找了半天也沒找到Math類,又向下退回Math類的加載請求給應(yīng)用程序類加載器,應(yīng)用程序類加載器于是在自己的類加載路徑里找Math類,結(jié)果找到了就自己加載了。。雙親委派機(jī)制說簡單點(diǎn)就是,先找父親加載,不行再由兒子自己加載。

GC垃圾回收機(jī)制

GC回收過程:

image.png

1.7 Eden與Survivor區(qū)默認(rèn)8:1:1大量的對象被分配在eden區(qū),eden區(qū)滿了后會(huì)觸發(fā)minor gc,可能會(huì)有99%以上的對象成為垃圾被回收掉,剩余存活的對象會(huì)被挪到為空的那塊survivor區(qū),下一次eden區(qū)滿了后又會(huì)觸發(fā)minor gc,把eden區(qū)和survivor去垃圾對象回收,把剩余存活的對象一次性挪動(dòng)到另外一塊為空的survivor區(qū),因?yàn)樾律膶ο蠖际浅λ赖?,存活時(shí)間很短,所以JVM默認(rèn)的8:1:1的比例是很合適的,讓eden區(qū)盡量的大,survivor區(qū)夠用即可JVM默認(rèn)有這個(gè)參數(shù)-XX:+UseAdaptiveSizePolicy,會(huì)導(dǎo)致這個(gè)比例自動(dòng)變化,如果不想這個(gè)比例有變化可以設(shè)置參數(shù)-XX:-UseAdaptiveSizePolicy

image.png

如何判斷對象可以被回收

2.1 引用計(jì)數(shù)法給對象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它,計(jì)數(shù)器就加1;當(dāng)引用失效,計(jì)數(shù)器就減1;任何時(shí)候計(jì)數(shù)器為0的對象就是不可能再被使用的。
堆中幾乎放著所有的對象實(shí)例,對堆垃圾回收前的第一步就是要判斷哪些對象已經(jīng)死亡(即不能再被任何途徑使用的對象)。
這個(gè)方法實(shí)現(xiàn)簡單,效率高,但是目前主流的虛擬機(jī)中并沒有選擇這個(gè)算法來管理內(nèi)存,其最主要的原因是它很難解決對象之間相互循環(huán)引用的問題。 所謂對象之間的相互引用問題,如下面代碼所示:除了對象objA 和 objB 相互引用著對方之外,這兩個(gè)對象之間再無任何引用。但是他們因?yàn)榛ハ嘁脤Ψ?,?dǎo)致它們的引用計(jì)數(shù)器都不為0,于是引用計(jì)數(shù)算法無法通知 GC 回收器回收他們。

2.2 可達(dá)性分析算法這個(gè)算法的基本思想就是通過一系列的稱為“GC Roots” 的對象作為起點(diǎn),從這些節(jié)點(diǎn)開始向下搜索,找到的對象都標(biāo)記為非垃圾對象,其余未標(biāo)記的對象都是垃圾對象GC Roots根節(jié)點(diǎn):線程棧的本地變量、靜態(tài)變量、本地方法棧的變量等等


image.png

垃圾收集器:

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容