天天看點

jvm面試題

 heap 和stack 有什麼差別?

(1)申請方式

stack:由系統自動配置設定。例如,聲明在函數中一個局部變量 int b; 系統自動在棧中為 b 開辟空間

heap:需要程式員自己申請,并指明大小,在 c 中 malloc 函數,對于Java 需要手動 new Object()的形式開辟

(2)申請後系統的響應

stack:隻要棧的剩餘空間大于所申請空間,系統将為程式提供記憶體,否則将報異常提示棧溢出。

heap:首先應該知道作業系統有一個記錄空閑記憶體位址的連結清單,當系統收到程式的申請時,會周遊該連結清單,尋找第一個空間大于所申請空間的堆結點,然後将該結點從空閑結點連結清單中删除,并将該結點的空間配置設定給程式。另外,由于找到的堆結點的大小不一定正好等于申請的大小,系統會自動的将多餘的那部分重新放入空閑連結清單中。

(3)申請大小的限制

stack:棧是向低位址擴充的資料結構,是一塊連續的記憶體的區域。這句話的意思是棧頂的位址和棧的最大容量是系統預先規定好的,在 WINDOWS 下,棧的大小是 2M(預設值也取決于虛拟記憶體的大小),如果申請的空間超過棧的剩餘空間時,将提示 overflow。是以,能從棧獲得的空間較小。

heap:堆是向高位址擴充的資料結構,是不連續的記憶體區域。這是由于系統是用連結清單來存儲的空閑記憶體位址的, 自然是不連續的,而連結清單的周遊方向是由低位址向高位址。堆的大小受限于計算機系統中有效的虛拟記憶體。由此可見, 堆獲得的空間比較靈活,也比較大。

(4)申請效率的比較

stack:由系統自動配置設定,速度較快。但程式員是無法控制的。

heap:由 new 配置設定的記憶體,一般速度比較慢,而且容易産生記憶體碎片,不過用起來最友善。

(5)heap和stack中的存儲内容

stack:在函數調用時,第一個進棧的是主函數中後的下一條指令(函數調用語句的下一條可執行語句)的位址, 然後是函數的各個參數,在大多數的 C 編譯器中,參數是由右往左入棧的,然後是函數中的局部變量。注意靜态變量是不入棧的。

當本次函數調用結束後,局部變量先出棧,然後是參數,最後棧頂指針指向最開始存的位址,也就是主函數中的下一條指令,程式由該點繼續運作。

heap:一般是在堆的頭部用一個位元組存放堆的大小。堆中的具體内容有程式員安排。

. 什麼情況下會發生棧記憶體溢出?

1、棧是線程私有的,棧的生命周期和線程一樣,每個方法在執行的時候就會建立一個棧幀,它包含局部變量表、操作數棧、動态連結、方法出口等資訊,局部變量表又包括基本資料類型和對象的引用;2、當線程請求的棧深度超過了虛拟機允許的最大深度時,會抛出StackOverFlowError異常,方法遞歸調用肯可能會出現該問題;3、調整參數-xss去調整jvm棧的大小

. 談談對 OOM 的認識?如何排查 OOM 的問題?

除了程式計數器,其他記憶體區域都有 OOM 的風險。

棧一般經常會發生 StackOverflowError,比如 32 位的 windows 系統單程序限制 2G 記憶體,無限建立線程就會發生棧的 OOM

Java 8 常量池移到堆中,溢出會出 java.lang.OutOfMemoryError: Java heap space,設定最大元空間大小參數無效;

堆記憶體溢出,報錯同上,這種比較好了解,GC 之後無法在堆中申請記憶體建立對象就會報錯;

方法區 OOM,經常會遇到的是動态生成大量的類、jsp 等;

直接記憶體 OOM,涉及到 -XX:MaxDirectMemorySize 參數和 Unsafe 對象對記憶體的申請。

排查 OOM 的方法:

增加兩個參數 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof,當 OOM 發生時自動 dump 堆記憶體資訊到指定目錄;

同時 jstat 檢視監控 JVM 的記憶體和 GC 情況,先觀察問題大概出在什麼區域;

使用 MAT 工具載入到 dump 檔案,分析大對象的占用情況,比如 HashMap 做緩存未清理,時間長了就會記憶體溢出,可以把改為弱引用 。

. 談談 JVM 中的常量池?

JVM常量池主要分為 Class檔案常量池、運作時常量池,全局字元串常量池,以及基本類型包裝類對象常量池 。

Class檔案常量池。class檔案是一組以位元組為機關的二進制資料流,在java代碼的編譯期間,我們編寫的java檔案就被編譯為.class檔案格式的二進制資料存放在磁盤中,其中就包括class檔案常量池。

運作時常量池:運作時常量池相對于class常量池一大特征就是具有動态性,java規範并不要求常量隻能在運作時才産生,也就是說運作時常量池的内容并不全部來自class常量池,在運作時可以通過代碼生成常量并将其放入運作時常量池中,這種特性被用的最多的就是String.intern()。

全局字元串常量池:字元串常量池是JVM所維護的一個字元串執行個體的引用表,在HotSpot VM中,它是一個叫做StringTable的全局表。在字元串常量池中維護的是字元串執行個體的引用,底層C++實作就是一個Hashtable。這些被維護的引用所指的字元串執行個體,被稱作”被駐留的字元串”或”interned string”或通常所說的”進入了字元串常量池的字元串”。

基本類型包裝類對象常量池:java中基本類型的包裝類的大部分都實作了常量池技術,這些類是Byte,Short,Integer,Long,Character,Boolean,另外兩種浮點數類型的包裝類則沒有實作。另外上面這5種整型的包裝類也隻是在對應值小于等于127時才可使用對象池,也即對象不負責建立和管理大于127的這些類的對象。

. 如何判斷一個對象是否存活?

判斷一個對象是否存活,分為兩種算法1:引用計數法;2:可達性分析算法;

引用計數法:給每一個對象設定一個引用計數器,當有一個地方引用該對象的時候,引用計數器就+1,引用失效時,引用計數器就-1;當引用計數器為0的時候,就說明這個對象沒有被引用,也就是垃圾對象,等待回收;缺點:無法解決循環引用的問題,當A引用B,B也引用A的時候,此時AB對象的引用都不為0,此時也就無法垃圾回收,是以一般主流虛拟機都不采用這個方法;

可達性分析法從一個被稱為GC Roots的對象向下搜尋,如果一個對象到GC Roots沒有任何引用鍊相連接配接時,說明此對象不可用,在java中可以作為GC Roots的對象有以下幾種:

虛拟機棧中引用的對象

方法區類靜态屬性引用的變量

方法區常量池引用的對象

本地方法棧JNI引用的對象

但一個對象滿足上述條件的時候,不會馬上被回收,還需要進行兩次标記;第一次标記:判斷目前對象是否有finalize()方法并且該方法沒有被執行過,若不存在則标記為垃圾對象,等待回收;若有的話,則進行第二次标記;第二次标記将目前對象放入F-Queue隊列,并生成一個finalize線程去執行該方法,虛拟機不保證該方法一定會被執行,這是因為如果線程執行緩慢或進入了死鎖,會導緻回收系統的崩潰;如果執行了finalize方法之後仍然沒有與GC Roots有直接或者間接的引用,則該對象會被回收;

. 強引用、軟引用、弱引用、虛引用是什麼,有什麼差別?

強引用,就是普通的對象引用關系,如 String s = new String(“ConstXiong”)

軟引用,用于維護一些可有可無的對象。隻有在記憶體不足時,系統則會回收軟引用對象,如果回收了軟引用對象之後仍然沒有足夠的記憶體,才會抛出記憶體溢出異常。SoftReference 實作

弱引用,相比軟引用來說,要更加無用一些,它擁有更短的生命周期,當 JVM 進行垃圾回收時,無論記憶體是否充足,都會回收被弱引用關聯的對象。WeakReference 實作

虛引用是一種形同虛設的引用,在現實場景中用的不是很多,它主要用來跟蹤對象被垃圾回收的活動。PhantomReference 實作

. 被引用的對象就一定能存活嗎?

不一定,看 Reference 類型,弱引用在 GC 時會被回收,軟引用在記憶體不足的時候,即 OOM 前會被回收,但如果沒有在 Reference Chain 中的對象就一定會被回收。

. Java中的垃圾回收算法有哪些?

java中有四種垃圾回收算法,分别是标記清除法、标記整理法、複制算法、分代收集算法; 标記清除法 :第一步:利用可達性去周遊記憶體,把存活對象和垃圾對象進行标記;第二步:在周遊一遍,将所有标記的對象回收掉;特點:效率不行,标記和清除的效率都不高;标記和清除後會産生大量的不連續的空間分片,可能會導緻之後程式運作的時候需配置設定大對象而找不到連續分片而不得不觸發一次GC