面試復(fù)習(xí)-Java虛擬機

一、Java內(nèi)存區(qū)域

1.Java虛擬機運行時數(shù)據(jù)區(qū)

  • 程序計數(shù)器(線程私有):如果執(zhí)行的是Java方法,則這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址,如果是Native方法,這個計數(shù)器值為空。
  • Java虛擬機棧(線程私有):每個方法在執(zhí)行時都會創(chuàng)建一個棧幀,用于存儲局部變量表,操作數(shù)棧,動態(tài)鏈接,方法出口、常量池引用等信息。通過-Xss來指定虛擬機棧內(nèi)存的大小。
  • 本地方法棧:為Native方法服務(wù)。
  • Java堆(線程共享):所有對象在這里分配內(nèi)存,是垃圾收集的主要區(qū)域。-Xms設(shè)置堆初始大小,-Xmx設(shè)置堆最大內(nèi)存。堆可以細(xì)分為新生代和老年代,再細(xì)致一點可以分為Eden,F(xiàn)rom Survivor,To Survivor。
  • 方法區(qū)(線程共享):用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。

2.對象的創(chuàng)建過程

  1. 類加載檢查
  2. 為對象分配內(nèi)存
  3. 將分配到的內(nèi)存空間初始化置零(不包括對象頭)
  4. 設(shè)置對象頭
  5. 初始化成員變量,并執(zhí)行構(gòu)造函數(shù)

3.OOM異常(內(nèi)存溢出)

  • Java堆溢出
  • 虛擬機棧和本地方法棧溢出
  • 方法區(qū)和運行時常量池溢出
  • 本機直接內(nèi)存溢出
3.1 JVM調(diào)優(yōu)
  1. 首先把OOM文件dump下來
  2. 使用jprofiler工具加載dump文件,查看是否有大對象占用很大內(nèi)存,此外還可以定位到哪行代碼出現(xiàn)問題
  3. 嘗試調(diào)大堆內(nèi)存

內(nèi)存泄漏:程序申請內(nèi)存后,無法釋放已申請的內(nèi)存空間,一次內(nèi)存泄漏危害可以忽略,但內(nèi)存泄漏堆積后果很嚴(yán)重,無論內(nèi)存多大都會被占光。

二、垃圾收集

1.判斷對象是否已死

  • 引用計數(shù)法:為對象添加計數(shù)器,引用為0時可被回收。缺點:當(dāng)存在循環(huán)引用時,所占用的內(nèi)存永遠(yuǎn)無法被回收。
  • 可達(dá)性分析算法:以GC Roots為起點搜索,可達(dá)對象都是存貨的,不可達(dá)對象都被回收。

可作為GC Roots對象的:

  • 虛擬機棧中引用的對象
  • 方法區(qū)中類靜態(tài)屬性引用的對象
  • 方法區(qū)中常量引用的對象
  • 本地方法棧中Native方法引用的對象

2.引用

  • 強引用:
    被強引用關(guān)聯(lián)的對象不會被回收。使用 new 一個新對象的方式來創(chuàng)建強引用。
Object obj = new Object();
  • 軟引用:只有在內(nèi)存不夠的情況下才回收。使用 SoftReference 類來創(chuàng)建軟引用。

假設(shè)有一個應(yīng)用需要大量讀取本地的圖片:每次讀取圖片都從磁盤讀取會嚴(yán)重影響性能,如果一次性全部加載到內(nèi)存中又可能造成溢出,此時用軟引用就可以解決這個問題。

  • 弱引用:被弱引用關(guān)聯(lián)的對象一定會被回收。使用 WeakReference 類來創(chuàng)建弱引用。
  • 虛引用(幽靈引用/幻影引用):為一個對象設(shè)置虛引用的唯一目的是能在這個對象被回收時收到一個系統(tǒng)通知。一個對象是否有虛引用的存在,不會對其生存時間造成影響,也無法通過虛引用得到一個對象。使用 PhantomReference 來創(chuàng)建虛引用。

3.finalize()

在可達(dá)性分析中不可達(dá)的對象,也并不是一定會被回收的,真正宣告它死亡,至少需要兩次標(biāo)記過程??蛇_(dá)性分析中的不可達(dá)對象會被進(jìn)行第一次標(biāo)記,并根據(jù)此對象是否有必要執(zhí)行finalize()方法進(jìn)行一次篩選。如果沒有覆蓋finalize()方法或已被執(zhí)行過,則都被視為沒有必要執(zhí)行。
如果有必要執(zhí)行finalize()方法,會被放入F-Queue中,有可能會通過finalize()方法實現(xiàn)自救。

4.回收方法區(qū)

常量池的回收和類的卸載

5.垃圾收集算法

  • 標(biāo)記-清除:會產(chǎn)生很多內(nèi)存碎片,后續(xù)會因為沒有連續(xù)內(nèi)存空間分配給大對象而提前進(jìn)行垃圾收集。
  • 復(fù)制算法:不會產(chǎn)生內(nèi)存碎片,但只用到一半的內(nèi)存。
  • 標(biāo)記-整理算法:沒有內(nèi)存碎片,效率偏低。

6.垃圾收集器

  • Serial 收集器(新生代,復(fù)制算法;老年代,標(biāo)記整理算法):
    Serial 翻譯為串行,也就是說它以串行的方式執(zhí)行。它是單線程的收集器,只會使用一個線程進(jìn)行垃圾收集工作。 會暫停所有的用戶線程,不適合服務(wù)器環(huán)境。
  • ParNew收集器(新生代,復(fù)制算法;老年代,標(biāo)記整理算法):
    Serial收集器的多線程版本。
  • Parallel Scavenge收集器:與 ParNew 一樣是多線程收集器。其它收集器目標(biāo)是盡可能縮短垃圾收集時用戶線程的停頓時間,而它的目標(biāo)是達(dá)到一個可控制的吞吐量,因此它被稱為“吞吐量優(yōu)先”收集器。這里的吞吐量指 CPU 用于運行用戶程序的時間占總時間的比值。在注重吞吐量以及 CPU 資源敏感的場合,都可以優(yōu)先考慮 Parallel Scavenge 加 Parallel Old 收集器
  • CMS 收集器(用于老年代,標(biāo)記-清除算法):
    是一種以獲取最短停頓回收時間為目標(biāo)的收集器。
    初始標(biāo)記:僅僅只是標(biāo)記一下 GC Roots 能直接關(guān)聯(lián)到的對象,速度很快,需要停頓。
    并發(fā)標(biāo)記:進(jìn)行 GC Roots Tracing 的過程,它在整個回收過程中耗時最長,不需要停頓。
    重新標(biāo)記:為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄,需要停頓。
    并發(fā)清除:不需要停頓。
  • G1收集器(整體采用標(biāo)記整理,局部采用復(fù)制):G1(Garbage-First),它是一款面向服務(wù)端應(yīng)用的垃圾收集器,在多 CPU 和大內(nèi)存的場景下有很好的性能。堆被分為新生代和老年代,其它收集器進(jìn)行收集的范圍都是整個新生代或者老年代,而 G1 可以直接對新生代和老年代一起回收。解決了CMS內(nèi)存碎片的問題,同時使停頓時間更短,甚至用戶可以自己指定停頓時間。

7.內(nèi)存分配與回收策略

  1. 對象優(yōu)先在Eden分配
  2. 大對象(需要大量連續(xù)內(nèi)存空間的對象 )直接進(jìn)入老年代
  3. 長期存活(默認(rèn)為15歲,每熬過一次minor GC 算一次)的對象將進(jìn)入老年代
  4. 如果Survivor空間中相同年齡所有對象大小總和超過Survivor空間的一半,則年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代。

三、類加載機制

1.類的生命周期

加載-驗證-準(zhǔn)備-解析-初始化-使用-卸載

  • 加載:
  1. 通過一個類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流
  2. 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)
  3. 在內(nèi)存中生成一個代表這個類的Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口
  • 驗證:確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機的要求,并且不會危害虛擬機自身的安全
  • 準(zhǔn)備:類變量是被 static 修飾的變量,準(zhǔn)備階段為類變量分配內(nèi)存并設(shè)置初始值,使用的是方法區(qū)的內(nèi)存。實例變量不會在這階段分配內(nèi)存,它會在對象實例化時隨著對象一起被分配在堆中。
  • 解析:將常量池的符號引用替換為直接引用的過程,解析某些時候可以在初始化后
  • 初始化:
    以下5種情況需要立即對類進(jìn)行初始化:
    1.遇到new、getstatic、putstatic、invokestatic這4條字節(jié)碼指令時。
    2.使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用時
    3.當(dāng)初始化一個類時,發(fā)現(xiàn)其父類還沒有初始化
    4.虛擬機啟動時,會初始化主類(main)
    5.如果一個java.lang.invoke.MethodHandle時力最后的解析結(jié)果為REF_getStatic、REF_putStatic、REF_invokeStaitc方法句柄時。

2.類加載器

  • 啟動類加載器:加載存放在<JAVA_HOME>\lib中的
  • 擴展類加載器:加載<JAVA_HOME>\lib\ext中的
  • 應(yīng)用程序類加載器:加載用戶類路徑上所指定的類庫

3.雙親委派模型

  • 工作過程:一個類加載器首先將類加載請求轉(zhuǎn)發(fā)到父類加載器,只有當(dāng)父類加載器無法完成時才嘗試自己加載。
  • 好處:保證Java程序的穩(wěn)定運作。使得 Java 類隨著它的類加載器一起具有一種帶有優(yōu)先級的層次關(guān)系,從而使得基礎(chǔ)類得到統(tǒng)一。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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