天天看點

JVM 常見面試題指南

如下圖所示:

黃色部分為線程共有,藍色部分為線程私有。

整體流程如下圖所示:

第1步:虛拟機遇到一個 new 指令,首先将去檢查這個指令的參數是否能在常量池中定位到這個類的符号引用,并且檢查這個符号引用的類是否已經被 加載 - 解析 - 初始化。

第2步:如果類已經被加載那麼進行第3步;如果沒有進行加載,那麼就需要先進行類的加載。

第3步:類加載檢查通過之後,接下來進行新生對象的記憶體配置設定。

第4步:對象生成需要的記憶體大小在類加載完成後便可完全确定,為對象配置設定空間等同于把一塊确定大小的記憶體從 Java堆 中劃分出來。

第5步:記憶體大小的劃分分為兩種情況:

第一種情況:JVM的記憶體是規整的,所有的使用的記憶體都放到一邊,空閑的記憶體在另外一邊,中間放一個指針作為分界點的訓示器。那麼這時候配置設定記憶體就比較簡單,隻要講指針向空閑空間那邊挪動一段與對象大小相同的距離。這種就是“指針碰撞”。

第二種情況:JVM 的記憶體不是規整的,也就是說已使用的記憶體與未使用的記憶體互相交錯。這時候就沒辦法利用指針碰撞了。這時候我們就需要維護一張表,用于記錄那些記憶體可用,在配置設定的時候從清單中找到一塊足夠大的空間劃分給對象執行個體,并更新到記錄表上。

第6步:空間申請完成之後,JVM需要将記憶體的空間都初始化為 0 值。如果使用 TLAB,就可以在 TLAB 配置設定的時候就可以進行該工作。

第7步:JVM對對象進行必要的設定。例如,這個對象是哪個類的執行個體、對象的哈希碼、GC年代等資訊。

第8步:完成了上面的步驟之後 從 JVM 來看一個對象基本上完成了,但從 Java 程式代碼絕對來看,對象建立才剛剛開始,需要執行 < init > 方法,按照程式中設定的初始化操作初始化,這時候一個真正的程式對象生成了。

類加載的過程包括了:

第一步:加載

加載是類加載過程的第一個階段,虛拟機在這一階段需要完成以下三件事情:

通過類的全限定名來擷取其定義的二進制位元組流

将位元組流所代表的靜态存儲結構轉化為方法區的運作時資料結構

在 Java 堆中生成一個代表這個類的 java.lang.Class 對象,作為對方法區中這些資料的通路入口

第二步:驗證

確定被加載的類的正确性。

這一階段是確定 Class 檔案的位元組流中包含的資訊符合目前虛拟機的規範,并且不會損害虛拟機自身的安全。

包含了四個驗證動作:檔案格式驗證,中繼資料驗證,位元組碼驗證,符号引用驗證。

第三步:準備

為類的靜态變量配置設定記憶體,并将其初始化為預設值。

準備階段是正式為類變量配置設定記憶體并設定類變量初始值的階段,這些記憶體都将在方法區中配置設定。

第四步:解析

把類中的符号引用轉換為直接引用。

解析階段是虛拟機将常量池内的符号引用替換為直接引用的過程,解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符 7 類符号引用進行。

第五步:初始化

類變量進行初始化。

為類的靜态變量賦予正确的初始值,JVM 負責對類進行初始化,主要對類變量進行初始化。

從圖中可以看出,CMS 收集器的工作過程可以分為 4 個階段:

初始标記(CMS initial mark)階段

并發标記(CMS concurrent mark)階段

重新标記(CMS remark)階段

并發清除((CMS concurrent sweep)階段

使用算法:複制+标記清除

G1 在壓縮空間方面有優勢。

G1 通過将記憶體空問分成區域 (Region)的方式避免記憶體碎片問題,

Eden , Survivor , old 區不再固定、在記憶體使用效率上區說更靈活。

G1 可以通過設定預期停頓時間 (Pause Time) 區控制垃圾收集時間避免應用雪崩現象。

G1 在回收記憶體後會馬上同時做合并空閑記憶體的工作、而 CMS 預設是在 STW (stopthe world)的時候做

G1 會在Young GC 中使用、而 CMS 隻能在 o 區使用。

吞吐量優先:G1

響應優先:CMS

CMS 的缺點是對 cpu 的要求比較高。

G1是将記憶體化成了多塊,所有對内段的大小有很大的要求。

CMS 是清除,是以會存在很多的記憶體碎片。

G1 是整理,是以碎片空間較小。

Xms:初始堆大小

Xmx:最大堆大小

Xss: Java 每個線程的Stack大小

XX:NewSize=n:設定年輕代大小。

XX:NewRatio=n:設定年輕代和年老代的比代。如:為3,表示年輕代與年老代比代為 1:3,年輕代占整個年輕代年老代和的 1/4。

XX:SurvivorRatio=n:年輕代中 Eden 區與兩個 Survivor 區的比代。注意 Survivor區有兩個。如:3,表示 Eden : Survivor=3: 2,一個 survivor 區占整個年輕代的1 / 5。

XX: MaxPermSize=n:設定持久代大小。

XX:+UseSesialGC:設定串行收集器

XX:+UsePasallelGC:設定并行收集器

XX:+UsePasalledlOld GC:設定并行年老代收集器

XX:+UseConcMask SweepGC:設定并發收集器

XX:+PsintGC:列印GC 的簡要資訊

XX:+PsintGCDetails:列印GC詳細資訊

XX:+PsintGCTimeStamps:輸出 GC 的時間戳

jps:用來顯示本地的Java 程序,可以檢視本地運作着幾個 Java程式,并顯示他們的程序号

指令格式:ips

jinfo:運作環境參數:Java System 屬性和 JVM 指令行參數,Java class path 等資訊

指令格式:info 程序 pid

jstat:監視虛拟機各種運作狀态資訊的指令行工具

指令格式:jstat -gc 123 250 20

jstack:可以觀察到 JVM 中目前所有線程的運作情況和線程目前狀态。

指令格式:istack 程序 pid

jmap:觀察運作中的 JVM 實體記憶體的占用情況(如:産生哪些對象,及其數量)

指令格式:jmap [loption] pid

調用 System.gc 時,系統建議執行 Full GC, 但是不必然執行。

老年代空間不足。

方法區空間不足。

超過 Minor GC 後進入老年代的平均大小大于老年代的可用記憶體。

由 Eden 區、survivor spacel (From Space)區向 survivor space2 (To Space)區複制時,對象大小大于 To Space 可用記憶體,則把該對象轉存到老年代,且老年代的可用記憶體小于該對象大小。

方法建立了個很大的對象,如 List , Array。

是否産生了循環調用、死循環。

是否引用了較大的全局變量。

強引用:new 出的對象之類的引用,隻要強引用還在,永遠不會回收。

軟引用:引用但非必須的對象,記憶體溢出異常之前,回收。

弱引用:非必須的對象,對象能生存到下一次垃圾收集發生之前。

虛引用:對生存時間無影響,在垃圾回收時得到通知。

top 檢視目前 CPU 情況,找到占用 CPU 過高的程序 PID=123。

top -H -p123 找出兩個 CPU 占用較高的線程,記錄下來 PID=2345, 3456轉換為十六進制。

jstack -l 123 > temp.txt 列印出目前程序的線程棧。

查找到對應于第二步的兩個線程運作棧,分析代碼。

使用top指令查詢伺服器系統狀态。

ps -aux|grep java 找出目前 Java 程序的 PID。

jmap -histo:dve pid 可用統計存活對象的分布情況,從高到低檢視占據記憶體最多的對象。

jmap -dump:format=b,fice=檔案名 [pid] 利用 Jmap dump。

使用性能分析工具對上一步dump出來的檔案進行分析,工具有 MAT 等。

jvm