JVM與垃圾回收面試總結

1. JVM垃圾回收機制與實現

  • 堆:所有的對象實例與數組,GC堆,分爲新生代與老年代
  • 棧:棧幀包含局部變量表(基本數據類型 8種、對象引用類型)、操作數棧、動態鏈接、方法出口
  • 方法區:類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據,也成爲永久代

一般說棧指的是 虛擬機棧,或者說是虛擬機棧中的局部變量表

TLAB:本地線程分配緩衝,線程分配內存,現用TLAB分配,用完重新分配新的TLAB
可以設置是否啓用TLAB

Mark Word:對象頭:對象自身的運行時數據,哈希碼,GC分代年齡,鎖狀態,持有的鎖,偏向線程ID,偏向時間戳

HotSpot採用直接指針訪問,棧中直接指向對象的地址,對象移動時,需要改變棧中的reference

2. 垃圾回收算法

  • 標記清除算法 Mark-sweep
    存在效率問題與空間問題(產生大量不連續的內存碎片)
  • 複製算法(Copying):應用十分廣泛,將內存分爲一塊較大的Eden和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。回收時,將Eden與Survivor複製到另一塊Survivor是哪個。
    默認Eden與Survivor的比例是8:1
  • 標記整理算法(Mark-Compact) 適用於老年代,將所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存

3. 垃圾回收的其他概念

  • Stop The World:從GC Roots進行可達性分析是,爲了保持一致性,停頓所有的Java執行線程。就像時間凍結
  • 準確式GC:虛擬機可以知道內存中某個位置的數據具體是什麼類型
  • OopMaps:用來記錄對象引用,HotSpot使用,類加載過程中,把對象內的偏移量上是什麼數據計算出來。
  • 安全點:SafePoint 既程序執行時並非在所有地方都能停頓下來,只有安全點才能停頓。程序長時間執行的特徵,例如:方法調用、循環跳轉、異常跳轉等
  • 搶先式中斷和主動式中斷:搶先式首先把所有的線程全部中斷,如果發現有線程不在安全點上,則回覆線程;主動式中斷:通過設置標誌,線程執行時輪詢標誌,發現標誌則自己中斷。
  • 安全區域(Safe Region):如果程序不執行的時候,就沒法中斷,所以需要安全區域,一段代碼片段中,應用關係不發生變化,則爲安全區域

4. 垃圾回收器

  1. Serial:新生代,採用複製算法,老年代採用標記整理算法,單線程,需要Stop The Worls,Client模式下的默認新生代收集器
  2. ParNew:Serial的多線程版本,新生代採用複製算法,老年代採用標記整理算法。Server模式的首選新生代收集器,因爲目前只有它與Serial能與CMS收集器配合工作,在多CPU>2的情況下,有更好的性能
  3. Parallel(並行) Scavenge:新生代收集器,複製算法,關注吞吐量,吞吐量=運行用戶代碼的時間/(運行用戶代碼的時間+垃圾收集時間),適合在後臺運算而不需要太多交互的任務。可以設置吞吐量大小eg:GCTimeRatio,也成爲吞吐量優先收集器。可以自動控制新生代的大小、Eden與Suvivor的比例、晉升老年代的年齡,自適應調節策略是Paralle Scavenge與Parnew的最大區別
  4. Serial Old:serial老年代版本,client模式下使用,可以爲Paralle Scavenge收集器搭配使用,或者作爲CMS的後備
  5. Parallel Old:Parallel Scavenge的老年代版本,標記整理算法,注重吞吐量與CPU資源敏感,可以用優先使用Parallel Scavengen+Parallel Old
  6. CMS(Concurrent併發 Mark Sweep):一種獲取最短回收停頓時間爲目標的收集器。目前大量使用在互聯網站或者B/S系統的服務端上,尤其重視服務的響應速度。基於標記-清除算法

  7. image
4個步驟
1.初始標記 需要Stop the World標記一下GCRoots能夠直接關聯到的對象,速度很快
2.併發標記 進行GCRoots Tracing的過程,耗時較長,併發
3.重新標記 需要Stop the World爲了修正併發標記期間因爲用戶程序繼續運作
而導致標記產生變動的那一部分對象的標記記錄,比初始標記階段階段稍長,比並發標記時間短得多
4.併發清除:耗時較長,併發
5.併發重置 :這個階段,重置CMS收集器的數據結構,等待下一次垃圾回收。

CMS的優點:併發收集、低停頓
對CPU資源非常敏感,因爲併發標記與併發清理的過程會佔用CPU,
默認啓動的回收線程數是(CPU數量+3)/4,當CPU 小於4時,佔用了不少於%25的CPU資源。
CPU越多,佔用降低,因此提出了ICMS(Incremental Concurrent Mark Sweep)增量式併發收集器

CMS無法處理浮動垃圾(Floating Garbage)因此可能出現Concurrent Mode Failure,
導致另一次Full GC的出現。
老年代使用達到一定標準,就會激活CMS,JDK1.5默認%68,1.6默認%92,
如果運行期間預留的內存無法滿足程序需要,就會出現Concurrent Mode Failure,臨時啓用Serial OLD

由於使用標記-清除算法,產生了大量空間碎片,
如果無法找到足夠大的連續空間來分配當前對象,不得不觸發另一個Full GC。

G1 收集器 Garbage First。一款新的面向服務端的收集器

 並行與併發:充分利用多CPU來縮短Stop The World的時間
 分代收集:不需要其他收集器,可以獨立管理GC堆
 空間整合:整體上是標記-整理算法,局部上是複製算法
 可預測的停頓:建立可預測的停頓時間模型

 通過將堆空間劃分成多個相等的獨立區域,新生代與老年代不再是物理隔離的,他們都是一部分Region的集合。

 有計劃的避免Full GC,G1跟蹤各個Region裏面的垃圾的價值大小
 (回收所獲得的的空間大小與回收所需時間的比值),
 在後臺維護一個優先列表。每次都根據允許的收集時間,優先回收價值最大的Region。
 1 初始標記 標記GCRoots 能夠直接關聯到的對象,並且修改TAMS
 2 併發標記 從GC roots進行可達性分析
 3 最終標記 修正標記期間因爲用戶程序繼續運作而導致的標記變化
 4 篩選回收 制定回收計劃,進行回收,與CMS不同的是,篩選回收過程在最終標記後直接進行,需要Stop The world

 由於只回收一部分Region,時間是用戶可以控制的,而且停頓用戶線程將大幅度提高收集效率。

5. 新生代與老年代的劃分

  1. 對象有限分配在Eden,Eden沒空間則進行一次Minor GC(新生代GC)
  2. 長期存活的對象,進入老年代,默認爲15次,初始爲0,每經過一次Minor GC+1,默認15晉升老年代,也可以調整閥值
  3. 大於閥值 PretenureSizeTreshold的直接進入老年代,避免Eden與兩個Survivor產生大量的內存複製。
  4. 動態對象年齡判定:如果在Survivor中相同連年大小的總和大於Survivor的一般,年齡大於或者等於該年齡的對象直接進入老年代

    空間分配擔當:如果老年代最大可用的連續空間大於新生代所有的對象的總空間,Minor Gc則可以確保是安全的的,可以設置是否允許擔保失敗,如果允許,則嘗試進行MinorGC,否則進行FullGC。如果MinorGC存活對象過多,出現了HandlePromotionFailure,則在失敗後發起FullGC。

    一般允許擔保失敗,避免FullGC過於頻繁。

6. FullGC 、Minor Gc 、Major GC

Full==MajorGC

Minor GC:新生代垃圾回收,非常頻繁,一般速度比較快
Major Gc:老年代垃圾回收,經常會伴隨一次MinorGC,一般比MinorGC慢10倍以上

7. 判斷對象是否存活一般有兩種方式

  • 引用計數:每個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數爲0時可以回收。此方法簡單,無法解決對象相互循環引用的問題。
  • 可達性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的,不可達對象。

8. 哪些對象可作爲GC Roots對象?

虛擬機棧中應用的對象

方法區裏面的靜態對象

方法區常量池的對象

本地方法棧JNI應用的對象

9. class加載的過程

首先,在代碼編譯後,就會生成JVM(Java虛擬機)能夠識別的二進制字節流文件(*.class)。而JVM把Class文件中的類描述數據從文件加載到內存,並對數據進行校驗、轉換解析、初始化,使這些數據最終成爲可以被JVM直接使用的Java類型,這個說來簡單但實際複雜的過程叫做JVM的類加載機制。

image

我們平常說的加載大多不是指的類加載機制,只是類加載機制中的第一步加載。

JVM主要完成三件事:

  1. 通過一個類的全限定名(包名與類名)來獲取定義此類的二進制字節流(Class文件)。而獲取的方式,可以通過jar包、war包、網絡中獲取、JSP文件生成等方式。

  2. 將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構。這裏只是轉化了數據結構,並未合併數據。(方法區就是用來存放已被加載的類信息,常量,靜態變量,編譯後的代碼的運行時內存區域)

  3. 在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口。這個Class對象並沒有規定是在Java堆內存中,它比較特殊,雖爲對象,但存放在方法區中。

啓動類加載器:Bootstrap ClassLoader,負責加載存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被-Xbootclasspath參數指定的路徑中的,並且能被虛擬機識別的類庫(如rt.jar,所有的java.*開頭的類均被Bootstrap ClassLoader加載)。啓動類加載器是無法被Java程序直接引用的。

擴展類加載器:Extension ClassLoader,該加載器由sun.misc.Launcher$ExtClassLoader實現,它負責加載JDK\jre\lib\ext目錄中,或者由java.ext.dirs系統變量指定的路徑中的所有類庫(如javax.*開頭的類),開發者可以直接使用擴展類加載器。

應用程序類加載器:Application ClassLoader,該類加載器由sun.misc.Launcher$AppClassLoader來實現,它負責加載用戶類路徑(ClassPath)所指定的類,開發者可以直接使用該類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。

應用程序都是由這三種類加載器互相配合進行加載的,如果有必要,我們還可以加入自定義的類加載器。因爲JVM自帶的ClassLoader只是懂得從本地文件系統加載標準的java class文件,因此如果編寫了自己的ClassLoader,便可以做到如下幾點:

1)在執行非置信代碼之前,自動驗證數字簽名。

2)動態地創建符合用戶特定需要的定製化構建類。

3)從特定的場所取得java class,例如數據庫中和網絡中。

JVM類加載機制

•全盤負責,當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其他Class也將由該類加載器負責載入,除非顯示使用另外一個類加載器來載入

•父類委託,先讓父類加載器試圖加載該類,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類

•緩存機制,緩存機制將會保證所有加載過的Class都會被緩存,當程序中需要使用某個Class時,類加載器先從緩存區尋找該Class,只有緩存區不存在,系統纔會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩存區。這就是爲什麼修改了Class後,必須重啓JVM,程序的修改纔會生效

類加載有三種方式:

1、命令行啓動應用時候由JVM初始化加載

2、通過Class.forName()方法動態加載

3、通過ClassLoader.loadClass()方法動態加載

10. 雙親委託模型

從JDK1.2開始,java虛擬機規範推薦開發者使用雙親委派模式(ParentsDelegation Model)進行類加載,其加載過程如下:

(1).如果一個類加載器收到了類加載請求,它首先不會自己去嘗試加載這個類,而是把類加載請求委派給父類加載器去完成。

(2).每一層的類加載器都把類加載請求委派給父類加載器,直到所有的類加載請求都應該傳遞給頂層的啓動類加載器。

(3).如果頂層的啓動類加載器無法完成加載請求,子類加載器嘗試去加載,如果連最初發起類加載請求的類加載器也無法完成加載請求時,將會拋出ClassNotFoundException,而不再調用其子類加載器去進行類加載。

雙親委派模型意義:

  • 系統類防止內存中出現多份同樣的字節碼

  • 保證Java程序安全穩定運行

雙親委派 模式的類加載機制的優點是java類它的類加載器一起具備了一種帶優先級的層次關係,越是基礎的類,越是被上層的類加載器進行加載,保證了java程序的穩定運行。雙親委派模式的實現:

image

http://blog.csdn.net/p10010/article/details/50448491

11. 如何 加載自定義的Jar包

兩種辦法
1. 使用URLClassloader來進行加載,需要指定路徑,可以加載子類和方法的實現類
2. 使用manifest.mf文件來加載,將jar包填入classpath裏面

12. Java JMX

JAVA_OPTS=-Dcom.sun.management.jmxremote

13. Java遠程調試

Java遠程調試的原理是兩個VM之間通過debug協議進行通信,然後以達到遠程調試的目的。兩者之間可以通過socket進行通信。

首先被debug程序的虛擬機在啓動時要開啓debug模式,啓動debug監聽程序。jdwp是Java Debug Wire Protocol的縮寫。

java -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8000,suspend=n zhc_application

這是jdk1.7版本之前的方法,1.7之後可以這樣用:

java -agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n zhc_application

然後用一個debug客戶端去debug遠程的程序了,比如用Eclipse自帶的debug客戶端,填寫運行被debug程序的虛擬機監聽的端口號和地址,選擇connect方式爲attach。

14. 調優參數

參數 說明 默認值
-Xms 初始堆大小 物理內存的1/64(<1GB) 默認(MinHeapFreeRatio參數可以調整)空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制.
-Xmx 最大堆大小 物理內存的1/4(<1GB) 默認(MaxHeapFreeRatio參數可以調整)空餘堆內存大於70%時,JVM會減少堆直到 -Xms的最小限制
-Xmn 年輕代大小(1.4or lator)注意:此處的大小是(eden+ 2 survivor space).與jmap -heap中顯示的New gen是不同的。 整個堆大小=年輕代大小 + 年老代大小 + 持久代大小.增大年輕代後,將會減小年老代大小.此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8
-Xss 每個線程的堆棧大小 JDK5.0以後每個線程堆棧大小爲1M,以前每個線程堆棧大小爲256K.更具應用的線程所需內存大小進行 調整.在相同物理內存下,減小這個值能生成更多的線程.但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右一般小的應用, 如果棧不是很深, 應該是128k夠用的 大的應用建議使用256k。這個選項對性能影響比較大,需要嚴格的測試。(校長)和threadstacksize選項解釋很類似,官方文檔似乎沒有解釋,在論壇中有這樣一句話:””-Xss is translated in a VM flag named ThreadStackSize”一般設置這個值就可以了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章