面試 JVM 八股文五問五答第一期
作者:程序員小白條,個人博客
相信看了本文后,對你的面試是有一定幫助的!
?點贊?收藏?不迷路!?
1.JVM內存布局
Heap (堆區)
堆是 OOM 故障最主要的發生區域。它是內存區域中最大的一塊區域,被所有線程共享,存儲著幾乎所有的實例對象、數組。
Java 堆是垃圾收集器管理的主要區域,因此很多時候也被稱做“GC 堆”。從內存回收的角度來看,由于現在收集器基本都采用分代收集算法,所以 Java 堆中還可以細分為:新生代和老年代。再細致一點的有 Eden 空間、From Survivor 空間、To Survivor 空間等。從內存分配的角度來看,線程共享的 Java 堆中可能劃分出多個線程私有的分配緩沖區(Thread Local Allocation Buffer,TLAB)。不過無論如何劃分,都與存放內容無關,無論哪個區域,存儲的都仍然是對象實例,進一步劃分的目的是為了更好地回收內存,或者更快地分配內存。
堆區的調整:通過設置如下參數,可以設定堆區的初始值和最大值,比如 -Xms256M -Xmx 1024M,其中 -X 這個字母代表它是 JVM 運行時參數,ms 是 memory start 的簡稱,中文意思就是內存初始值,mx 是 memory max 的簡稱,意思就是最大內存。
在通常情況下,服務器在運行過程中,堆空間不斷地擴容與回縮,會形成不必要的系統壓力所以在線上生產環境中 JVM 的 Xms 和 Xmx 會設置成同樣大小,避免在 GC 后調整堆大小時帶來的額外壓力。
堆的默認空間分配:查看當前 JDK 版本所有默認的 JVM 參數:=======》java -XX:+PrintFlagsFinal -version
Java 虛擬機棧:
對于每一個線程,JVM 都會在線程被創建的時候,創建一個單獨的棧。也就是說虛擬機棧的生命周期和線程是一致,并且是線程私有的。
棧幀(Stack Frame)是用于支持虛擬機進行方法調用和方法執行的數據結構。棧幀存儲了方法的局部變量表(局部變量表:定義為一個數字數組,用于方法參數和方法內部的局部變量,包括三種數據類型:8種基本數據類型+引用數據類型地址+returnAddress類型(指向一條字節碼指令的地址) )、操作數棧(操作數棧主要用于保存運算過程中的的中間結果,同時作為計算過程中變量的臨時的存儲空間.)、動態鏈接(動態鏈接就是講指令中的符號引用轉化為真實的方法地址(直接引用)和方法返回地址等信息。每一個方法從調用至執行完成的過程,都對應著一個棧幀在虛擬機棧里從入棧到出棧的過程。
本地方法棧:
本地方法棧(Native Method Stack)與虛擬機棧所發揮的作用是非常相似的,它們之間的區別不過是虛擬機棧為虛擬機執行 Java 方法(也就是字節碼)服務,而本地方法棧則為虛擬機使用到的 Native 方法服務。在虛擬機規范中對本地方法棧中方法使用的語言、使用方式與數據結構并沒有強制規定,因此具體的虛擬機可以自由實現它。甚至有的虛擬機(譬如 Sun HotSpot 虛擬機)直接就把本地方法棧和虛擬機棧合二為一。與虛擬機棧一樣,本地方法棧區域也會拋出 StackOverflowError 和 OutOfMemoryError 異常。
拓展:
- 靜態鏈接:當一個字節碼文件被裝載進JVM內部時,如果被調用的目標方法在編譯器可知,且運行時期不變,這種情況下將調用方法的符號引用轉換為直接引用的過程稱之為靜態鏈接.對應早期綁定(早期綁定就是指被調用的目標方法如果在編譯期可知,且運行時期不變,即可將這個方法與所屬的類型進行綁定,這樣以來,由于明確了被調用的方法是哪一個,因此可以使用靜態鏈接的方式將符號引用轉換為直接引用).
- 動態鏈接:如果被調用的方法在編譯期無法被確定下來,只能夠在程序運行期將調用方法的符號引用轉換為直接引用,由于這種引用轉換過程具備動態性,因此被稱之為動態鏈接.對應晚期綁定(如果被調用方法在編譯期無法被確定下來,只能在程序運行期根據實際的類型綁定相應的方法,這種綁定方式被稱為晚期綁定).
程序計數器:
程序計數器(Program Counter Register)是一塊較小的內存空間。是線程私有的。它可以看作是當前線程所執行的字節碼的行號指示器
直接內存:
直接內存(Direct Memory)并不是虛擬機運行時數據區的一部分,也不是 Java 虛擬機規范中定義的內存區域。但是這部分內存也被頻繁地使用,而且也可能導致 OutOfMemoryError 異常出現
Code Cache:
JVM 代碼緩存是 JVM 將其字節碼存儲為本機代碼的區域 。我們將可執行本機代碼的每個塊稱為 nmethod。該 nmethod 可能是一個完整的或內聯 Java 方法。
2.Java如何標記垃圾
Java中的垃圾回收是通過標記-清除算法實現的。當一個對象不再被任何引用指向時,它就可以被垃圾回收器回收。
在Java中,垃圾回收器通過可達性分析算法來標記垃圾對象。可達性分析算法是從一組被稱為“GC Roots”的根對象開始遍歷內存中的所有對象,任何能夠被遍歷到的對象都被認為是“存活”的對象,而未被遍歷到的對象則被認為是“垃圾”對象。
Java中的GC Roots包括以下幾種類型的對象:
- 虛擬機棧中引用的對象
- 方法區中靜態屬性引用的對象
- 方法區中常量引用的對象
- 本地方法棧中JNI(Java Native Interface)引用的對象
垃圾回收器會從GC Roots開始遍歷內存中的所有對象,標記出所有被引用的對象,然后將沒有被標記的對象視為垃圾對象進行回收。這個過程中,需要注意的是,垃圾回收器不能回收互相引用的對象,即使這些對象已經沒有任何被引用的路徑,因為它們之間仍然存在一條循環引用的路徑,所以它們仍然被視為“存活”的對象。
需要注意的是,Java虛擬機的垃圾回收機制是自動的,程序員無法直接干預。但是,可以通過一些手段來優化垃圾回收的效率,比如盡量避免創建大量的臨時對象,避免使用過多的finalize()方法等。
3.GC回收的是哪里?
因為虛擬機棧、本地方法棧、程序計數器是線程私有的,隨著線程的消亡而消亡,方法結束或者線程結束時,內存自然就跟隨著回收了。 GC回收的區域是堆和方法區,為什么回收這兩個區域那,因為他們是線程共享的,即java程序中所有的線程都可以訪問。
4.常見的垃圾回收算法有哪些?
**分代收集算法:**根據內存對象的存活周期不同,將內存劃分成幾塊,java虛擬機一般將內存分成新生代和老生代,在新生代中,有大量對象死去和少量對象存活,所以采用復制算法,只需要付出少量存活對象的復制成本就可以完成收集;老年代中因為對象的存活率極高,沒有額外的空間對他進行分配擔保,所以采用標記清理或者標記整理算法進行回收;
**標記清除法:**第一步:利用可達性去遍歷內存,把存活對象和垃圾對象進行標記;
第二步:在遍歷一遍,將所有標記的對象回收掉;
特點:效率不行,標記和清除的效率都不高;標記和清除后會產生大量的不連續的空間分片,可能會導致之后程序運行的時候需分配大對象而找不到連續分片而不得不觸發一次GC(垃圾回收);
**標記整理法:**第一步:利用可達性去遍歷內存,把存活對象和垃圾對象進行標記;第二步:將所有的存活的對象向一段移動,將端邊界以外的對象都回收掉;
特點:適用于存活對象多,垃圾少的情況;需要整理的過程,無空間碎片產生;
**復制算法:**將內存按照容量大小分為大小相等的兩塊,每次只使用一塊,當一塊使用完了,就將還存活的對象移到另一塊上,然后在把使用過的內存空間移除;特點:不會產生空間碎片;內存使用率極低;
5.哪些情況下會出現FULLGC
- 調用 System.gc()
只是建議虛擬機執行 Full GC,但是虛擬機不一定真正去執行。不建議使用這種方式,而是讓虛擬機管理內存。
- 未指定老年代和新生代大小,堆伸縮時會產生fullgc,所以一定要配置-Xmx、-Xms
- 老年代空間不足
老年代空間不足的常見場景比如大對象、大數組直接進入老年代、長期存活的對象進入老年代等。
為了避免以上原因引起的 Full GC,應當盡量不要創建過大的對象以及數組。
除此之外,可以通過 -Xmn 虛擬機參數調大新生代的大小,讓對象盡量在新生代被回收掉,不進入老年代。
還可以通過 -XX:MaxTenuringThreshold 調大對象進入老年代的年齡,讓對象在新生代多存活一段時間。
在執行Full GC后空間仍然不足,則拋出錯誤:java.lang.OutOfMemoryError: Java heap space
- JDK 1.7 及以前的(永久代)空間滿
在 JDK 1.7 及以前,HotSpot 虛擬機中的方法區是用永久代實現的,永久代中存放的為一些 Class 的信息、常量、靜態變量等數據。
當系統中要加載的類、反射的類和調用的方法較多時,永久代可能會被占滿,在未配置為采用 CMS GC 的情況下也
會執行 Full GC。
如果經過 Full GC 仍然回收不了,那么虛擬機會拋出java.lang.OutOfMemoryError PermGen space
為避免以上原因引起的 Full GC,可采用的方法為增大Perm Gen或轉為使用 CMS GC。
- 空間分配擔保失敗
空間擔保,下面兩種情況是空間擔保失敗:
1、每次晉升的對象的平均大小 > 老年代剩余空間
2、Minor GC后存活的對象超過了老年代剩余空間
注意GC日志中是否有promotion failed和concurrent mode failure兩種狀況,當出現這兩種狀況的時候就有可能會觸發Full GC。
promotion failed 是在進行 Minor GC時候,survivor space空間放不下只能晉升老年代,而此時老年代也空間不足時發生的。
concurrent mode failure 是在進行CMS GC過程,此時有對象要放入老年代而空間不足造成的,這種情況下會退化使用Serial Old收集器變成單線程的,此時是相當的慢的。