JVM相關面試題彙總

說一下 JVM 的主要組成部分?及其作用?

類加載器(ClassLoader)
運行時數據區(Runtime Data Area)
執行引擎(Execution Engine)
本地庫接口(Native Interface)
組件的作用: 首先通過類加載器(ClassLoader)會把 Java代碼轉換成字節碼,運行時數據區(Runtime Data Area)再把字節碼加載到內存中,而字節碼文件只是 JVM的一套指令集規範,並不能直接交給底層操作系統去執行,因此需要特定的命令解析器執行引擎(ExecutionEngine),將字節碼翻譯成底層系統指令,再交由 CPU 去執行,而這個過程中需要調用其他語言的本地庫接口(NativeInterface)來實現整個程序的功能

說一下 JVM 運行時數據區?詳細介紹下每個區域的作用?

程序計數器(Program Counter
Register):當前線程所執行的字節碼的行號指示器,字節碼解析器的工作是通過改變這個計數器的值,來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能,都需要依賴這個計數器來完成;
Java 虛擬機棧(Java Virtual Machine Stacks):用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息;
本地方法棧(Native Method Stack):與虛擬機棧的作用是一樣的,只不過虛擬機棧是服務 Java 方法的,而本地方法棧是爲虛擬機調用 Native 方法服務的;
Java 堆(Java Heap):Java 虛擬機中內存最大的一塊,是被所有線程共享的,幾乎所有的對象實例都在這裏分配內存;
方法區(Methed Area):用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯後的代碼等數據。

java中都有哪些類加載器

啓動類加載器(Bootstrap ClassLoader),是虛擬機自身的一部分,用來加載Java_HOME/lib/目錄中的,或者被
-Xbootclasspath 參數所指定的路徑中並且被虛擬機識別的類庫;
擴展類加載器(Extension ClassLoader):負責加載\lib\ext目錄或Java. ext. dirs系統變量指定的路徑中的所有類庫;
應用程序類加載器(Application ClassLoader)。負責加載用戶類路徑(classpath)上的指定類庫,我們可以直接使用這個類加載器。一般情況,如果我們沒有自定義類加載器默認就是用這個加載器。

自定義類加載器:通過繼承ClassLoader抽象類,實現loadClass方法

哪些情況會觸發類加載機制

什麼情況下需要開始類加載呢?
JVM規定有且只有5種情況需要立即對類進行初始化:

1.在遇到 new、putstatic、getstatic、invokestatic 字節碼指令時,如果類尚未初始化,則需要先觸發初始化。    
  

2.對類進行反射調用時,如果類還沒有初始化,則需要先觸發初始化。

  
3.初始化一個類時,如果其父類還沒有初始化,則需要先初始化父類。

4.虛擬機啓動時,用於需要指定一個包含 main() 方法的主類,虛擬機會先初始化這個主類。

5.當使用 JDK 1.7 的動態語言支持時,如果一個 java.lang.invoke.MethodHandle 實例最後的解析結果爲 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,並且這個方法句柄所對應的類還沒初始化,則需要先觸發初始化。

什麼是雙親委派模型?

如果一個類加載器收到了類加載的請求,它首先不會自己去加載這個類,而是把這個請求委派給父類加載器去完成,每一層的類加載器都是如此,這樣所有的加載請求都會被傳送到頂層的啓動類加載器中,只有當父加載無法完成加載請求(它的搜索範圍中沒找到所需的類)時,子加載器纔會嘗試去加載類。

說一下類裝載的執行過程?

類裝載分爲以下 5 個步驟:

加載:根據查找路徑找到相應的 class 文件然後導入; 檢查:檢查加載的 class 文件的正確性; 準備:給類中的靜態變量分配內存空間;
解析:虛擬機將常量池中的符號引用替換成直接引用的過程。符號引用就理解爲一個標示,而在直接引用直接指向內存中的地址;
初始化:對靜態變量和靜態代碼塊執行初始化工作。

怎麼判斷對象是否可以被回收?

一般有兩種方法來判斷:

引用計數器:爲每個對象創建一個引用計數,有對象引用時計數器 +1,引用被釋放時計數 -1,當計數器爲 0
時就可以被回收。它有一個缺點不能解決循環引用的問題; 可達性分析:從 GC Roots
開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到 GC Roots 沒有任何引用鏈相連時,則證明此對象是可以被回收的。

哪些變量可以作爲GC Roots

見可作爲GC Roots節點的對象

虛擬機棧中 (棧幀中的本地變量表) 引用的對象
方法區中類靜態屬性引用的對象
方法區中常量的引用對象
本地方法棧中引用的對象

Java 中都有哪些引用類型?

強引用:發生 gc 的時候不會被回收。 軟引用:有用但不是必須的對象,在發生內存溢出之前會被回收。
弱引用:有用但不是必須的對象,在下一次GC時會被回收。 虛引用(幽靈引用/幻影引用):無法通過虛引用獲得對象,用 PhantomReference 實現虛引用,虛引用的用途是在 gc 時返回一個通知。

說一下 JVM 有哪些垃圾回收算法?

標記-清除算法:標記無用對象,然後進行清除回收。缺點:效率不高,無法清除垃圾碎片。
標記-整理算法:標記無用對象,讓所有存活的對象都向一端移動,然後直接清除掉端邊界以外的內存。
複製算法:按照容量劃分二個大小相等的內存區域,當一塊用完的時候將活着的對象複製到另一塊上,然後再把已使用的內存空間一次清理掉。缺點:內存使用率不高,只有原來的一半。
分代算法:根據對象存活週期的不同將內存劃分爲幾塊,一般是新生代和老年代,新生代基本採用複製算法,老年代採用標記整理或標記清楚算法。

說一下 JVM 有哪些垃圾回收器?

Serial:最早的單線程串行垃圾回收器。
Serial Old:Serial 垃圾回收器的老年版本,同樣也是單線程的,可以作爲 CMS 垃圾回收器的備選預案。
ParNew:是 Serial 的多線程版本。
Parallel 和 ParNew 收集器類似是多線程的,但 Parallel 是吞吐量優先的收集器,可以犧牲等待時間換取系統的吞吐量。
Parallel Old 是 Parallel 老生代版本,Parallel 使用的是複製的內存回收算法,Parallel Old 使用的是標記-整理的內存回收算法。
CMS:一種以獲得最短停頓時間爲目標的收集器,非常適用 B/S 系統。
G1:一種兼顧吞吐量和停頓時間的 GC 實現,是 JDK 9 以後的默認 GC 選項。

新生代垃圾回收器和老生代垃圾回收器都有哪些?有什麼區別?

  • 新生代回收器:Serial、ParNew、Parallel Scavenge
  • 老年代回收器:Serial Old、Parallel Old、CMS
  • 整堆回收器:G1

新生代垃圾回收器一般採用的是複製算法,複製算法的優點是效率高,缺點是內存利用率低;老年代回收器一般採用的是標記-整理的算法進行垃圾回收。

簡述分代垃圾回收器是怎麼工作的?

分代回收器有兩個分區:老生代和新生代,新生代默認的空間佔比總空間的 1/3,老生代的默認佔比是 2/3。

新生代使用的是複製算法,新生代裏有 3 個分區:Eden、To Survivor、From Survivor,它們的默認佔比是
8:1:1,它的執行流程如下:

把 Eden + From Survivor 存活的對象放入 To Survivor 區; 清空 Eden 和 From Survivor
分區; From Survivor 和 To Survivor 分區交換,From Survivor 變 To Survivor,To
Survivor 變 From Survivor。 每次在 From Survivor 到 To Survivor
移動時都存活的對象,年齡就 +1,當年齡到達 15(默認配置是 15)時,升級爲老生代。大對象也會直接進入老生代。

老生代當空間佔用到達某個值之後就會觸發全局垃圾收回,一般使用標記整理的執行算法。以上這些循環往復就構成了整個分代垃圾回收的整體執行流程。

Minor GC與Full GC分別在什麼時候發生?

新生代內存不夠用時候發生MGC也叫YGC,JVM堆內存不夠的時候發生FGC在

有沒有在生產環境下排過錯,說說過程?

可以說案例分析裏面的案例,也可以說內存飆高的排查

說一下 你知道的JVM 性能監控的工具?

JDK 自帶了很多監控工具,都位於 JDK 的 bin 目錄下,其中最常用的是 jconsole 和 jvisualvm
這兩款視圖監控工具。 jps jinfo jstat jmap jstack

jconsole:用於對 JVM 中的內存、線程和類等進行監控; jvisualvm:JDK
自帶的全能分析工具,可以分析:內存快照、線程快照、程序死鎖、監控內存的變化、gc 變化等

idea + jprofiler內存分析

常用的 JVM 調優的參數都有哪些?

-Xms2g:初始化推大小爲 2g;
-Xmx2g:堆最大內存爲 2g;
-Xmn500M 設置年輕代爲500m;
-XX:PermSize=500M ;  1.8之後採用 MetaspaceSize
-XX:MaxPermSize=500M ;  1.8之後採用 MaxMetaspaceSize
-XX:NewRatio=4:設置年輕的和老年代的內存比例爲 1:4;
-XX:SurvivorRatio=8:設置新生代 Eden 和 Survivor 比例爲 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器組合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器組合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器組合;
-XX:+PrintGC:開啓打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 詳細信息。
-XX:MaxDirectMemorySize : 設置直接內存的大小

JVM調優小結

JVM參數調優實際上沒有具體的答案,要根據不同的實戰場景進行對應的設置,還需要不斷的調試和磨合,設置的不好,JVM不斷執行Full
GC,導致整個系統變得很慢,網站停滯時間能達10秒以上,這種情況如果沒隔幾分鐘就來一次,自己都受不了。這種停滯在測試的時候看不出來,只有網站pv達到數十萬/天的時候問題就暴露出來了。
所以對於JVM調優的話術,我們可以這麼說,結合我們公司之前的經驗,對於JVM調優我們可以從下面方向進行分析:

1:如果服務器硬件性能足夠,建議採用64位操作系統,Linux下64位的jdk比32位jdk要慢一些,但是吃得內存更多,吞吐量更大。

2:XMX和XMS設置一樣大,MaxPermSize和MinPermSize設置一樣大,這樣可以減輕伸縮堆大小帶來的壓力。

3: -Xmn年輕代的大小, 並行:吞吐量 併發:低延遲 -XX:NewRadio年輕代和年老代的比值,
Sun建議 年輕代與年老代的比例:3/8 4: 垃圾回收器的選擇: 響應時間優先的應用:併發收集器 ParNew + CMS
或者 G1 吞吐量優先的應用:並行收集器 Parallel Scavenge + Parallel Old
使用併發收集器,肯定就是追求最小的響應時間,所以應該減少年輕代,加大年老代,這樣可以利用年老代的併發CMS收集器來減少響應時間。
使用併發收集器,一般是最求吞吐量優先的應用,會加大年輕代,縮小年老代。這樣可以在年輕代回收掉大部分短期對象,減少中期對象,而老年代只存少部分長時間存活的對象。
(年老代的併發收集器使用標記,清除算法,所以不會對堆進行壓縮.當收集器回收時,他會把相鄰的空間進行合併,這樣可以分配給較大的對象.但是,當堆空間較小時,運行一段時間以後,就會出現"內存碎片",如果併發收集器找不到足夠的空間,那麼併發收集器將會停止,然後使用傳統的標記,清除方式進行回收.如果出現"碎片",可能需要進行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用併發收集器時,開啓對年老代的壓縮.
-XX:CMSFullGCsBeforeCompaction=0:上面配置開啓的情況下,這裏設置多少次Full GC後,對年老代進行壓縮 )

5:調試的時候設置一些打印參數 如: -XX:+PrintClassHistogram
-XX:+HeapOnOutOfMerroryError -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log 這樣可以讓jvm虛擬機打印出類加載的情況,堆轉儲的快照,GC的詳細回收日誌 等等日誌信息
6:當系統發生停頓的時候可能是GC的問題也可能是程序的問題,還有內存飆高,系統響應慢的時候,多利用jvm的監控工具實時注意jvm虛擬機的情況。
如可以通過jmap轉儲堆內存情況,通過jstack可以打印出線程的快照,在通過JProfiler或者JVisoulVM的分析工具進行分析。
– 這裏可以加入JVM調優案例

7:仔細瞭解自己的應用,如果用了緩存,那麼年老代應該大一些

8:垃圾回收時promotion failed是個很頭痛的問題,一般可能是兩種原因產生
第一個原因是救助空間不夠,救助空間裏的對象還不應該被移動到年老代,但年輕代又有很多對象需要放入救助空間;第二個原因是年老代沒有足夠的空間接納來自年輕代的對象;這兩種情況都會轉向Full
GC,網站停頓時間較長。 第一個原因我的最終解決辦法是去掉救助空間, 設置-XX:SurvivorRatio=65536
-XX:MaxTenuringThreshold=0即可

第二個原因我的解決辦法是設置CMSInitiatingOccupancyFraction爲某個值(假設70),這樣年老代空間到70%時就開始執行CMS,年老代有足夠的空間接納來自年輕代的對象。

服務器: centos 6.4 linux64位操作系統 8G內存: 每日百萬PV,無壓力:

JAVAARGS.="Dresin.home=JAVA_ARGS .= " -Dresin.home=SERVER_ROOT
-server
-Xms6000M //堆初始值內存
-Xmx6000M //堆的最大內存
-Xmn500M //新生代內存
-XX:PermSize=500M //永久代初始值
-XX:MaxPermSize=500M //永久代最大值
-XX:SurvivorRatio=65536 //新生代救助區和Eden區 比值 這麼設置實際上市去掉救助區
-XX:MaxTenuringThreshold=0 //新生代對象晉升老年的代年齡,設置0
-Xnoclassgc //不開啓class收集
-XX:+DisableExplicitGC //禁止 System.gc()顯示調用,防止手殘黨
-XX:+UseParNewGC //年輕代垃圾收集器 ParNewGC
-XX:+UseConcMarkSweepGC //年老代垃圾收集器 CMS
-XX:+UseCMSCompactAtFullCollection //開啓內存壓縮功能
-XX:CMSFullGCsBeforeCompaction=0
-XX:+CMSClassUnloadingEnabled //開啓永久代回收
-XX:-CMSParallelRemarkEnabled
-XX:CMSInitiatingOccupancyFraction=80 //永久代佔用多少時 進行永久代回收
-XX:SoftRefLRUPolicyMSPerMB=0
-XX:+PrintClassHistogram //打印class加載信息
-XX:+PrintGCDetails //打印GC信息
-XX:+PrintGCTimeStamps //打印GC信息
-XX:+PrintHeapAtGC //打印GC 堆的信息
-Xloggc:log/gc.log ";

發佈了13 篇原創文章 · 獲贊 7 · 訪問量 781
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章