JVM核心知識(shí)講解

前言

前一陣子在公司內(nèi)部做了一次技術(shù)分享尘盼,主要講的就是JVM核心知識(shí)购对。由于JVM涉及的知識(shí)太多太廣岂座,所以我就以個(gè)人的經(jīng)驗(yàn)把內(nèi)容做了一下精簡,只保留最核心的內(nèi)容堰汉,并且把核心的內(nèi)容都給抽出來辽社,讓大家記住最重要的部分。現(xiàn)在翘鸭,我把分享的內(nèi)容總結(jié)出來滴铅。(文中如有紕漏,還望您批評指正就乓,謝謝)

什么是JVM汉匙?

JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫拱烁,是通過在實(shí)際的計(jì)算機(jī)上仿真模擬各種計(jì)算機(jī)功能來實(shí)現(xiàn)的。由一套字節(jié)碼指令集噩翠、一組寄存器戏自、一個(gè)棧、一個(gè)垃圾回收堆和一個(gè)存儲(chǔ)方法域等組成伤锚。JVM屏蔽了與操作系統(tǒng)平臺(tái)相關(guān)的信息浦妄,使得Java程序只需要生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可在多種平臺(tái)上不加修改的運(yùn)行见芹,這也是Java能夠“一次編譯剂娄,到處運(yùn)行的”原因。

Java文件被加載到JVM的過程

Java代碼執(zhí)行過程.png

Java類的加載

1.四大主動(dòng)引用

  • 調(diào)用類的構(gòu)造方法 new Test();

  • 調(diào)用類的靜態(tài)變量玄呛、靜態(tài)方法已經(jīng)反射

  • 初始化子類(java規(guī)定:初始化子類的時(shí)候會(huì)先初始化其父類阅懦,所以父類會(huì)被加載)

  • 含有main方法類會(huì)被提前加載

    注意:調(diào)用常量不會(huì)引起類的加載

2.類的加載過程

未命名文件.png

類加載過程都做好了什么?

  • 準(zhǔn)備:給靜態(tài)變量開辟內(nèi)存空間徘铝,同時(shí)設(shè)置初始化的值(注意:這里不是代碼設(shè)置的初始化值耳胎,而是靜態(tài)默認(rèn)的缺省值,比如 int的默認(rèn)值是0)

  • 解析:把常量的符號(hào)引用改成直接引用(直接引用即為內(nèi)存地址)

  • 初始化:執(zhí)行靜態(tài)變量和靜態(tài)代碼塊(當(dāng)存在多個(gè)靜態(tài)變量和靜態(tài)代碼塊的時(shí)候惕它,按照從上到下的順序執(zhí)行)

總結(jié):當(dāng)一個(gè)類被主動(dòng)引用的時(shí)候怕午,會(huì)進(jìn)行類的加載,類的加載過程中會(huì)進(jìn)行靜態(tài)變量的初始化和靜態(tài)代碼塊的執(zhí)行淹魄,并且會(huì)把常量的符號(hào)引用替換成直接引用郁惜。整個(gè)類的加載過程網(wǎng)上一搜有一大堆,黑子加粗的是需要大家必須知道和記住甲锡。

為什么靜態(tài)方法中不能引入非靜態(tài)變量兆蕉?

答:當(dāng)類被加載的時(shí)候,非靜態(tài)變量還沒有得到初始化缤沦,而靜態(tài)方法不需要擁有對象實(shí)例就可以得到執(zhí)行虎韵,所以靜態(tài)方法中不能引用非靜態(tài)變量。

為什么可以使用 private static final Singleton instance = new Singleton();這種方式做單例模式缸废?

答:因?yàn)轭愔粫?huì)被初始化加載一次包蓝,當(dāng)類被加載的時(shí)候靜態(tài)變量會(huì)得到初始化,Java的枚舉類就是利用這種方式企量,里面的枚舉類型的數(shù)據(jù)就是靜態(tài)常量

類加載器

  • 啟動(dòng)類加載器: 負(fù)責(zé)加載存放在<JAVA_HOME>\lib目錄中的類测萎;被-Xbootclasspath參數(shù)所指定路徑中、并且是被虛擬機(jī)識(shí)別的類庫

  • 擴(kuò)展類加載器:負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中的類庫梁钾;被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫

  • 應(yīng)用類加載器:負(fù)責(zé)加載 用戶類路徑(ClassPath)上所指定的類庫(如無特殊指定绳泉,我們的寫的在項(xiàng)目里寫的java代碼都是被這個(gè)類加載器進(jìn)行加載的)

    類加載器.png

4.雙親委派模型

當(dāng)一個(gè)類被加載的時(shí)候逊抡,所有的類加載器都會(huì)把加載任務(wù)交給父加載器姆泻,結(jié)合上這張圖來看零酪,也就說雙親委派模型最后都會(huì)交給啟動(dòng)類加載器進(jìn)行加載,只有當(dāng)父類加載器加載不了的時(shí)候才會(huì)交給子類進(jìn)行加載拇勃。

protected Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
        synchronized(this.getClassLoadingLock(var1)) {
            Class var4 = this.findLoadedClass(var1);
            if (var4 == null) {
                long var5 = System.nanoTime();

                try {
                    if (this.parent != null) {
                        var4 = this.parent.loadClass(var1, false);//先讓父類進(jìn)行加載
                    } else {
                        var4 = this.findBootstrapClassOrNull(var1);//當(dāng)parent為null的時(shí)候使用啟動(dòng)類加載器加載
                    }
                } catch (ClassNotFoundException var10) {
                }

                if (var4 == null) {
                    long var7 = System.nanoTime();
                    var4 = this.findClass(var1);//當(dāng)父類加載不了的時(shí)候才會(huì)交給子類進(jìn)行加載
                    PerfCounter.getParentDelegationTime().addTime(var7 - var5);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(var7);
                    PerfCounter.getFindClasses().increment();
                }
            }

            if (var2) {
                this.resolveClass(var4);
            }

            return var4;
        }
    }

優(yōu)點(diǎn):保證了類的唯一性

注意:雙親委派模型是java推薦的一種技術(shù)模型四苇,不是自定義ClassLoader加載器必須遵守的,另外這里的雙親指的就是父類方咆,別問我我也不知道為啥非要叫雙親

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

運(yùn)行時(shí)數(shù)據(jù)區(qū)

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

JVM的運(yùn)行時(shí)數(shù)據(jù)區(qū)主要5個(gè)部分組成月腋,分別是方法區(qū)、程序計(jì)數(shù)器瓣赂、方法棧榆骚、本地方法棧以及堆內(nèi)存。其中用紅色標(biāo)注的堆內(nèi)存和方法區(qū)是線程共享煌集,剩下的三個(gè)是線程私有的妓肢。

方法區(qū)

方法區(qū),又叫永久代苫纤,jdk1.6以后也叫元空間碉钠。是JVM內(nèi)存區(qū)域中比較穩(wěn)定的一塊,很少發(fā)生GC卷拘。在方法區(qū)中存放類信息喊废、靜態(tài)變量以及常量等信息。方法區(qū)是線程共享的栗弟,存在線程安全問題污筷。

棧內(nèi)存

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

也叫線程棧,每一個(gè)線程都有線程棧乍赫,每一個(gè)方法都是一個(gè)棧幀颓屑,在方法從被調(diào)用到結(jié)束的過程就是對應(yīng)棧幀入棧以及出棧的過程。在棧幀中又有4個(gè)區(qū)域

  • 局部變量表:存放方法中的局部變量
  • 操作數(shù)棧:執(zhí)行方法中的各種操作耿焊,例如:賦值之類的操作
  • 動(dòng)態(tài)鏈接:把方法中的符號(hào)引用轉(zhuǎn)化為直接引用(直接引用即內(nèi)存地址)
  • 方法出口:記錄返回值以
    及程序當(dāng)前執(zhí)行的位置

本地方法棧

同方法棧類似揪惦,存放的是本地調(diào)用方法(即native修飾的,如public static native void yield();

程序計(jì)數(shù)器

線程私有罗侯,每個(gè)線程會(huì)有一個(gè)區(qū)域存放程序計(jì)數(shù)器

作用:記錄所在線程執(zhí)行到了哪一步

場景:當(dāng)某個(gè)所有的線程的時(shí)間片被搶走時(shí)器腋,過了一段時(shí)間再被恢復(fù)以后,該線程的代碼不會(huì)從頭執(zhí)行钩杰,而是會(huì)按照程序計(jì)數(shù)器所記錄的位置執(zhí)行

堆內(nèi)存

運(yùn)行時(shí)數(shù)據(jù)區(qū)中棧內(nèi)存最大纫塌,線程共線,是主要發(fā)生GC的位置讲弄,存放的是Java實(shí)例對象措左,也就是說所有通過new的對象都存在這里。在堆內(nèi)存中避除,有劃分了成了新生代和老年代怎披,默認(rèn)比例1:2胸嘁,在新生代中又劃分為了eden、sur0和sur1,默認(rèn)的比例是8:1:1凉逛,所有剛new的出來的對象都會(huì)放到eden區(qū)域中性宏。


堆內(nèi)存.png

總結(jié)

JVM內(nèi)存結(jié)構(gòu)是非常重要的一個(gè)部分,有幾個(gè)關(guān)鍵的點(diǎn)一定要記鬃捶伞:

  • 所有new的對象一定存在堆內(nèi)存中

  • 非靜態(tài)成員變量存在堆內(nèi)存中毫胜,局部變量都在棧內(nèi)存中

    通過代碼再來加固一下

public class Test {

    public static User user = new User();//static修飾 user這個(gè)實(shí)例存在方法區(qū) new User()生成的對象在堆區(qū),此時(shí)user引用了堆區(qū)的對象

    private int a = 10;//a存在了堆區(qū)诬辈,引用了棧里面的整型變量10
    
    private Date date = new Date();//date存在堆區(qū)酵使,new Date()生成的對象也在堆區(qū),date引用了生成的對象

    private int compute() {
        int a = 1;
        int b = 2;
        User user = new User();//注意此時(shí)user是局部變量焙糟,所以存在棧區(qū)凝化,而new User()生成的對象在堆區(qū),此時(shí)棧區(qū)的user引用了堆區(qū)生成的對象
        int c = (a + b) * 10;
        int d = com();
        return c;
    }

    public static void main(String[] args) {
        Test test = new Test();
        System.out.println(test.compute());
    }
    
    }

垃圾回收

垃圾回收即GC酬荞,剛才講堆內(nèi)存的時(shí)候搓劫,提到了堆內(nèi)存是存放的java對象,是主要發(fā)生GC的位置混巧。

垃圾回收.png

這里需要注意的是枪向,垃圾回收器在判斷一個(gè)對象是否存活時(shí)用的是可達(dá)性分析法∵值常可達(dá)性分析法是判斷一個(gè)對象有沒有被GC ROOT直接或者間接持有秘蛔,只要被GC ROOT直接或者間接持有,那么該對象就是存活狀態(tài)傍衡,或者即為死亡深员。

GC ROOT:

1.方法棧(局部變量表)中引用的對象
2.本地方法棧 中 JNI引用的對象
3.方法區(qū) 中常量、類靜態(tài)屬性引用的對象

總結(jié)

本文主要講了類的加載過程蛙埂、JVM的內(nèi)存結(jié)構(gòu)以及垃圾回收倦畅。由于我個(gè)人讀過很多JVM的文章都寫得太多太詳盡,反正讀多了容易掌握不到重點(diǎn)绣的,而本文就是對核心知識(shí)一個(gè)總結(jié)叠赐,雖然不夠詳盡,但是無論是工作還是面試當(dāng)中應(yīng)該都夠用了屡江。如果文章有錯(cuò)誤的內(nèi)容或者漏講了一些重要的知識(shí)點(diǎn)芭概,歡迎大家在評論區(qū)給我留言,謝謝~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惩嘉,一起剝皮案震驚了整個(gè)濱河市罢洲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌文黎,老刑警劉巖惹苗,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件殿较,死亡現(xiàn)場離奇詭異,居然都是意外死亡鸽粉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門抓艳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來触机,“玉大人,你說我怎么就攤上這事玷或±苁祝” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵偏友,是天一觀的道長蔬胯。 經(jīng)常有香客問我,道長位他,這世上最難降的妖魔是什么氛濒? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮鹅髓,結(jié)果婚禮上舞竿,老公的妹妹穿的比我還像新娘。我一直安慰自己窿冯,他們只是感情好骗奖,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著醒串,像睡著了一般执桌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芜赌,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天仰挣,我揣著相機(jī)與錄音,去河邊找鬼缠沈。 笑死椎木,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的博烂。 我是一名探鬼主播香椎,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼禽篱!你這毒婦竟也來了畜伐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤躺率,失蹤者是張志新(化名)和其女友劉穎玛界,沒想到半個(gè)月后万矾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡慎框,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年良狈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笨枯。...
    茶點(diǎn)故事閱讀 40,427評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡薪丁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出馅精,到底是詐尸還是另有隱情严嗜,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布洲敢,位于F島的核電站漫玄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏压彭。R本人自食惡果不足惜睦优,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望壮不。 院中可真熱鬧刨秆,春花似錦、人聲如沸忆畅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽家凯。三九已至缓醋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绊诲,已是汗流浹背送粱。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掂之,地道東北人抗俄。 一個(gè)月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像世舰,于是被迫代替她去往敵國和親动雹。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評論 2 359