《面試八股文》之 JVM 20卷


微信公眾號:moon聊技術

關注選擇“ 星標 ”, 重磅干貨,第一 時間送達!

[如果你覺得文章對你有幫助,歡迎關注,在看,點贊,轉發]

大家好,我是 moon。

《面試八股文》之 JVM 20卷 它來了,整理大部分經常會問到的考點,整整 20 問,當然,moon 給出的答案也是相當豐富的,雖然只有 20 問,但是本文足足有 1W 多字,這也是 moon 想告訴大家的,就在面試的時候也需要學會拓展,不要面試官問什么你就只回答什么,象征性的擴展開來,要讓面試官能知道,你並不是個只會背八股文的人,知其然要知其所以然~

再說一下,moon 的群聊也開放了,二維碼貼在下方了,一起聊技術,侃生活吧

如果二維碼過期了可以在下方添加 moon 的微信,單獨拉你進去~



1.說說 JVM 內存區域

JVM 內存區域

這張圖就是一個 JVM 運行時數據圖,紫色區域代表是線程共享的區域,JAVA 程序在運行的過程中會把他管理的內存划分為若干個不同的數據區域,每一塊兒的數據區域所負責的功能都是不同的,他們也有不同的創建時間和銷毀時間

  • 1.程序計數器

    • 程序計數器是程序控制流的指示器,循環,跳轉,異常處理,線程的恢復等工作都需要依賴程序計數器去完成。程序計數器是線程私有的,它的生命周期是和線程保持一致的,我們知道,N 個核心數的 CPU 在同一時刻,最多有 N個線程同時運行,在我們真實的使用過程中可能會創建很多線程,JVM 的多線程其實是通過線程輪流切換,分配處理器執行時間來實現的。既然涉及的線程切換,所以每條線程必須有一個獨立的程序計數器。
  • 2.虛擬機棧

    • 虛擬機棧,其描述的就是線程內存模型,也可以稱作線程棧,也是每個線程私有的,生命周期與線程保持一致。在每個方法執行的時候,jvm 都會同步創建一個棧幀去存儲局部變量表,操作數棧,動態連接,方法出口等信息。一個方法的生命周期就貫徹了一個棧幀從入棧到出棧的全部過程。
  • 3.本地方法棧
    本地方法棧的概念很好理解,我們知道,java底層用了很多c的代碼去實現,而其調用c端的方法上都會有native,代表本地方法服務,而本地方法棧就是為其服務的。

  • 4.堆
    堆可以說是jvm中最大的一塊兒內存區域了,它是所有線程共享的,不管你是初學者還是資深開發,多少都會聽說過堆,畢竟幾乎所有的對象都會在堆中分配。

  • 5.方法區

    • 方法區也是所有線程共享的區域,它存儲了被 jvm 加載的類型信息、常量、靜態變量等數據。運行時常量池就是方法區的一部分,編譯期生成的各種字面量與符號引用就存儲在其中。
  • 6.直接內存

    • 這部分數據並不是 jvm 運行時數據區的一部分,nio 就會使用到直接內存,也可以說堆外內存,通常會配合虛引用一起去使用,就是為了資源釋放,會將堆外內存開辟空間的信息存儲到一個隊列中,然后GC會去清理這部分空間。堆外內存優勢在 IO 操作上,對於網絡 IO,使用 Socket 發送數據時,能夠節省堆內存到堆外內存的數據拷貝,所以性能更高。看過 Netty 源碼的同學應該了解,Netty 使用堆外內存池來實現零拷貝技術。對於磁盤 IO 時,也可以使用內存映射,來提升性能。另外,更重要的幾乎不用考慮堆內存煩人的 GC 問題。但是既然是內存。也會受到本機總內存的限制,

2.垃圾對象是怎么找到的?

  • 1.引用計數算法

就是給對象添加一個計數器

  • 每當有一個地方引用它的時候,計數器就加1
  • 每當有一個引用失效的時候,計數器就減1

當計數器的值為0的時候,那么該對象就是垃圾了」這種方案的原理很簡單,而且判定的效率也非常高,但是卻可能會有其他的額外情況需要考慮。

相互引用

比如兩個「對象循環引用」,a 對象引用了 b 對象,b 對象也引用了 a 對象,a、b 對象卻沒有再被其他對象所引用了,其實正常來說這兩個對象已經是垃圾了,因為沒有其他對象在使用了,但是計數器內的數值卻不是 0,所以引用計數算法就無法回收它們。這種算法是比較「直接的找到垃圾」,然后去回收,也被稱為"直接垃圾收集"。

  • 2.根可達算法

這也是「JVM 默認使用」的尋找垃圾算法它的原理就是定義了一系列的根,我們把它稱為 「"GC Roots"」 ,從 「"GC Roots"」 開始往下進行搜索,走過的路徑我們把它稱為 「"引用鏈"」 ,當一個對象到 「"GC Roots"」 之間沒有任何引用鏈相連時,那么這個對象就可以被當做垃圾回收了。

root search

如圖,「根可達算法」就可以「避免」計數器算法不好解決的「循環引用問題」,Object 6、Object 7、Object 8彼此之前有引用關系,但是沒有與「"GC Roots"」 相連,那么就會被當做垃圾所回收

3.GC Roots 有哪些?

在java中,有「固定的GC Roots 對象」和「不固定的臨時GC Roots對象:」

固定的GC Roots:

  • 1.在「虛擬機棧(棧幀的本地變量表)中所引用的對象」,譬如各個線程被調用的方法堆棧中使用到的參數、局部變量、臨時變量等。
  • 在方法區中「類靜態屬性引用的對象」,譬如 Java 類的引用靜態變量
  • 在方法區中「常量引用的對象」,譬如字符串常量池中的引用。
  • 在方法區棧中 「JNI (譬如 Native 方法)引用的對象」
  • Java 「虛擬機內部的引用」,如基本數據類型對應的 Class 對象,一些常駐的異常對象(空指針異常、OOM等),還有類加載器。
  • 所有「被 Synchronized 持有的對象」。
  • 反應 Java 虛擬機內部情況的 「JMXBean、JVMTI 中注冊的回調本地代碼緩存等」

臨時GC Roots:

  • 為什么會有臨時的 GC Roots ?」:目前的垃圾回收大部分都是「分代收集和局部回收」,如果只針對某一部分區域進行局部回收,那么就必須要考慮的「當前區域的對象有可能正被其他區域的對象所引用」,這時候就要將這部分關聯的對象也添加到 GC Roots 中去來確保根可達算法的准確性。這種算法是利用了「逆向思維」,找到使用的對象,剩下的就是垃圾,也被稱為"間接垃圾收集"。

4.java 有哪四種引用類型?

  • 1.強引用

"Object o = new Object()" 就是一種強引用關系,這也是我們在代碼中最常用的一種引用關系。無論任何情況下,只要強引用關系還存在,垃圾回收器就不會回收掉被引用的對象。

  • 2.軟引用

當內存空間不足時,就會回收軟引用對象。

// 軟引用  
SoftReference<String> softRef = new SoftReference<String>(str);  

軟引用用來描述那些有用但是沒必要的對象。

  • 3.弱引用

弱引用要比軟引用更弱一點,它「只能夠存活到下次垃圾回收之前」。也就是說,垃圾回收器開始工作,會回收掉所有只被弱引用關聯的對象。

WeakReference<String> weakRef = new WeakReference<String>(str);  

ThreadLocal 中就使用了弱引用來防止內存泄漏。

  • 4.虛引用

虛引用是最弱的一種引用關系,它的唯一作用是用來作為一種通知。如零拷貝(Zero Copy),開辟了堆外內存,虛引用在這里使用,會將這部分信息存儲到一個隊列中,以便於后續對堆外內存的回收管理。

5.說一說分代收集理論

大多數的垃圾回收器都遵循了分代收集的理論進行設計,它建立在兩個分代假說之上:

  • 「弱分代假說」:絕大多數對象都是朝生夕滅的。
  • 「強分代假說」:熬過越多次數垃圾回收過程的對象就越難消亡。

這兩種假說的設計原則都是相同的:垃圾收集器「應該將jvm划分出不同的區域」,把那些較難回收的對象放在一起(一般指老年代),這個區域的垃圾回收頻率就可以降低,減少垃圾回收的開銷。剩下的區域(一般指新生代)可以用較高的頻率去回收,並且只需要去關心那些存活的對象,也不用標記出需要回收的垃圾,這樣就能夠以較低的代價去完成垃圾回收。

  • 「跨代引用假說」:如果某個新生代的對象存在了跨代引用,但是老年代的對象是很難消亡的,那么隨着時間的推移,這個新生代對象也會慢慢晉升為老年代對象,那么這種跨代引用也就被消除了。

由於跨代引用是很少的,所以我們不應該為了少量的跨代引用去掃描整個老年代的數據,只需要在新生代對象建立一個「記憶集」來記錄引用信息。記憶集:「將老年代分為若干個小塊,每塊區域中有 N 個對象」,在對象引用信息發生變動的時候來維護記憶集數據的准確性,這樣每次發生了 「"Minor GC"」 的時候只需要將記憶集中的對象添加到 「"GC Roots"」 中就可以了。

6.垃圾收集算法有哪些?

總共有三種

  • 1.標記清除算法

這種算法的實現是很簡單的,有兩種方式

  • 1.標記出垃圾,然后清理掉
  • 2.標記出存貨的對象,回收其他空間

標記清除算法

這種算法有兩個缺點

  • 1.隨着對象越來越多,那么所需要消耗的時間就會越來越多

  • 2.標記清除后會導致碎片化,如果有大對象分配很有可能分配不下而出發另一次的垃圾收集動作

  • 2.標記復制算法

這種算法解決了第一種算法碎片化的問題。就是「開辟兩塊完全相同的區域」,對象只在其中一篇區域內分配,然后「標記」出那些「存活的對象,按順序整體移到另外一個空間」,如下圖,可以看到回收后的對象是排列有序的,這種操作只需要移動指針就可以完成,效率很高,「之后就回收移除前的空間」。

標記復制算法

這種算法的缺點也是很明顯的

  • 浪費過多的內存,使現有的「可用空間變為」原先的「一半

  • 3.標記整理算法

這種算法可以說是結合了前兩種算法,既有標記刪除,又有整理功能。

標記整理算法

這種算法就是通過標記清除算法找到存活的對象,然后將所有「存活的對象,向空間的一端移動」,然后回收掉其他的內存。

7.什么是 STW ?

Java 中「Stop-The-World機制簡稱 STW」 ,是在執行垃圾收集算法時,Java 應用程序的其他所有線程都被掛起(除了垃圾收集幫助器之外)。Java 中一種全局暫停現象,全局停頓,所有 Java 代碼停止,native 代碼可以執行,但不能與 JVM 交互。

8.為什么需要 STW?

在 java 應用程序中「引用關系」是不斷發生「變化」的,那么就會有會有很多種情況來導致「垃圾標識」出錯。想想一下如果 Object a  目前是個垃圾,GC 把它標記為垃圾,但是在清除前又有其他對象指向了 Object a,那么此刻 Object a 又不是垃圾了,那么如果沒有 STW 就要去無限維護這種關系來去采集正確的信息。再舉個例子,到了秋天,道路上灑滿了金色的落葉,環衛工人在打掃街道,卻永遠也無法打掃干凈,因為總會有不斷的落葉。

9.垃圾回收器是怎樣尋找 GC Roots 的?

我們在前面說明了根可達算法是通過 GC Roots 來找到存活的對象的,也定義了 GC Roots,那么垃圾回收器是怎樣尋找GC Roots 的呢?首先,「為了保證結果的准確性,GC Roots枚舉時是要在STW的情況下進行的」,但是由於 JAVA 應用越來越大,所以也不能逐個檢查每個對象是否為 GC Root,那將消耗大量的時間。一個很自然的想法是,能不能用空間換時間,在某個時候把棧上代表引用的位置全部記錄下來,這樣到真正 GC 的時候就可以直接讀取,而不用再一點一點的掃描了。事實上,大部分主流的虛擬機也正是這么做的,比如 HotSpot ,它使用一種叫做 「OopMap」 的數據結構來記錄這類信息。

10.OopMap 是做什么的?有什么好處?

我們知道,一個線程意味着一個棧,一個棧由多個棧幀組成,一個棧幀對應着一個方法,一個方法里面可能有多個安全點。gc 發生時,程序首先運行到最近的一個安全點停下來,然后更新自己的 OopMap ,記下棧上哪些位置代表着引用。枚舉根節點時,遞歸遍歷每個棧幀的 OopMap ,通過棧中記錄的被引用對象的內存地址,即可找到這些對象( GC Roots )。使用 OopMap 可以「避免全棧掃描」,加快枚舉根節點的速度。但這並不是它的全部用意。它的另外一個更根本的作用是,可以幫助 HotSpot 實現准確式 GC (即使用准確式內存管理,虛擬機可用知道內存中某個位置的數據具體是什么類型) 。

11.什么是安全點?

從線程角度看,安全點可以理解成是在「代碼執行過程中」的一些「特殊位置」,當線程執行到這些位置的時候,說明「虛擬機當前的狀態是安全」的。比如:「方法調用、循環跳轉、異常跳轉等這些地方才會產生安全點」。如果有需要,可以在這個位置暫停,比如發生GC時,需要暫停所有活動線程,但是線程在這個時刻,還沒有執行到一個安全點,所以該線程應該繼續執行,到達下一個安全點的時候暫停,等待 GC 結束。那么如何讓線程在垃圾回收的時候都跑到最近的安全點呢?這里有「兩種方式」:

  • 搶先式中斷
    • 搶先式中斷:就是在stw的時候,先讓所有線程「完全中斷」,如果中斷的地方不在安全點上,然后「再激活」,「直到運行到安全點的位置」再中斷。
  • 主動式中斷
    • 主動式中斷:在安全點的位置打一個標志位,每個線程執行都去輪詢這個標志位,如果為真,就在最近的安全點掛起。

12.安全區域是什么?解決了什么問題

剛剛說到了主動式中斷,但是如果有些線程處於sleep狀態怎么辦呢?

為了解決這種問題,又引入了安全區域的概念安全區域是指「在一段代碼片中,引用關系不會發生改變」,實際上就是一個安全點的拓展。當線程執行到安全區域時,首先標識自己已進入安全區域,那樣,當在這段時間里 JVM 要發起 GC 時,就不用管標識自己為“安全區域”狀態的線程了,該線程只能乖乖的等待根節點枚舉完或者整個GC過程完成之后才能繼續執行。

13.常見的垃圾回收器?

前面和大家聊了很多垃圾收集算法,所以在真正實踐的時候會有多種選擇,垃圾回收器就是真正的實踐者,接下來就和大家聊聊10種垃圾回收器

  • 1.Serial

Serial是一個「單線程」的垃圾回收器,「采用復制算法負責新生代」的垃圾回收工作,可以與 CMS 垃圾回收器一起搭配工作。

Serial

在 STW 的時候「只會有一條線程」去進行垃圾收集的工作,所以可想而知,它的效率會比較慢。但是他確是所有垃圾回收器里面消耗額外內存最小的,沒錯,就是因為簡單。

  • 2.ParNew

ParNew 是一個「多線程」的垃圾回收器,「采用復制算法負責新生代」的垃圾回收工作,可以與CMS垃圾回收器一起搭配工作。

ParNew

它其實就是 Serial 的多線程版本,主要區別就是在 STW 的時候可以用多個線程去清理垃圾。

  • 3.Pararllel Scavenge

Pararllel Scavenge 是一個「多線程」的垃圾回收器,「采用復制算法負責新生代」的垃圾回收工作,可以與 Serial Old , Parallel Old 垃圾回收器一起搭配工作。

Pararllel Scavenge

是與 ParNew 類似,都是用於年輕代回收的使用復制算法的並行收集器,與 ParNew 不同的是,Parallel Scavenge 的「目標是達到一個可控的吞吐量」。吞吐量=程序運行時間/(程序運行時間+GC時間)。如程序運行了99s,GC耗時1s,吞吐量=99/(99+1)=99%。Parallel Scavenge 提供了兩個參數用以精確控制吞吐量,分別是用以控制最大 GC 停頓時間的 -XX:MaxGCPauseMillis 及直接控制吞吐量的參數 -XX:GCTimeRatio.「停頓時間越短就越適合需要與用戶交互的程序」,良好的響應速度能提升用戶體驗,而高吞吐量則可以高效的利用 CPU 時間,盡快完成程序的運算任務,主要適合在后台運算而不需要太多交互的任務。

  • 4.Serial Old

Serial Old 是一個「單線程」的垃圾回收器,「采用標記整理算法負責老年代」的垃圾回收工作,有可能還會配合 「CMS」 一起工作。

其實它就是 Serial 的老年代版本,整體鏈路和 Serial 大相徑庭。

  • 5.Parallel Old

Parallel Old 是一個「多線程」的垃圾回收器,「采用標記整理算法負責新生代」的垃圾回收工作,可以與 Parallel Scavenge 垃圾回收器一起搭配工作。

Parallel Old 是 Pararllel Scavenge 的老年代版本,它的設計思路也是以吞吐量優先的,ps+po 是很常用的一種組合。

  • 6.CMS

CMS可以說是一款具有"跨時代"意義的垃圾回收器,支持了和用戶線程一起工作,做到了「一起並發回收垃圾」的"壯舉"。

CMS

  • 1.初始標記

  • 初始標記只是標記出來「和 GC Roots 直接關聯」的對象,整個速度是非常快的,為了保證標記的准確,這部分會在 「STW」 的狀態下運行。

  • 2.並發標記

  • 並發標記這個階段會直接根據第一步關聯的對象找到「所有的引用」關系,這一部分時刻用戶線程「並發運行」的,雖然耗時較長,但是不會有很大的影響。

  • 3.重新標記

  • 重新標記是為了解決第二步並發標記所導致的標錯情況,這里簡單舉個例子:並發標記時a沒有被任何對象引用,此時垃圾回收器將該對象標位垃圾,在之后的標記過程中,a又被其他對象引用了,這時候如果不進行重新標記就會發生「誤清除」。這部分內容也是在「STW」的情況下去標記的。

  • 4.並發清除

  • 這一步就是最后的清除階段了,將之前「真正確認為垃圾的對象回收」,這部分會和用戶線程一起並發執行。

CMS的「三個缺點」:

  • 1.影響用戶線程的執行效率

    • CMS默認啟動的回收線程數是(處理器核心數 + 3)/ 4 ,由於是和用戶線程一起並發清理,那么勢必會影響到用戶線程的執行速度,並且這個影響「隨着核心線程數的遞減而增加」。所以 JVM 提供了一種 "「增量式並發收集器」"的 CMS 變種,主要是用來減少垃圾回收線程獨占資源的時間,所以會感覺到回收時間變長,這樣的話「單位時間內處理垃圾的效率就會降低」,也是一種緩和的方案。
  • 2.會產生"浮動垃圾"

    • 之前說到 CMS 真正清理垃圾是和用戶線程一起進行的,在「清理」這部分垃圾的時候「用戶線程會產生新的垃圾」,這部分垃圾就叫做浮動垃圾,並且只能等着下一次的垃圾回收再清除。
  • 3.會產生碎片化的空間

    • CMS 是使用了標記刪除的算法去清理垃圾的,而這種算法的缺點就是會產生「碎片化」,后續可能會「導致大對象無法分配」從而觸發「和 Serial Old 一起配合使用」來處理碎片化的問題,當然這也處於 「STW」的情況下,所以當 java 應用非常龐大時,如果采用了 CMS 垃圾回收器,產生了碎片化,那么在 STW 來處理碎片化的時間會非常之久。
  • 7.G1

G1(Garbage First):顧名思義,「垃圾回收第一」,官方對它的評價是在垃圾回收器技術上具有「里程碑式」的成果。G1 回收的目標不再是整個新生代,不再是整個老年代,也不再是整個堆了。G1 可以「面向堆內存的任何空間來進行」回收,衡量的標准也不再是根據年代來區分,而是哪塊「空間的垃圾最多就回收哪」塊兒空間,這也符合 G1 垃圾回收器的名字,垃圾第一,這就是 G1 的 「Mixed GC」 模式。當然我的意思是「垃圾回收不根據年代來區分」,但是 G1 還是「根據年代來設計」的,我們先來看下 G1 對於堆空間的划分:

G1

G1 垃圾回收器把堆划分成一個個「大小相同的Region」,每個 Region 都會扮演一個角色,H、S、E、O。E代表伊甸區,S代表 Survivor 區,H代表的是 Humongous(G1用來分配「大對象的區域」,對於 Humongous 也分配不下的超大對象,會分配在連續的 N 個 Humongous 中),剩余的深藍色代表的是 Old 區,灰色的代表的是空閑的 region。在 HotSpot 的實現中,整個堆被划分成2048左右個 Region。每個 Region 的大小在1-32MB之間,具體多大取決於堆的大小。在並發標記垃圾時也會產生新的對象,G1 對於這部分對象的處理是這樣的:將 Region 「新增一塊並發回收過程中分配對象的空間」,並為此設計了兩個 TAMS(Top at Mark Start)指針,這塊區域專門用來在並發時分配新對象,有對象新增只需要將 TAMS 指針移動下就可以了,並且這些「新對象默認是標記為存活」,這樣就「不會干擾到標記過程」。

但是這種方法也會有個問題,有可能「垃圾回收的速度小於新對象分配的速度」,這樣會導致 "Full GC" 而產生長時間的 STW。在 G1 的設計理念里,「最小回收單元是 Region」,每次回收的空間大小都是Region的N倍,那么G1是「怎么選擇要回收哪塊兒區域」的呢?G1 會跟蹤各個 Region 區域內的垃圾價值,和回收空間大小回收時間有關,然后「維護一個優先級列表」,來收集那些價值最高的Reigon區域。

執行的步驟:

  • 初始標記

    • 標記出來 GC Roots 能「直接關聯」到的對象
    • 修改 TAMS 的值以便於並發回收時新對象分配
    • 是在 Minor GC 時期(「STW」)完成的
  • 並發標記

    • 根據剛剛關聯的對像掃描整個對象引用圖,和用戶線程「並發執行」
    • 記錄 SATB(原始快照) 在並發時有引用的值
  • 最終標記

    • 處於 「STW」,處理第二步遺留下來的少量 SATB(原始快照) 記錄
  • 篩選回收

    • 維護之前提到的優先級列表
    • 根據「優先級列表」,「用戶設置的最大暫停時間」來回收 Region
    • 將需要回收的 Region 內存活的對象「復制」到不需要回收的 Region區域內,然后回收需要回收的 Region
    • 這部分是處於 「STW」 下執行,並且是多線程的

14.說說三色標記

這里我們又提到了一個概念叫做 「SATB 原始快照」,關於SATB會延伸出有一個概念,「三色標記算法」,也就是垃圾回收器標記垃圾的時候使用的算法,這里我們簡單說下:將對象分為「三種顏色」:

  • 白色:沒被 GC 訪問過的對象(被 GC 標記完后還是白色代表是垃圾)
  • 黑絲:存活的對象
  • 灰色:被 GC 訪問過的對象,但是對象引用鏈上至少還有一個引用沒被掃描過

我們知道在 「並發標記」 的時候 「可能會」 出現 「誤標」 的情況,這里舉兩個例子:

  • 1.剛開始標記為 「垃圾」 的對象,但是在並發標記過程中 「變為了存活對象」
  • 2.剛開始標記為 「存活」 的對象,但是在並發標記過程中 「變為了垃圾對象」

第一種情況影響還不算很大,只是相當於垃圾沒有清理干凈,待下一次清理的時候再清理一下就好了。第二種情況就危險了,正在使 「用的對象的突然被清理掉」 了,后果會很嚴重。那么 「產生上述第二種情況的原因」 是什么呢?

  • 1.「新增」 一條或多條 「黑色到白色」 對象的新引用
  • 2.刪除 「了」 灰色 「對象」 到該白色對象 「的直接」 引用或間接引用。

當這兩種情況 「都滿足」 的時候就會出現這種問題了。所以為了解決這個問題,引入了 「增量更新」 (Incremental Update)和 「原始快照」 (SATB)的方案:

  • 增量更新破壞了第一個條件:「增加新引用時記錄」 該引用信息,在后續 STW 掃描中重新掃描(CMS的使用方案)。
  • 原始快照破壞了第二個條件:「刪除引用時記錄下來」,在后續 STW 掃描時將這些記錄過的灰色對象為根再掃描一次(G1的使用方案)。

15.什么情況下會發生棧內存溢出?

Java 棧內存溢出可能拋出兩種異常,兩種異常雖然都發生在棧內存,但是兩者導致內存溢出的根本原因是不一樣的:

  • 1.如果線程請求分配的棧容量超過 Java 虛擬機棧允許的最大容量的時候,Java 虛擬機將拋出一個 StackOverFlowError 異常。

  • 2.如果 Java 虛擬機棧可以動態拓展,並且擴展的動作已經嘗試過,但是目前無法申請到足夠的內存去完成拓展,或者在建立新線程的時候沒有足夠的內存去創建對應的虛擬機棧,那 Java 虛擬機將會拋出一個 OutOfMemoryError 異常。

16.如何排查 OOM 的問題?

  • 1.增加兩個參數 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof,當 OOM 發生時自動 dump 堆內存信息到指定目錄;
  • 2.同時 jstat 查看監控 JVM 的內存和 GC 情況,先觀察問題大概出在什么區域;
  • 3.使用工具載入到 dump 文件,分析大對象的占用情況。

17.說一說類加載機制是什么?加載的過程又是怎么樣的?

類加載機制:

  • Java 虛擬機把描述類的數據從 Class 文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被 Jvm 可以直接使用的類型,這個過程就可以成為虛擬機的類加載機制。

這是一張很經典的圖,標明了一個類的生命周期,而很多人一眼看過去就以為明白了類的生命周期,但是這只是其中一種情況。

真實情況是加載、驗證、准備、初始化、卸載這五個階段的順序是確定的,是依次有序的。但是解析階段有可能會在初始化之后才會進行,這是為了支持 Java 動態綁定的特性。

動態綁定:

  • 在運行時根據具體對象的類型進行綁定。提供了一些機制,可在運行期間判斷對象的類型,並分別調用適當的方法。也就是說,編譯器此時依然不知道對象的類型,但方法調用機制能自己去調查,找到正確的方法主體。

18.介紹一下雙親委派模型,它的好處是什么?

雙親委派模型

  • 簡而言之,就是說一個類加載器收到了類加載的請求,不會自己先加載,而是把它交給自己的父類去加載,層層迭代

用上圖來說明就是如果應用程序類加載器收到了一個類加載的請求,會先給擴展類加載器,然后再給啟動類加載器,如果啟動類加載器無法完成這個類加載的請求,再返回給擴展類加載器,如果擴展類加載器也無法完成,就返回給應用類加載器。

好處:

  • 說這個問題前我要先和大家說一個概念,Jvm 中類的唯一性是由類本身和加載這個類的類加載器決定的,簡單的說,如果有個a類,如果被兩個不同的類加載器加載,那么他們必不相等。你看到這里會不會想到所有類的父類都是 Object 是怎么實現的了嗎?是因為無論哪一個類加載器加載 Object 類,都會交給最頂層的啟動類加載器去加載,這樣就保證了 Object 類在 Jvm 中是唯一的

19.說一說對象的棧上分配吧?

如果所有對象都分配在堆中那么會給 GC 帶來許多不必要的壓力,比如有些對象的生命周期只是在當前線程中,為了減少臨時對象在堆內分配的數量,就可以在在棧上分配,隨着線程的消亡而消亡。當然棧上空間必須充足,否則也無法分配,在判斷是否能分配到棧上的另一條件就是要經過逃逸分析,

逃逸分析(Escape Analysis):

  • 簡單來講就是:Java Hotspot 虛擬機判斷這個新對象是否只會被當前線程引用,並且決定是否能夠在 Java 堆上分配內存。

20.說一說對象的內存布局是怎樣的?

對象內存布局

  • 1.對象頭:
    對象頭又分為 MarkWordClass Pointer 兩部分。
    • MarkWord:包含一系列的標記位,比如輕量級鎖的標記位,偏向鎖標記位,gc記錄信息等等。
    • ClassPointer:用來指向對象對應的 Class 對象(其對應的元數據對象)的內存地址。在 32 位系統占 4 字節,在 64 位系統中占 8 字節。
  • 2.Length:只在數組對象中存在,用來記錄數組的長度,占用 4 字節
  • 3.Instance data:
    對象實際數據,對象實際數據包括了對象的所有成員變量,其大小由各個成員變量的大小決定。(這里不包括靜態成員變量,因為其是在方法區維護的)
  • 4.Padding:Java 對象占用空間是 8 字節對齊的,即所有 Java 對象占用 bytes 數必須是 8 的倍數,是因為當我們從磁盤中取一個數據時,不會說我想取一個字節就是一個字節,都是按照一塊兒一塊兒來取的,這一塊大小是 8 個字節,所以為了完整,padding 的作用就是補充字節,保證對象是 8 字節的整數倍


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM