十個(gè)問題弄清JVM&GC
每個(gè)java開發(fā)同學(xué)不管是日常工作中還是面試?yán)铮紩?huì)遇到JDK、JVM和GC的問題。本文會(huì)從以下10個(gè)問題為切入點(diǎn),帶著大家一起全面了解一下JVM的方方面面。
- JVM、JRE和JDK的區(qū)別和聯(lián)系
- JVM是什么?以及它的主要作用
- JVM的核心功能有哪些
- 類加載機(jī)制和過程
- 運(yùn)行時(shí)數(shù)據(jù)區(qū)的邏輯結(jié)構(gòu)
- JVM的內(nèi)存模型
- 如何確定對(duì)象是垃圾
- 垃圾收集的算法有哪些
- 各種問世的垃圾收集器
- JVM調(diào)優(yōu)的參數(shù)配置
1、JVM、JRE和JDK的區(qū)別和聯(lián)系
這個(gè)基本是步入java世界的入門級(jí)知識(shí)認(rèn)知,首先我們來(lái)看一下來(lái)自java官網(wǎng)的一張圖:

從這張圖里我們基本就可以看出“JRE”是運(yùn)行Java語(yǔ)言編寫的程序所不可缺少的運(yùn)行環(huán)境。有了JRE我們寫的java程序才可以運(yùn)行起來(lái)被用戶所使用。
而“JDK”俗稱java開發(fā)工具包,它包括了Java運(yùn)行環(huán)境JRE(Java Runtime Envirnment)以及一堆Java工具(javac/java/jdb等)和Java基礎(chǔ)的類庫(kù)(即Java API 包括rt.jar)。
但不管是JRE還是JDK都是以JVM為基石的??梢哉f(shuō)JVM是java程序可以在某臺(tái)機(jī)器上得以運(yùn)行的最底層的保障。
2、那么什么是JVM?它的主要作用又是什么?
JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫,它的用途簡(jiǎn)單的說(shuō)就是它能讓我們寫的java程序在不同的操作系統(tǒng)的不同CPU上運(yùn)行。我們寫的java程序會(huì)利用開發(fā)工具(如Intellij idea)把它編譯成.class文件,但這個(gè)class文件是不能直接被操作系統(tǒng)識(shí)別運(yùn)行的,需要利用jvm按jvm規(guī)范將編譯好的.class文件轉(zhuǎn)變成機(jī)器語(yǔ)言,再交由操作系統(tǒng)提交給cpu去執(zhí)行。

用一句話評(píng)價(jià)JVM的主要作用就是:JVM屏蔽了與具體操作系統(tǒng)平臺(tái)相關(guān)的信息,使得Java程序只需生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可以在多種平臺(tái)上不加修改地運(yùn)行。
3、這么牛的JVM的核心功能有哪些?
JVM中核心的功能總體有三塊:
- 類加載器:在JVM啟動(dòng)時(shí)或者在類運(yùn)行時(shí)將需要的class文件加載到JVM中
- 執(zhí)行引擎:負(fù)責(zé)執(zhí)行class文件,包括分配運(yùn)行時(shí)數(shù)據(jù)區(qū)(如程序計(jì)數(shù)器、本地方法棧和虛擬棧)和 最終將class中的字節(jié)碼指令轉(zhuǎn)為機(jī)器指令通過操作系統(tǒng)交給CPU執(zhí)行
- 垃圾回收器:對(duì)JVM的堆內(nèi)存進(jìn)行管理,及時(shí)回收調(diào)無(wú)用的資源釋放內(nèi)存空間
4、JVM類的加載機(jī)制和過程?
首先,我們談?wù)勯_發(fā)工具編譯生成的class文件是如何被JVM加載的。所謂的類加載機(jī)制其實(shí)就是:虛擬機(jī)(JVM)把class文件加載到內(nèi)存中,然后對(duì)它進(jìn)行正確性的校驗(yàn),檢查通過再進(jìn)行解析和初始化,最終把class文件變成一個(gè)內(nèi)存中可以直接使用的java.lang.Class對(duì)象。
從一個(gè)class文件的裝載到銷毀,它的生命周期基本可以分為以下五個(gè)階段:裝載、鏈接(驗(yàn)證、準(zhǔn)備和解析)、初始化、使用和卸載。

- 裝載:裝載(Load)階段總共有三項(xiàng)工作(1)通過類的全限定名獲取其定義的二進(jìn)制字節(jié)流,需要借助類裝載器(ClassLoader)完成;(2)在運(yùn)行時(shí)數(shù)據(jù)區(qū)的“方法區(qū)”中分配一塊區(qū)域保存這個(gè)類的信息,包括類的基本信息、常量和靜態(tài)變量等等;(3)在“Java堆”內(nèi)存上生成一個(gè)該類的java.lang.Class對(duì)象,用于對(duì)外暴露使用該類的入口。
- 鏈接:鏈接(link)階段同樣有三項(xiàng)工作(1)驗(yàn)證(Verify),驗(yàn)證文件格式、元數(shù)據(jù)、字節(jié)碼和符號(hào)引用,以保證被加載類的準(zhǔn)確性;(2)準(zhǔn)備(Prepare),為靜態(tài)變量分配內(nèi)存并初始化為默認(rèn)值。(3)解析(Resolve),解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程。解析動(dòng)作主要針對(duì)類或接口、字段、類方法、接口方法、方法類型、方法句柄和調(diào)用限定符7類符號(hào)引用進(jìn)行。
- 初始化:初始化(Initialize)階段所做的工作就是對(duì)類的靜態(tài)成員變量和靜態(tài)方法進(jìn)行初始化賦值或調(diào)用。
比如上面的靜態(tài)變量age初始化之后的值變?yōu)榱?0。
在裝載階段的第(2),(3)步可以發(fā)現(xiàn)有運(yùn)行時(shí)數(shù)據(jù)區(qū),堆,方法區(qū)等名詞,那么究竟什么是“運(yùn)行時(shí)數(shù)據(jù)區(qū)”,它有哪些結(jié)構(gòu)構(gòu)成?
5、什么是JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)?及其邏輯結(jié)構(gòu)
“運(yùn)行時(shí)數(shù)據(jù)區(qū)”是JVM在執(zhí)行Java程序的過程中出于內(nèi)存管理方面的目的,在設(shè)計(jì)上把內(nèi)存分為若干個(gè)不同的區(qū)域。這些區(qū)域有著各自的用途,有的區(qū)域生命周期跟虛擬機(jī)一樣,隨著虛擬機(jī)進(jìn)程的啟動(dòng)而存在,伴隨這虛擬機(jī)的進(jìn)程結(jié)束而消亡。而有些區(qū)域則依賴用戶線程的啟動(dòng)和結(jié)束而建立和銷毀。具體如下圖:

方法區(qū)(Method Area):
(1)用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù);
(2)方法區(qū)是各個(gè)線程共享的內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,因?yàn)橥粋€(gè)class類信息只需要加載一份就夠了;
(3)java虛擬機(jī)規(guī)范中把方法區(qū)描述為堆內(nèi)存的一個(gè)邏輯部分,但它有另外一個(gè)別名叫“非堆”,用于與java堆區(qū)分開來(lái)。在JDK8之前方法區(qū)叫做Perm space,在JDK8及以后叫做Metaspace(即元數(shù)據(jù)區(qū))。
堆(Heap):Java堆是被所有線程共享,虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,此內(nèi)存區(qū)域唯一的目的就是存放對(duì)象實(shí)例,在Java虛擬機(jī)規(guī)范中的描述是:所有的對(duì)象實(shí)例以及數(shù)組都要在堆上分配,但是隨著JIT編譯器的發(fā)展和逃逸分析技術(shù)逐漸成熟,棧上分配,標(biāo)量替換優(yōu)化技術(shù)將會(huì)導(dǎo)致一些微妙的變化發(fā)生,所有的對(duì)象都分配在堆上也就變得不那么絕對(duì)了。
虛擬機(jī)棧(Java Virtual Machine Stacks):虛擬機(jī)棧是線程私有的或者說(shuō)是獨(dú)有的,隨著線程的創(chuàng)建而創(chuàng)建。一個(gè)線程的運(yùn)行狀態(tài)(正在調(diào)用哪個(gè)方法),就是由這個(gè)線程對(duì)應(yīng)的虛擬機(jī)棧來(lái)保存的。
每一個(gè)被線程執(zhí)行的方法,為虛擬機(jī)棧中的一個(gè)棧幀,調(diào)用一個(gè)方法,就會(huì)向棧中壓入一個(gè)棧幀;一個(gè)方法調(diào)用完成,就會(huì)把該棧幀從棧中彈出。如下圖解:

程序計(jì)數(shù)器(The Pc Register):我們都知道一個(gè)JVM進(jìn)程中有多個(gè)線程在執(zhí)行,而線程中的內(nèi)容是否能夠擁有執(zhí)行權(quán),是根據(jù)CPU調(diào)度來(lái)的。假如線程A正在執(zhí)行到某個(gè)地方,突然失去了CPU的執(zhí)行權(quán),切換到線程B了,然后當(dāng)線程A再獲得CPU執(zhí)行權(quán)的時(shí)候,怎么能繼續(xù)執(zhí)行呢?這就是需要在線程中維護(hù)一個(gè)變量,記錄線程執(zhí)行到的位置,這就是程序計(jì)數(shù)器。
本地方法棧(Native Method Stacks):本地方法棧與虛擬機(jī)棧所發(fā)揮的作用非常相似,他們之間的區(qū)別不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)中使用到的native方法服務(wù)。即如果當(dāng)前線程執(zhí)行的方法是Native類型的,這些方法就會(huì)在本地方法棧中執(zhí)行。
總結(jié)一下,就JVM的設(shè)計(jì)規(guī)范,從使用用途角度JVM的內(nèi)存大體的分為:線程私有內(nèi)存區(qū) 和 線程共享內(nèi)存區(qū)。

線程私有內(nèi)存區(qū)在類加載器編譯某個(gè)class文件時(shí)就確定了執(zhí)行時(shí)需要的“程序計(jì)數(shù)器”和“虛擬棧幀”等所需的空間,并且會(huì)伴隨著當(dāng)前執(zhí)行線程的產(chǎn)生而產(chǎn)生,執(zhí)行線程的消亡而消亡,因此“線程私有內(nèi)存區(qū)”并不需要考慮內(nèi)存管理和垃圾回收的問題。
線程共享內(nèi)存區(qū)在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,被所有線程共享,是Java虛擬機(jī)所管理內(nèi)存中最應(yīng)該關(guān)注的和最大的一塊。