1. 程式人生 > >面試專題(JVM 虛擬機器)

面試專題(JVM 虛擬機器)

Java記憶體模型問題

jvm 程序記憶體邏輯結構

直接記憶體:不是虛擬機器執行時資料區的一部分,也不是java虛擬機器規範中定義的記憶體區域;
 如果使用了NIO,這塊區域會被頻繁使用,在java堆內可以用directByteBuffer物件直接引用並操作;
 這塊記憶體不受java堆大小限制,但受本機總記憶體的限制,可以通過MaxDirectMemorySize來設定(預設與堆記憶體最大值一樣),所以也會出現OOM異常;

 

你對jvm記憶體結構瞭解嗎?

程式計數器:較小的記憶體空間,當前執行緒執行的位元組碼的行號指示器;各執行緒之間獨立儲存,互不影響;

java 棧:執行緒私有,生命週期和執行緒,每個方法在執行的同時都會建立一個 棧幀用於儲存區域性變量表,運算元棧,動態連結,方法出口等資訊。方法的執行就對應著棧幀在虛擬機器棧中入棧和出棧的過程;棧裡面存放著各種基本資料型別和物件的引用;

本地方法棧:本地方法棧儲存的是native方法的資訊,當一個JVM建立的執行緒呼叫native方法後,JVM不再為其在虛擬機器棧中建立棧幀,JVM只是簡單地動態連結並直接呼叫native方法;

堆:Java堆是Javaer需要重點關注的一塊區域,因為涉及到記憶體的分配(new關鍵字,反射等)與回收(回收演算法,收集器等);

方法區:也叫永久區,用於儲存已經被虛擬機器載入的類資訊,常量("zdy","123"等),靜態變數(static變數)等資料。

執行時常量池:執行時常量池是方法區的一部分,用於存放編譯期生成的各種字面量("zdy","123"等)和符號引用。

 

你對jvm記憶體結構瞭解嗎?執行緒共享與執行緒私有

堆和棧的區別是什麼?

  功能
 以棧幀的方式儲存方法呼叫的過程,並存儲方法呼叫過程中基本資料型別的變數(int、short、long、byte、float、double、boolean、char等)以及物件的引用變數,其記憶體分配在棧上,變量出了作用域就會自動釋放;
 而堆記憶體用來儲存Java中的物件。無論是成員變數,區域性變數,還是類變數,它們指向的物件都儲存在堆記憶體中;


 執行緒獨享還是共享
棧記憶體歸屬於單個執行緒,每個執行緒都會有一個棧記憶體,其儲存的變數只能在其所屬執行緒中可見,即棧記憶體可以理解成執行緒的私有記憶體。
堆記憶體中的物件對所有執行緒可見。堆記憶體中的物件可以被所有執行緒訪問。

 

 空間大小
的記憶體要遠遠小於堆記憶體,棧的深度是有限制的,如果遞迴沒有及時跳出,很可能發生StackOverFlowError問題。
 你可以通過-Xss選項設定棧記憶體的大小。-Xms選項可以設定堆的開始時的大小,-Xmx選項可以設定堆的最大值

 

你對jvm記憶體結構瞭解嗎?堆和棧

 

你對jvm記憶體結構瞭解嗎?執行緒安全的本質

 

jdk1.6、jdk1.7和jdk1.8記憶體結構區別

jdk1.8的jvm 程序記憶體邏輯結構

為什麼去除方法區

 永久代來儲存類資訊、常量、靜態變數等資料不是個好主意, 很容易遇到記憶體溢位的問題.JDK8的實現中將類的元資料放入 native memory, 將字串池和類的靜態變數放入java堆中. 可以使用MaxMetaspaceSize對元資料區大小進行調整;
 對永久代進行調優是很困難的,同時將元空間與堆的垃圾回收進行了隔離,避免永久代引發的Full GC和OOM等問題;

 

jvm常用記憶體引數設定

注意: java8去掉了-XX:PermSize和-XX:MaxPermSize,新增了-XX:MetaspaceSize和-XX:MaxMetaspaceSize

 

常見記憶體溢位異常問題

有哪些java記憶體溢位異常?

java記憶體溢位異常主要有兩個:
 OutOfMemeoryError:當堆、棧(多執行緒情況)、方法區、元資料區、直接記憶體中資料達到最大容量時產生;
 StackOverFlowError:如果執行緒請求的棧深度大於虛擬機器鎖允許的最大深度,將丟擲StackOverFlowError,其本質還是資料達到最大容量;

 

什麼情況下出現堆溢位?怎麼解決?

  產生原因
堆用於儲存例項物件,只要不斷建立物件,並且保證GC Roots到物件之間有引用的可達,避免垃圾收集器回收例項物件,就會在物件數量達到堆最大容量時產生OutOfMemoryError異常。
java.lang.OutOfMemoryError: Java heap space

  解決辦法
使用-XX:+HeapDumpOnOutOfMemoryError可以讓java虛擬機器在出現記憶體溢位時產生當前堆記憶體快照以便進行異常分析,主要分析那些物件佔用了記憶體;也可使用jmap將記憶體快照匯出;一般檢查哪些物件佔用空間比較大,由此判斷程式碼問題,沒有問題的考慮調整堆引數;

 

什麼情況下出現棧溢位?怎麼解決?

  產生原因
 如果執行緒請求的棧深度大於虛擬機器鎖允許的最大深度,將丟擲StackOverFlowError;
 如果虛擬機器在擴充套件棧時無法申請到足夠的記憶體空間,丟擲OutOfMemeoryError;

  解決辦法
 StackOverFlowError 一般是函式呼叫層級過多導致,比如死遞迴、死迴圈;
 OutOfMemeoryError一般是在多執行緒環境才會產生,一般用“減少記憶體的方法”,既減少最大堆和減少棧容量來換取更多的執行緒支援;

 

什麼情況下出現方法區或元資料區溢位?怎麼解決?

  產生原因
 jdk 1.6以前,執行時常量池還是方法區一部分,當常量池滿了以後(主要是字串變數),會丟擲OOM異常;
 方法區和元資料區還會用於存放class的相關資訊,如:類名、訪問修飾符、常量池、方法、靜態變數等;當工程中類比較多,而方法區或者元資料區太小,在啟動的時候,也容易丟擲OOM異常

  解決辦法
 jdk 1.7之前,通過-XX:PermSize,-XX:MaxPerSize,調整方法區的大小;
 jdk 1.8以後,通過-XX:MetaspaceSize ,-XX:MaxMetaspaceSize,調整元資料區的大小;

 

什麼情況下出現本機直接記憶體溢位?怎麼解決?

  產生原因
jdk本身很少操作直接記憶體,而直接記憶體(DirectMemory)導致溢位最大的特徵是,Heap Dump檔案不會看到明顯異常,而程式中直接或者間接的用到了NIO;
  解決辦法
直接記憶體不受java堆大小限制,但受本機總記憶體的限制,可以通過MaxDirectMemorySize來設定(預設與堆記憶體最大值一樣)

 

垃圾回收面試問題

關於垃圾回收我們必須要了解的知識

 垃圾回收主要回收的是堆記憶體,基於分代的思想:

記憶體怎麼樣分配

  物件分配
 優先在Eden區分配。當Eden區沒有足夠空間分配時, VM發起一次Minor GC, 將Eden區和其中一塊Survivor區內尚存活的物件放入另一塊Survivor區域。如MinorGC時survivor空間不夠,物件提前進入老年代,老年代空間不夠時進行Full GC;
 大物件直接進入老年代,避免在Eden區和Survivor區之間產生大量的記憶體複製, 此外大物件容易導致還有不少空閒記憶體就提前觸發GC以獲取足夠的連續空間.
  物件晉級
 年齡閾值:VM為每個物件定義了一個物件年齡(Age)計數器, 經第一次Minor GC後仍然存活, 被移動到Survivor空間中, 並將年齡設為1. 以後物件在Survivor區中每熬過一次Minor GC年齡就+1. 當增加到一定程度(-XX:MaxTenuringThreshold, 預設15), 將會晉升到老年代.
 提前晉升: 動態年齡判定;如果在Survivor空間中相同年齡所有物件大小的總和大於Survivor空間的一半, 年齡大於或等於該年齡的物件就可以直接進入老年代, 而無須等到晉升年齡.

 

哪些要收回?物件生死判定

  可達性分析演算法
通過一系列的稱為 GC Roots 的物件作為起點, 然後向下搜尋; 搜尋所走過的路徑稱為引用鏈/Reference Chain, 當一個物件到 GC Roots 沒有任何引用鏈相連時, 即該物件不可達, 也就說明此物件是不可用的;

在Java, 可作為GC Roots的物件包括:
1.方法區: 類靜態屬性引用的物件;
2.方法區: 常量引用的物件;
3.虛擬機器棧(本地變量表)中引用的物件.
4.本地方法棧JNI(Native方法)中引用的物件。

 

怎麼回收?方法論?分代收集

  新生代- 標記清除法
該演算法分為“標記”和“清除”兩個階段: 首先標記出所有需要回收的物件(可達性分析), 在標記完成後統一清理掉所有被標記的物件.

  缺點
 效率問題: 標記和清除過程的效率都不高;
 空間問題: 標記清除後會產生大量不連續的記憶體碎片, 空間碎片太多可能會導致在執行過程中需要分配較大物件時無法找到足夠的連續內
存而不得不提前觸發另一次垃圾收集.

 

  新生代- 複製演算法
該演算法的核心是將可用記憶體按容量劃分為大小相等的兩塊, 每次只用其中一塊, 當這一塊的記憶體用完, 就將還存活的物件複製到另外一塊上面, 然後把已使用過的記憶體空間一次清理掉.

  優點
 由於是每次都對整個半區進行記憶體回收,記憶體分配時不必考慮記憶體碎片問題。
 垃圾回收後空間連續,只要移動堆頂指標,按順序分配記憶體即可;
 特別適合java朝生夕死的物件特點;


  缺點
 記憶體減少為原來的一半,太浪費了;
 物件存活率較高的時候就要執行較多的複製操作,效率變低;、
 如果不使用50%的對分策略,老年代需要考慮的空間擔保策略

 

怎麼回收?方法論?分代收集

  老年代- 標記整理演算法
該演算法分為“標記”和“清除”兩個階段:  首先標記出所有需要回收的物件(可達性分析), 在標記完成後讓所有存活的物件都向一端移動,然後清理掉端邊界以外的記憶體;

  優點
 不會損失50%的空間;
 垃圾回收後空間連續,只要移動堆頂指標,按順序分配記憶體即可;
 比較適合有大量存活物件的垃圾回收;
  缺點
 標記/整理演算法唯一的缺點就是效率也不高,不僅要標記所有存活物件,還要整理所有存活物件的引用地址。從效率上來說,標記/整理演算法要低於複製演算法。

 

實現回收?誰來做?垃圾回收器

實現回收?誰來做?垃圾回收器

垃圾回收預設配置及網際網路後臺推薦配置

 在JVM的客戶端模式(Client)下,JVM預設垃圾收集器是序列垃圾收集器(Serial GC + Serial Old,-XX:+USeSerialGC);
 在JVM伺服器模式(Server)下預設垃圾收集器是並行垃圾收集器(ParallelScavaenge +Serial Old,-XX:+UseParallelGC)
 而適用於Server模式下
 ParNew + CMS + SerialOld(失敗擔保),-XX:UseConcMarkSweepGC;
 Parallel scavenge + Parallel,-XX:UseParallelOldGC

JVM 垃圾回收面試常見面試題

 JVM 垃圾回收面試常見面試題
 垃圾回收常用的演算法有哪些?特點是什麼?(見垃圾回收演算法)
 哪幾種垃圾收集器,各自的優缺點,重點講下cms(見垃圾回收器)
 jvm中一次完整的GC流程(從ygc到fgc)是怎樣的,重點講講物件如何晉升到老年代等(見記憶體怎麼樣分配)
 JVM垃圾回收機制,何時觸發MinorGC或FullGC等操作
答:從年輕代空間(包括 Eden 和 Survivor 區域)回收記憶體被稱為 Minor GC,對老年代GC稱為Major GC,而Full GC是對整個堆來說;Minor GC觸發條件:當Eden區滿時,觸發Minor GC。

Full GC觸發條件:
 System.gc()
 老年代空間不足
 永生區空間不足
 統計得到的Minor GC晉升到舊生代的平均大小大於老年代的剩餘空間
 堆中分配很大的物件

 

效能調優工具問題

面試題

1. 你常用的調優工具有哪些?
2. 如果碰到應用故障你怎麼樣排除問題?

 

java常用調優工具

堆dump分析

 堆dump分析:堆dump分析主要目的是定位OOM異常的原因;解決oom問題四部曲:
1. 分析OOM異常的原因,堆溢位?棧溢位?本地記憶體溢位?
2. 如果是堆溢位,匯出堆dump,並對堆記憶體使用有個整體瞭解;
3. 找到最有可能導致記憶體洩露的元凶,通常也就是消耗記憶體最多的物件;
4. 使用輔助工具對dump檔案進行分析;
 注意其他幾類造成OOM異常的原因:
1. Direct Memory
2. 執行緒堆疊:
單執行緒:StackOverflowError
多執行緒:OutOfMemoryError:unable to create new native thread
3. Socket 緩衝區:IOException:Too many open files

執行緒dump分析

 執行緒dump分析:執行緒dump分析主要目的是定位執行緒長時間停頓的原因;

應用故障你怎麼樣排除問題?

 應用故障一般指應用執行緩慢、使用者體驗差或者週期性的出現卡頓,排除的思路:
1. 檢查應用所在的生產環境的軟硬體以及網路環境,排除外圍因素;
2. 確定是否為OOM異常,這類異常影響最惡劣,但是比較容易排查;
3. 確定是否有大量長時間停頓的應用執行緒,非常佔用cpu資源;
4. 週期性的卡頓很可能是垃圾回收造成,web後端系統建議使用cms垃圾回收器;

 

類載入機制問題

類的完整生命週期

什麼時候出發類載入?

1. 使用new關鍵字例項化物件,讀取或者設定一個類的靜態變數的時候,呼叫類的靜態方法的時候;
2. 對類進行反射呼叫的時候;
3. 初始化子類時,父類會先被初始化;
4. 對類使用動態代理的時候需要先被初始化

談下你對雙親委派模型理解?

談下你對雙親委派模型理解?

 雙親委派模型好處
Java類隨著它的類載入器一起具備了帶有優先順序的層次關係,保證java程式穩定執行

 

tomcat 類載入機制

 同一個tomcat容器下的兩個應用以及lib目錄中都有UserServiceImpl類,tomcat怎麼樣保證類的隔離性?

類載入器與類的唯一性:類載入器雖然只用於實現類的載入動作,但是對於任意一個類,都需要由載入它的類載入器和這個類本身共同確立其在Java虛擬機器中的唯一性。通俗的說,JVM中兩個類是否“相等”,首先就必須是同一個類載入器載入的,否則,即使這兩個類來源於同一個Class檔案,被同一個虛擬機器載入,只要類載入器不同,那麼這兩個類必定是不相等的。

 Tomcat目錄結構中,有三組目錄(“/common/*”,“/server/*”和“shared/*”)可以存放公用Java類庫,此外還有第四組Web應用程式自身的目錄“/WEB-INF/*”,把java類庫放置在這些目錄中的含義分別是:
 放置在common目錄中:類庫可被Tomcat和所有的Web應用程式共同使用。
 放置在server目錄中:類庫可被Tomcat使用,但對所有的Web應用程式都不可見。
 放置在shared目錄中:類庫可被所有的Web應用程式共同使用,但對Tomcat自己不可見。
 放置在/WebApp/WEB-INF目錄中:類庫僅僅可以被此Web應用程式使用,對Tomcat和其他Web應用程式都不可見。
 注意:tomcat的類載入機制是違反了雙親委託原則的,對於一些未載入的非基礎類(Object,String等),各個web應用自己的類載入器(WebAppClassLoader)會優先載入,載入不到時再交給commonClassLoader走雙親委託