這些面試必備的Java內(nèi)存管理知識你需要嗎?

0、Java 對內(nèi)存的劃分:

內(nèi)存劃分

Java虛擬機(jī)規(guī)范將物理內(nèi)存(主內(nèi)存和CPU中的緩存、寄存器)劃分為 程序計(jì)數(shù)器、Java 虛擬機(jī)棧本地方法棧、Java 堆、方法區(qū)五個區(qū)域,但并沒有規(guī)定這些區(qū)域的具體實(shí)現(xiàn),在其他地方聽到的一些名詞(如永久代、元空間等,這些都是方法區(qū)的具體實(shí)現(xiàn))可能都是這些區(qū)域具體的實(shí)現(xiàn),這點(diǎn)要特別注意,別被這些概念搞暈。

各個區(qū)域的特點(diǎn)如下表:

區(qū)域 線程關(guān)系 內(nèi)存異常 垃圾回收 作用
程序計(jì)數(shù)器 線程私有 記錄Java虛擬機(jī)正在指向的字節(jié)碼指令
Java 虛擬機(jī)棧 線程私有 StackOverflowError、OutOfMemoryError 描述 Java 方法執(zhí)行時的內(nèi)存模型,棧中棧幀存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法返回地址等信息。
本地方法棧 線程私有 StackOverflowError、OutOfMemoryError 描述本地方法(非 Java 代碼編寫)執(zhí)行時的內(nèi)存模型
方法區(qū) 線程共享 OutOfMemoryError 存儲虛擬機(jī)加載過的類信息、常量(常量池)、靜態(tài)變量、即時編譯器(JIT)生成的代碼
Java 堆 線程共享 OutOfMemoryError 存放 Java對象(實(shí)例)

1、類加載器:

類加載器分為 BootstrapExtension ClassLoader(Java9 中是 Platform ClassLoader)、Application ClassLoader,級別也是從高到低。

可以調(diào)用類加載器對象的 getParent() 方法查找該級加載器的上一級加載器,也成為父類加載器。

類加載器 描述 是否為 Java 實(shí)現(xiàn)
Bootstrap JVM啟動時創(chuàng)建,通常由操作系統(tǒng)相關(guān)的本地代碼實(shí)現(xiàn),是最根基的類加載器,負(fù)責(zé)裝載的是最核心的 Java 類,如 Object 類、System 類、String 類等
Extension ClassLoader 加載一些擴(kuò)展的系統(tǒng)類,如 XML、加密、壓縮相關(guān)功能的類
Application ClassLoader 加載用戶定義的 CLASSPATH 路徑下的類

此處不翻譯了,翻譯后就變味了,尤其是下面的 Parents Delegation Model 翻譯為雙親委派模型很不恰當(dāng)。

字節(jié)碼文件加載到內(nèi)存中,才可以實(shí)例化出類,而類加載器就是負(fù)責(zé)加載 Java 類的。低級別的類加載器在加載一個類時會先詢問上一級的類加載器,直到詢問到頂級的類加載器(Bootstrap),如果頂級的類加載器可以加載就加載該類,否則向下嘗試是否可以加載該類,也即是如果上一級類加載器能加載的就用上一級加載(復(fù)用上一級的類加載器),用不了再用自身的類加載器加載,這也就是口口相傳卻是翻譯很不恰當(dāng)?shù)碾p親委派模型。這樣做可以使類加載更加安全,避免加載和標(biāo)準(zhǔn) Java 類同包同名的類破壞虛擬機(jī)。
[圖片上傳失敗...(image-b827f6-1545720305999)]

可以根據(jù)需要繼承 Application ClassLoader 實(shí)現(xiàn)自定義類加載器,隔離加載器、修改類的加載方式、擴(kuò)展加載源、防止源碼泄露。

2、類加載的過程:

類加載是將字節(jié)碼文件實(shí)例化成 Class 對象并進(jìn)行相關(guān)初始化的過程。類加載包括類的加載(Load)、類的鏈接(Link)、類的初始化(init)三個步驟。

類的加載是將字節(jié)碼文件以二進(jìn)制流的方式讀取到內(nèi)存中并轉(zhuǎn)化為特定的數(shù)據(jù)結(jié)構(gòu),檢查 cafe baby 這個魔法數(shù)(是不是Java文件的標(biāo)志),是否有父類等,創(chuàng)建類對應(yīng)的 Class 對象。

類的鏈接又分為驗(yàn)證、準(zhǔn)備解析三個階段,驗(yàn)證階段是進(jìn)行更加詳細(xì)的校驗(yàn),如類型是否正確,靜態(tài)變量是否合理等;準(zhǔn)備階段是為類的靜態(tài)變量分配內(nèi)存空間,并設(shè)定默認(rèn)值;解析階段是保證類和類之間相互引用的正確性,完成類在內(nèi)存中的結(jié)構(gòu)布局。

類的初始化并不是初始化對象,而是根據(jù)代碼中的值初始化類的靜態(tài)變量值,類的靜態(tài)變量的初始化方式也有直接在聲明時指定值和在靜態(tài)代碼塊中指定值兩種方式。

3、訪問對象的兩種方式:

Java虛擬機(jī)棧中的局部變量表存放的數(shù)據(jù)除了基本的數(shù)據(jù)類型外,還有對象的引用類型(reference),這關(guān)系到如何訪問一個對象。

在不同的虛擬機(jī)中,對象的訪問方式也是不同的,主流的訪問方式有使用句柄直接指針兩種。

  • 使用句柄:
通過使用句柄訪問對象

使用句柄 是在 Java 堆中劃分出一塊區(qū)域作為句柄池,句柄池中存放對象的實(shí)例數(shù)據(jù)和類型數(shù)據(jù)(類相關(guān)的信息)的地址,reference 中存放的是對象對應(yīng)句柄中的地址,這是一種間接訪問對象方式。

  • 直接指針:
通過直接指針訪問對象

直接指針 是reference中直接存放對象的地址,但 Java 堆需要考慮如何存放訪問對象類型的指針。

兩種方式其實(shí)各有優(yōu)劣,如下表:

方式 優(yōu)勢 特點(diǎn)
使用句柄 reference 中存放的是穩(wěn)定的句柄地址,對象在移動時只改變句柄池中對象的地址,而reference中的地址不需要改變。 間接訪問
直接指針 節(jié)省了一次指針定位的時間開銷,訪問速度相對更快。 直接訪問

4、判斷對象是否可以回收的算法:

垃圾回收之前需要判斷對象是否可以回收,常見的判斷算法有引用計(jì)數(shù)算法和可達(dá)性分析算法。

引用計(jì)數(shù)算法:

每個對象都有對應(yīng)的引用計(jì)數(shù)器,當(dāng)有一個地方引用該對象時,就將引用計(jì)數(shù)器的值加1,當(dāng)引用失效時,就將引用計(jì)數(shù)器的值減1,當(dāng)計(jì)數(shù)器的值為0時,表示對象沒有引用,可以被回收了。

缺點(diǎn):看起來簡單高效,但是有循環(huán)引用問題。如果兩個對象中包含對方的引用就會產(chǎn)生循環(huán)引用問題,導(dǎo)致垃圾收集器不能回收對象。

可達(dá)性分析算法:

如果對象與GC Roots 之間沒有直接或間接的應(yīng)用關(guān)系,就可以被回收了。常見的 GC Roots 對象包括虛擬機(jī)棧(棧幀本地變量表)中引用的對象、方法區(qū)中靜態(tài)屬性引用的對象、方法區(qū)常量引用的對象、本地方法棧中(Native 方法)引用的對象。GC Roots,是一個特殊的對象,且絕對不能被其他對象引用,不然也會像引用計(jì)數(shù)算法那樣有循環(huán)引用的問題。

5、常見的垃圾回收算法:

  • 標(biāo)記-清除算法

最基本的垃圾回收算法,后續(xù)的算法都是對它的改進(jìn)。

首先標(biāo)記出需要回收的對象,再將標(biāo)記出的區(qū)域內(nèi)容清除。

缺點(diǎn)是:標(biāo)記時的查找效率,清除時產(chǎn)生內(nèi)存碎片。

標(biāo)記-清除算法
  • 標(biāo)記-復(fù)制算法

將內(nèi)存區(qū)域劃分為兩塊,每次只使用一塊,垃圾回收時,標(biāo)記正在使用的內(nèi)存區(qū)域,將存活的對象復(fù)制到另一塊內(nèi)存區(qū)域,再將原來的那一塊內(nèi)存區(qū)域一次性清除。避免了內(nèi)存碎片的產(chǎn)生,但不適合存活時間長的對象。

缺點(diǎn):浪費(fèi)了一半的內(nèi)存空間,當(dāng)對象存活率高時,進(jìn)行大量的復(fù)制操作,效率不高。

標(biāo)記-復(fù)制算法
  • 標(biāo)記-整理算法

標(biāo)記過程和標(biāo)記-除算法相同,垃圾回收時,是將存活的對象向同一端移動,再清除這之外的內(nèi)存區(qū)域,這樣就使得對象占用的內(nèi)存區(qū)域連續(xù),避免了內(nèi)存碎片的產(chǎn)生。

標(biāo)記-整理算法
  • 分代收集算法

根據(jù)對象存活時間的長短,將堆內(nèi)存分為新生代和老生代,存活時間短的對象放在新生代區(qū)域,存活時間長的大對象(如對象數(shù)組)放在老生代區(qū)域。新生代和老生代的比例是 1 : 2,新生代又分為一個 Eden 區(qū)和兩個 Survivor 區(qū)。新生代使用標(biāo)記-復(fù)制算法,老生代使用標(biāo)記-清除算法或標(biāo)記-整理算法,這樣最大發(fā)揮各自算法的優(yōu)勢。

分代收集算法

6、常見的垃圾回收器:

  • Serial 回收器

Serial 采取 “復(fù)制算法” 實(shí)現(xiàn),如果是在單 CPU 環(huán)境下,Serial 收集器沒有線程交互的開銷,理論上是可以獲得最高的單線程執(zhí)行效率,STW 的時間也可以控制在幾十到幾百毫秒內(nèi),這個時間是完全可以接受的。

  • Serial Old (PS MarkSweep)回收器

Serial Old 收集器 是 Serial 收集器的老年代版本,同樣也是一個單線程收集器,使用了 “標(biāo)記-整理算法”。

  • ParNew 回收器

ParNew 收集器實(shí)際上就是 Serial 收集器的多線程版本,收集算法、STW、對象分配的規(guī)則、回收策略等都與 Serial 收集器完全一樣,兩者相同的代碼很多。ParNew 收集器雖然有多線程優(yōu)勢,但在單 CPU 和多 CPU 環(huán)境下,效果并不一定會比 Serial 好,至少在單 CPU 環(huán)境下是肯定不如的 Serial 的。

  • Parallel Scavenge 回收器

Parallel Scavenge收集器和 ParNew 收集器很像,也是一個新生代收集器,也是使用復(fù)制算法,并且還是并行的多線程的收集器。相比于 ParNew 收集器,Parallel Scavenge收集器可以更加精準(zhǔn)的控制 CPU 的吞吐量和 STW 的時間,對于交互不多的任務(wù)可以更快地完成。

  • Parallel Old 回收器

Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本,使用多線程和 “標(biāo)記-整理算法”。在 Parallel Old 收集器出現(xiàn)之間,選擇了 Parallel Scavenge 收集器作為新生代的收集器,就只能選擇 Serial Old 收集器作為老生代收集器,這樣肯定就是對多 CPU 的浪費(fèi),所以 Parallel Scavenge收集器 + Parallel Old 收集器,對于多 CPU 環(huán)境吞吐量要求高的環(huán)境,算是強(qiáng)強(qiáng)聯(lián)合。

  • CMS 回收器

CMS (Concurrent Mark Sweep)收集器從英文名字上看就是基于 “標(biāo)記-清除算法” 實(shí)現(xiàn)的,并且還有并發(fā)的特點(diǎn),它是一種以縮短 STW 的時間為目標(biāo)的收集器,對于一些重視服務(wù)響應(yīng)速度的網(wǎng)站,肯定是 STW 越短,用戶體驗(yàn)越好,但是缺點(diǎn)是會在垃圾收集結(jié)束后產(chǎn)生大量的空間碎片。

通過初始標(biāo)記(Initial Mark)、并發(fā)標(biāo)記(Concurrent Mark)、重新標(biāo)記(Remark)、并發(fā)清除(Concurrent Sweep)四個步驟完成垃圾回收。

  • G1 回收器

G1 收集器是目前最先進(jìn)的收集器,它是基于 “標(biāo)記-復(fù)制算法” 實(shí)現(xiàn)的,所以不會產(chǎn)生內(nèi)存碎片,并且也可以精準(zhǔn)地控制 STW 的時間。G1 收集器對于新生代和老年代都是適用的,優(yōu)先回收垃圾最多的區(qū)域。

關(guān)于 Java 內(nèi)存管理的詳細(xì)內(nèi)容可參看 我的博客

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

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

  • Java和C++之間有一堵由內(nèi)存動態(tài)分配和垃圾收集技術(shù)所圍成的“高墻”,墻外面的人想進(jìn)來,墻里面的人想出來。 對象...
    胡二囧閱讀 1,333評論 0 4
  • 1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虛擬機(jī)(JVM)垃圾回收器提供...
    簡欲明心閱讀 90,389評論 17 311
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理,因此不免有一些不準(zhǔn)確的地方,同時不同JDK版本的...
    高廣超閱讀 16,070評論 3 83
  • 《深入理解Java虛擬機(jī)》筆記_第一遍 先取看完這本書(JVM)后必須掌握的部分。 第一部分 走近 Java 從傳...
    xiaogmail閱讀 5,482評論 1 34
  • 梅花問柳何時綠, 柳斥梅花開太急。 不見東風(fēng)曉才吹, 此時才有瀟瀟雨。 雨打梅枝梅知趣, 紅顏淡淡蕊向西。 花開花...
    云逸1108閱讀 354評論 0 0

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