JVM 面試總結(jié)

介紹下 Java 內(nèi)存區(qū)域

線程私有的:

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

程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。字節(jié)碼解釋器工作時(shí)通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等功能都需要依賴這個(gè)計(jì)數(shù)器來(lái)完成。

2. java棧

棧內(nèi)存,主管Java程序的運(yùn)行,是在線程創(chuàng)建時(shí)創(chuàng)建,它的生命期是跟隨線程的生命期,線程結(jié)束棧內(nèi)存也就釋?zhuān)饕娣帕?種基本類(lèi)型的變量+對(duì)象的引用變量+實(shí)例方法都是在函數(shù)的棧內(nèi)存中分配。

3. 本地方法棧

和虛擬機(jī)棧所發(fā)揮的作用非常相似,區(qū)別是:?虛擬機(jī)棧為虛擬機(jī)執(zhí)行 Java 方法 (也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的 Native 方法服務(wù)。?在 HotSpot 虛擬機(jī)中和 Java 虛擬機(jī)棧合二為一。

本地方法被執(zhí)行的時(shí)候,在本地方法棧也會(huì)創(chuàng)建一個(gè)棧幀,用于存放該本地方法的局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、出口信息。

方法執(zhí)行完畢后相應(yīng)的棧幀也會(huì)出棧并釋放內(nèi)存空間,也會(huì)出現(xiàn)?StackOverFlowError?和?OutOfMemoryError?兩種錯(cuò)誤。

線程共享的:

1. 堆

Java 堆是所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例以及數(shù)組都在這里分配內(nèi)存。堆內(nèi)存分為三個(gè)部分 :新生代內(nèi)存(Young Generation)、老年代(Old Generation)、永久代(Permanent Generation)

新生代區(qū)域分為Eden區(qū)域?Survivor form區(qū)域和Survivor to區(qū)域,所有的類(lèi)都是在Eden被new出來(lái)的,當(dāng)垃圾回收器對(duì)Eden進(jìn)行回收的時(shí),如果對(duì)象還存活,則會(huì)進(jìn)入 s0 或者 s1,并且對(duì)象的年齡還會(huì)加 1(Eden 區(qū)->Survivor 區(qū)后對(duì)象的初始年齡變?yōu)?1),當(dāng)它的年齡增加到一定程度(默認(rèn)為 15 歲),就會(huì)被晉升到老年代中。對(duì)象晉升到老年代的年齡閾值,可以通過(guò)參數(shù)?-XX:MaxTenuringThreshold?來(lái)設(shè)置。

經(jīng)過(guò)這次 GC 后,Eden 區(qū)和"From"區(qū)已經(jīng)被清空。這個(gè)時(shí)候,"From"和"To"會(huì)交換他們的角色,也就是新的"To"就是上次 GC 前的“From”,新的"From"就是上次 GC 前的"To"。不管怎樣,都會(huì)保證名為 To 的 Survivor 區(qū)域是空的。Minor GC 會(huì)一直重復(fù)這樣的過(guò)程,直到“To”區(qū)被填滿,"To"區(qū)被填滿之后,會(huì)將所有對(duì)象移動(dòng)到老年代中。

2. 方法區(qū)

用于存儲(chǔ)虛擬機(jī)加載的:類(lèi)信息+普通常量+靜態(tài)常量+編譯器編譯后的代碼等等,方法區(qū)也被稱為永久代。1.8取而代之是元空間,元空間使用的是直接內(nèi)存。

3. 直接內(nèi)存?

直接內(nèi)存并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域,但是這部分內(nèi)存也被頻繁地使用。而且也可能導(dǎo)致 OutOfMemoryError 錯(cuò)誤出現(xiàn)。

JDK1.4 中新加入的?NIO(New Input/Output) 類(lèi),引入了一種基于通道(Channel)與緩存區(qū)(Buffer)的 I/O 方式,它可以直接使用 Native 函數(shù)庫(kù)直接分配堆外內(nèi)存,然后通過(guò)一個(gè)存儲(chǔ)在 Java 堆中的 DirectByteBuffer 對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。這樣就能在一些場(chǎng)景中顯著提高性能,因?yàn)楸苊饬嗽?Java 堆和 Native 堆之間來(lái)回復(fù)制數(shù)據(jù)。

本機(jī)直接內(nèi)存的分配不會(huì)受到 Java 堆的限制,但是,既然是內(nèi)存就會(huì)受到本機(jī)總內(nèi)存大小以及處理器尋址空間的限制。

Java 對(duì)象的創(chuàng)建過(guò)程



JVM垃圾回收

Java 堆是垃圾收集器管理的主要區(qū)域,因此也被稱作GC 堆(Garbage Collected Heap),GC按照回收的區(qū)域又分了兩種類(lèi)型,一種是普通GC(minor GC),一種是全局GC(major GC or Full GC)

普通GC(minor GC):只針對(duì)新生代區(qū)域的GC,指發(fā)生在新生代的垃圾收集動(dòng)作,因?yàn)榇蠖鄶?shù)Java對(duì)象存活率都不高,所以Minor GC非常頻繁,一般回收速度也比較快。

全局GC(major GC or Full GC):指發(fā)生在老年代的垃圾收集動(dòng)作,出現(xiàn)了Major GC,經(jīng)常會(huì)伴隨至少一次的Minor GC(但并不是絕對(duì)的)。Major GC的速度一般要比Minor GC慢上10倍以上

如何判斷對(duì)象已經(jīng)死亡


1. 引用計(jì)數(shù)法

給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它,計(jì)數(shù)器就加 1;當(dāng)引用失效,計(jì)數(shù)器就減 1;任何時(shí)候計(jì)數(shù)器為 0 的對(duì)象就是不可能再被使用的。

這個(gè)方法實(shí)現(xiàn)簡(jiǎn)單,效率高,但是目前主流的虛擬機(jī)中并沒(méi)有選擇這個(gè)算法來(lái)管理內(nèi)存,其最主要的原因是它很難解決對(duì)象之間相互循環(huán)引用的問(wèn)題。?所謂對(duì)象之間的相互引用問(wèn)題,如下面代碼所示:除了對(duì)象 objA 和 objB 相互引用著對(duì)方之外,這兩個(gè)對(duì)象之間再無(wú)任何引用。但是他們因?yàn)榛ハ嘁脤?duì)方,導(dǎo)致它們的引用計(jì)數(shù)器都不為 0,于是引用計(jì)數(shù)算法無(wú)法通知 GC 回收器回收他們。

2.?可達(dá)性分析算法

垃圾回收算法



1.? 標(biāo)記-清除算法

就是當(dāng)程序運(yùn)行期間,若可以使用的內(nèi)存被耗盡的時(shí)候,GC線程就會(huì)被觸發(fā)并將程序暫停,隨后將要回收的對(duì)象標(biāo)記一遍,最終統(tǒng)一回收這些對(duì)象,完成標(biāo)記清理工作接下來(lái)便讓?xiě)?yīng)用程序恢復(fù)運(yùn)行。老年代一般是由標(biāo)記清除或者是標(biāo)記清除與標(biāo)記整理的混合實(shí)現(xiàn)

主要進(jìn)行兩項(xiàng)工作,第一項(xiàng)則是標(biāo)記,第二項(xiàng)則是清除。?

? 標(biāo)記:從引用根節(jié)點(diǎn)開(kāi)始標(biāo)記遍歷所有的GC Roots, 先標(biāo)記出要回收的對(duì)象。

? 清除:遍歷整個(gè)堆,把標(biāo)記的對(duì)象清除。

? 缺點(diǎn):此算法需要暫停整個(gè)應(yīng)用,會(huì)產(chǎn)生內(nèi)存碎片

2.?? 標(biāo)記-復(fù)制算法

它可以將內(nèi)存分為大小相同的兩塊,每次使用其中的一塊。當(dāng)這一塊的內(nèi)存使用完后,就將還存活的對(duì)象復(fù)制到另一塊去,然后再把使用的空間一次清理掉。這樣就使每次的內(nèi)存回收都是對(duì)內(nèi)存區(qū)間的一半進(jìn)行回收。

制算法它的缺點(diǎn)也是相當(dāng)明顯的。

1、它浪費(fèi)了一半的內(nèi)存,這太要命了。

2、如果對(duì)象的存活率很高,我們可以極端一點(diǎn),假設(shè)是100%存活,那么我們需要將所有對(duì)象都復(fù)制一遍,并將所有引用地址重置一遍。復(fù)制這一工作所花費(fèi)的時(shí)間,在對(duì)象存活率達(dá)到一定程度時(shí),將會(huì)變的不可忽視。 所以從以上描述不難看出,復(fù)制算法要想使用,最起碼對(duì)象的存活率要非常低才行,而且最重要的是,我們必須要克服50%內(nèi)存的浪費(fèi)。

3. 標(biāo)記-整理算法

根據(jù)老年代的特點(diǎn)提出的一種標(biāo)記算法,標(biāo)記過(guò)程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對(duì)可回收對(duì)象回收,而是讓所有存活的對(duì)象向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存。

4. 分代收集算法

當(dāng)前虛擬機(jī)的垃圾收集都采用分代收集算法,這種算法沒(méi)有什么新的思想,只是根據(jù)對(duì)象存活周期的不同將內(nèi)存分為幾塊。一般將 java 堆分為新生代和老年代,這樣我們就可以根據(jù)各個(gè)年代的特點(diǎn)選擇合適的垃圾收集算法。

比如在新生代中,每次收集都會(huì)有大量對(duì)象死去,所以可以選擇”標(biāo)記-復(fù)制“算法,只需要付出少量對(duì)象的復(fù)制成本就可以完成每次垃圾收集。而老年代的對(duì)象存活幾率是比較高的,而且沒(méi)有額外的空間對(duì)它進(jìn)行分配擔(dān)保,所以我們必須選擇“標(biāo)記-清除”或“標(biāo)記-整理”算法進(jìn)行垃圾收集。

垃圾收集器




類(lèi)加載器

JVM 中內(nèi)置了三個(gè)重要的 ClassLoader,除了 BootstrapClassLoader 其他類(lèi)加載器均由 Java 實(shí)現(xiàn)且全部繼承自java.lang.ClassLoader:

BootstrapClassLoader(啟動(dòng)類(lèi)加載器)?:最頂層的加載類(lèi),由C++實(shí)現(xiàn),負(fù)責(zé)加載?%JAVA_HOME%/lib目錄下的jar包和類(lèi)或者或被?-Xbootclasspath參數(shù)指定的路徑中的所有類(lèi)。

ExtensionClassLoader(擴(kuò)展類(lèi)加載器)?:主要負(fù)責(zé)加載目錄?%JRE_HOME%/lib/ext?目錄下的jar包和類(lèi),或被?java.ext.dirs?系統(tǒng)變量所指定的路徑下的jar包。

AppClassLoader(應(yīng)用程序類(lèi)加載器)?:面向我們用戶的加載器,負(fù)責(zé)加載當(dāng)前應(yīng)用classpath下的所有jar包和類(lèi)。

雙親委派模型

當(dāng)一個(gè)類(lèi)收到了類(lèi)加載請(qǐng)求,他首先不會(huì)嘗試自己去加載這個(gè)類(lèi),而是把這個(gè)請(qǐng)求委派給父類(lèi)去完成,每一個(gè)層次類(lèi)加載器都是如此,因此所有的加載請(qǐng)求都應(yīng)該傳送到啟動(dòng)類(lèi)加載其中,只有當(dāng)父類(lèi)加載器反饋?zhàn)约簾o(wú)法完成這個(gè)請(qǐng)求的時(shí)候(在它的加載路徑下沒(méi)有找到所需加載的Class),子類(lèi)加載器才會(huì)嘗試自己去加載。

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

相關(guān)閱讀更多精彩內(nèi)容

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