JVM問題及解答

Real_man發表於2019-02-15

常見JVM問題

  1. JVM記憶體模型,GC機制和原理。 注意JVM記憶體模型與Java記憶體模型(JMM)不是同一個東西。 JVM = 類載入器(classloader) + 執行引擎(execution engine) + 執行時資料區域(runtime data area)
    image
  • PC暫存器:儲存JVM正在執行的位元組碼指令地址。如果是native的,那麼pc暫存器的值為undefined
  • JVM Stack:儲存區域性變數與一些過程結果的地方。在方法呼叫和返回中也扮演了很重要的角色。
  • Heap:堆是可以可供各個執行緒共享的執行時儲存區域,也是供所有類的例項和陣列物件分配記憶體的區域。堆在JVM啟動的時候建立。堆所儲存的就是被GC所管理的各種物件。
  • Method Area:各個執行緒共享的執行時記憶體區,它儲存每一個類的例項資訊,執行時常量池,欄位和方法資料,建構函式和普通方法的位元組碼等內容。還有一些特殊方法。
  • Run-Time Constant Pool:在方法區中分配,在載入類和介面到虛擬機器之後,就建立對應的執行時常量池。
  • Native Method Stacks:JDK中native的方法,System類和Thread類中有很多。使用C語言編寫的方法,這個也通常叫做C stack。

image

參考:多執行緒之Java記憶體模型(JMM)(一)

  1. GC分哪兩種,Minor GC 和Full GC有什麼區別?什麼時候會觸發Full GC?分別採用什麼演算法?
  • 從年輕代空間(包括 Eden 和 Survivor 區域)回收記憶體被稱為 Minor GC。當Eden區滿時,觸發Minor GC。
  • Full GC 是清理整個堆空間—包括年輕代和老年代。Major GC通常是跟full GC是等價的,收集整個GC堆。
    • 呼叫System.gc時,系統建議執行Full GC,但是不必然執行,
    • 由Eden區、From Space區向To Space區複製時,物件大小大於To Space可用記憶體,則把該物件轉存到老年代,且老年代的可用記憶體小於該物件大小
    • 老年代空間不足
    • 通過Minor GC後進入老年代的平均大小大於老年代的可用記憶體

Minor GC採用複製演算法,將Eden區的物件複製到Suvivor空間,然後進行清除 Full Gc採用的是標記-壓縮演算法,讓所有的物件都向一端移動,然後直接清理掉端邊界以外的記憶體。

參考:Java的垃圾回收機制-垃圾收集演算法(一)

  1. JVM裡的有幾種classloader,為什麼會有多種? JDK中預設情況下有三種,如下圖
    image
  • BootStrap ClassLoader,負責載入<JAVA_HOME>/lib或被-Xbootclasspath指定路徑下的類庫,開發者不可以直接使用
  • Extension ClassLoader,負責載入<JAVA_HOME>/lib/ext或被java.ext.dirs系統變數指定的路徑中的所有類庫,開發者可以直接使用
  • App ClassLoader,這個類載入器是ClassLoader.getSystemClassLoader()的返回值,負責載入使用者類路徑上所指定的類庫,開發者可以直接使用這個類載入器,如果應用程式沒有自定義過類載入器,那麼系統預設使用這個類載入器。

如果一個類收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把請求委派給父類來實現,每一個層次的類載入器都是這樣,因此所有的類載入請求都會最終傳送到啟動類載入器,只有當父類載入器無法完成這個載入請求,子類載入器才會自己嘗試載入。

好處:避免記憶體中出現同樣的位元組碼。可以很好的解決各個類載入器的基礎類統一問題。

參考:JVM是如何載入類的?

  1. 什麼情況下我們需要破壞雙親委派模型? 熱替換,熱部署。OSGI是Java業界廣泛認可的模組化標準,而OSGI模組化熱部署的關鍵是它自定義的類載入器。每一個模組都有一個自己的類載入器,當需要更換一個 Bundle(包) 時,Bundle連同類載入器一同替換實現程式碼熱部署。

弄懂了OSGi的精髓,就可以算是掌握了類載入器的精髓

參考:JVM是如何載入類的?

  1. 常見的JVM調優方法有哪些?可以具體到調整哪個引數,調成什麼值? 主要調節吞吐量和響應時間: 參考: 記一次JVM調優 如何解決OutOfMemoryError

  2. JVM class檔案結構是如何解析的;

  3. 可見性,重排序,final關鍵字 可見性是指一個執行緒修改某個共享變數的值的時候,其它執行緒能立刻得知。 重排序是底層做的優化,有些程式碼會在機器層面上提前執行,從而出現不正常的結果 final在Java中是一個保留的關鍵字,可以宣告成員變數、方法、類以及本地變數。一旦你將引用宣告作final,你將不能改變這個引用了,編譯器會檢查程式碼,如果你試圖將變數再次初始化的話,編譯器會報編譯錯誤。

參考:深入理解final關鍵字 8. Java物件模型 每一個Java類,在被JVM載入的時候,JVM會給這個類建立一個instanceKlass,儲存在方法區,用來在JVM層表示該Java類。當我們在Java程式碼中,使用new建立一個物件的時候,JVM會建立一個instanceOopDesc物件,這個物件中包含了兩部分資訊,物件頭以及後設資料。物件頭中有一些執行時資料,其中就包括和多執行緒相關的鎖的資訊。後設資料其實維護的是指標,指向的是物件所屬的類的instanceKlass。

這個內容比較多,需要深入理解

參考:Java物件模型

  1. oop-klass

  2. 物件存活判定 可達性分析演算法:可達性分析,通過一系列"GC Roots"的物件作為起始點,往下走,一個物件到GC roots沒有任何引用的時候,則證明物件是不可用的 可作為GC roots的物件:

  • 虛擬機器棧中引用的物件
  • 方法區中類靜態屬性引用的物件
  • 方法區中常量引用的物件
  • 本地方法棧中JNI引用的物件
  1. Java的引用型別
  • 強引用:正常物件賦值的語句,String a = "abc";強引用可以直接訪問目標物件,並且不會被系統回收,強引用可能導致記憶體洩漏
  • 軟引用:比強引用稍微弱一點的型別,只有再記憶體不足的情況下才會被回收。如果再記憶體足夠的時候,呼叫System.gc()也不會回收軟引用的物件。
  • 弱引用:比軟引用弱一點的引用型別,只要發現弱引用,都會將物件進行回收。
  • 虛引用:最弱的一個引用型別,一個持有虛引用的物件,和沒有引用幾乎是一樣的。虛引用必須和引用佇列一起使用,它的作用在於跟蹤垃圾回收的過程。

參考:Java Primitive type與References type

最後

本文持續更新...

相關文章