【劍指offer】JVM經典面試題
JVM是Java Virtual Machine(Java虛擬機器)的縮寫,JVM是一種用於計算裝置的規範,它是一個虛構出來的計算機,是通過在實際的計算機上模擬模擬各種計算機功能來實現的。
Java語言的一個非常重要的特點就是與平臺的無關性。而使用Java虛擬機器是實現這一特點的關鍵。一般的高階語言如果要在不同的平臺上執行,至少需要編譯成不同的目的碼。而引入Java語言虛擬機器後,Java語言在不同平臺上執行時不需要重新編譯。Java語言使用Java虛擬機器遮蔽了與具體平臺相關的資訊,使得Java語言編譯程式只需生成在Java虛擬機器上執行的目的碼(位元組碼),就可以在多種平臺上不加修改地執行。Java虛擬機器在執行位元組碼時,把位元組碼解釋成具體平臺上的機器指令執行。這就是Java的能夠“一次編譯,到處執行”的原因
問題清單
1. 你知道哪些或者你們線上使⽤什麼GC策略?它有什麼優勢,適⽤於什麼場景?
堆記憶體劃分為 Eden、Survivor 和 Tenured/Old 空間,如下圖所示:
從年輕代空間(包括 Eden 和 Survivor 區域)回收記憶體被稱為 Minor GC,對老年代GC稱為Major GC,而Full GC是對整個堆來說的,在最近幾個版本的JDK裡預設包括了對永生帶即方法區的回收(JDK8中無永生帶了),出現Full GC的時候經常伴隨至少一次的Minor GC,但非絕對的。Major GC的速度一般會比Minor GC慢10倍以上
下邊看看有那種情況觸發JVM進行Full GC及應對策略。
【System.gc()方法的呼叫】
此方法的呼叫是建議JVM進行Full GC,雖然只是建議而非一定,但很多情況下它會觸發 Full GC,從而增加Full GC的頻率,也即增加了間歇性停頓的次數。強烈影響系建議能不使用此方法就別使用,讓虛擬機器自己去管理它的記憶體,可通過通過
-XX:+ DisableExplicitGC來禁止RMI呼叫System.gc。
【老年代代空間不足】
老年代空間只有在新生代物件轉入及建立為大物件、大陣列時才會出現不足的現象,當執行Full GC後空間仍然不足,則丟擲如下錯誤:
java.lang.OutOfMemoryError: Java heap space
為避免以上兩種狀況引起的Full GC,調優時應儘量做到讓物件在Minor GC階段被回收、讓物件在新生代多存活一段時間及不要建立過大的物件及陣列。
【永生區空間不足】
JVM規範中執行時資料區域中的方法區,在HotSpot虛擬機器中又被習慣稱為永生代或者永生區,Permanet Generation中存放的為一些class的資訊、常量、靜態變數等資料,當系統中要載入的類、反射的類和呼叫的方法較多時,Permanet Generation可能會被佔滿,在未配置為採用CMS GC的情況下也會執行Full GC。如果經過Full GC仍然回收不了,那麼JVM會丟擲如下錯誤資訊:
java.lang.OutOfMemoryError: PermGen space
為避免Perm Gen佔滿造成Full GC現象,可採用的方法為增大Perm Gen空間或轉為使用CMS GC。
【CMS GC時出現promotion failed和concurrent mode failure】
對於採用CMS進行老年代GC的程式而言,尤其要注意GC日誌中是否有promotion failed和concurrent mode failure兩種狀況,當這兩種狀況出現時可能會觸發Full GC。promotion failed是在進行Minor GC時,survivor space放不下、物件只能放入老年代,而此時老年代也放不下造成的;concurrent mode failure是在執行CMS GC的過程中同時有物件要放入老年代,而此時老年代空間不足造成的(有時候“空間不足”是CMS GC時當前的浮動垃圾過多導致暫時性的空間不足觸發Full GC)。對措施為:增大survivor space、老年代空間或調低觸發併發GC的比率,但在JDK 5.0+、6.0+的版本中有可能會由於JDK的bug29導致CMS在remark完畢後很久才觸發sweeping動作。對於這種狀況,可通過設定-XX: CMSMaxAbortablePrecleanTime=5(單位為ms)來避免。
【統計得到的Minor GC晉升到舊生代的平均大小大於老年代的剩餘空間】
這是一個較為複雜的觸發情況,Hotspot為了避免由於新生代物件晉升到舊生代導致舊生代空間不足的現象,在進行Minor GC時,做了一個判斷,如果之前統計所得到的Minor GC晉升到舊生代的平均大小大於舊生代的剩餘空間,那麼就直接觸發Full GC。
例如程式第一次觸發Minor GC後,有6MB的物件晉升到舊生代,那麼當下一次Minor GC發生時,首先檢查舊生代的剩餘空間是否大於6MB,如果小於6MB,則執行Full GC。
當新生代採用PS GC時,方式稍有不同,PS GC是在Minor GC後也會檢查,例如上面的例子中第一次Minor GC後,PS GC會檢查此時舊生代的剩餘空間是否大於6MB,如小於,則觸發對舊生代的回收。
除了以上4種狀況外,對於使用RMI來進行RPC或管理的Sun JDK應用而言,預設情況下會一小時執行一次Full GC。可通過在啟動時通過
java -Dsun.rmi.dgc.client.gcInterval=3600000
來設定Full GC執行的間隔時間或通過-XX:+ DisableExplicitGC來禁止RMI呼叫System.gc。
【堆中分配很大的物件】
所謂大物件,是指需要大量連續記憶體空間的java物件,例如很長的陣列,此種物件會直接進入老年代,而老年代雖然有很大的剩餘空間,但是無法找到足夠大的連續空間來分配給當前物件,此種情況就會觸發JVM進行Full GC。為了解決這個問題,CMS垃圾收集器提供了一個可配置的引數,
即-XX:+UseCMSCompactAtFullCollection
開關引數,用於在“享受”完Full GC服務之後額外免費贈送一個碎片整理的過程,記憶體整理的過程無法併發的,空間碎片問題沒有了,但提頓時間不得不變長了,JVM設計者們還提供了另外一個引數 -XX:CMSFullGCsBeforeCompaction,這個引數用於設定在執行多少次不壓縮的Full GC後,跟著來一次帶壓縮的。
2.Java類載入器包括⼏種?它們之間的⽗⼦關係是怎麼樣的?雙親委派機制是什麼意思?有什麼好處?
啟動Bootstrap類載入、擴充套件Extension類載入、系統System類載入。
父子關係如下:
-
啟動類載入器 ,由C++ 實現,沒有父類;
-
擴充套件類載入器,由Java語言實現,父類載入器為null;
-
系統類載入器,由Java語言實現,父類載入器為擴充套件類載入器;
-
自定義類載入器,父類載入器肯定為AppClassLoader。
雙親委派機制:類載入器收到類載入請求,自己不載入,向上委託給父類載入,父類載入不了,再自己載入。
優勢避免Java核心API篡改
3.如何⾃定義⼀個類載入器?你使⽤過哪些或者你在什麼場景下需要⼀個⾃定義的類載入器嗎?
自定義類載入的意義:
-
載入特定路徑的class檔案
-
載入一個加密的網路class檔案
-
熱部署載入class檔案
4.堆記憶體設定的引數是什麼?
-
-Xmx 設定堆的最大空間大小
-
-Xms 設定堆的最小空間大小
5.Perm Space中儲存什麼資料?會引起OutOfMemory嗎?
載入class檔案。
會引起,出現異常可以設定 -XX:PermSize 的大小。JDK 1.8後,字串常量不存放在永久帶,而是在堆記憶體中,JDK8以後沒有永久代概念,而是用元空間替代,元空間不存在虛擬機器中,二是使用本地記憶體。
補充:Java8記憶體模型—永久代(PermGen)和元空間(Metaspace)
根據 JVM 規範,JVM 記憶體共分為虛擬機器棧、堆、方法區、程式計數器、本地方法棧五個部分。
絕大部分 Java 程式設計師應該都見過
"java.lang.OutOfMemoryError: PermGen space "這個異常。這裡的 “PermGen space”其實指的就是方法區。不過方法區和“PermGen space”又有著本質的區別。前者是 JVM 的規範,而後者則是 JVM 規範的一種實現,並且只有 HotSpot 才有 “PermGen space”,而對於其他型別的虛擬機器,如 JRockit(Oracle)、J9(IBM) 並沒有“PermGen space”。由於方法區主要儲存類的相關資訊,所以對於動態生成類的情況比較容易出現永久代的記憶體溢位。最典型的場景就是,在 jsp 頁面比較多的情況,容易出現永久代記憶體溢位。
移除永久代的工作從JDK1.7就開始了。JDK1.7中,儲存在永久代的部分資料就已經轉移到了Java Heap或者是 Native Heap。但永久代仍存在於JDK1.7中,並沒完全移除,譬如符號引用(Symbols)轉移到了native heap;字面量(interned strings)轉移到了java heap;類的靜態變數(class statics)轉移到了java heap。
6.做GC時,⼀個物件在記憶體各個Space中被移動的順序是什麼?
標記清除法,複製演算法,標記整理、分代演算法。
新生代一般採用複製演算法 GC,老年代使用標記整理演算法。
垃圾收集器:序列新生代收集器、序列老生代收集器、並行新生代收集器、並行老年代收集器。
CMS(Current Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器,它是一種併發收集器,採用的是Mark-Sweep演算法。
如果確定某個物件是“垃圾”?既然垃圾收集器的任務是回收垃圾物件所佔的空間供新的物件使用,那麼垃圾收集器如何確定某個物件是“垃圾”?—即通過什麼方法判斷一個物件可以被回收了。
在java中是通過引用來和物件進行關聯的,也就是說如果要操作物件,必須通過引用來進行。那麼很顯然一個簡單的辦法就是通過引用計數來判斷一個物件是否可以被回收。不失一般性,如果一個物件沒有任何引用與之關聯,則說明該物件基本不太可能在其他地方被使用到,那麼這個物件就成為可被回收的物件了。這種方式成為引用計數法。
7.你有沒有遇到過OutOfMemory問題?你是怎麼來處理這個問題的?處理 過程中有哪些收穫?
permgen space、heap space 錯誤。
常見的原因
-
記憶體載入的資料量太大:一次性從資料庫取太多資料;
-
集合類中有對物件的引用,使用後未清空,GC不能進行回收;
-
程式碼中存在迴圈產生過多的重複物件;
-
啟動引數堆記憶體值小。
8.JDK 1.8之後Perm Space有哪些變動? MetaSpace⼤⼩預設是⽆限的麼? 還是你們會通過什麼⽅式來指定⼤⼩?
JDK 1.8後用元空間替代了 Perm Space;字串常量存放到堆記憶體中。
MetaSpace大小預設沒有限制,一般根據系統記憶體的大小。JVM會動態改變此值。
-
-XX:MetaspaceSize:分配給類元資料空間(以位元組計)的初始大小(Oracle邏輯儲存上的初始高水位,the initial high-water-mark)。此值為估計值,MetaspaceSize的值設定的過大會延長垃圾回收時間。垃圾回收過後,引起下一次垃圾回收的類元資料空間的大小可能會變大。
-
-XX:MaxMetaspaceSize:分配給類元資料空間的最大值,超過此值就會觸發Full GC,此值預設沒有限制,但應取決於系統記憶體的大小。JVM會動態地改變此值。
9.jstack 是⼲什麼的? jstat 呢?如果線上程式週期性地出現卡頓,你懷疑可 能是 GC 導致的,你會怎麼來排查這個問題?執行緒⽇志⼀般你會看其中的什麼 部分?
jstack 用來查詢 Java 程序的堆疊資訊。
jvisualvm 監控記憶體洩露,跟蹤垃圾回收、執行時記憶體、cpu分析、執行緒分析。
10.StackOverflow異常有沒有遇到過?⼀般你猜測會在什麼情況下被觸發?如何指定⼀個執行緒的堆疊⼤⼩?⼀般你們寫多少?
棧記憶體溢位,一般由棧記憶體的區域性變數過爆了,導致記憶體溢位。出現在遞迴方法,引數個數過多,遞迴過深,遞迴沒有出口。
文章首發自公眾號【Ahab雜貨鋪】關注公眾號技術分享第一時間送達!