JVM補充篇

后羿飛箭發表於2020-08-05

1.物件分配原則

   1)物件優先分配在Eden區,如果Eden區沒有足夠的空間時,虛擬機器執行一次Minor GC

    2)大物件直接進入老年代(大物件是指需要大量連續記憶體空間的物件),這樣做的目的是避免在Eden區和兩個Survivor區之間發生大量的記憶體拷貝(新生代採用複製演算法收集記憶體)

    3)長期存活的物件進入老年代,虛擬機器為每個物件定義了一個年齡計數器,如果物件經過了一次Minor GC那麼物件會進入Survivor區,之後沒經過一次Minor GC,那麼年齡會增加1,直到達到閾值物件進入老年代

    4)空間分配擔保。每次進行minor GC時,JVM會計算Survivor區移至老年區的物件的平均大小,如果這個值大於老年區的剩餘值大小,則進行一次Full GC,如果小於檢查HandleFailure設定,如果true則只進行Monitor GC,如果false則進行Full GC

  2.解釋記憶體中的棧(stack),堆(heap)和靜態區(static area)的用法

      通常我們定義一個基本資料型別的變數,一個物件的引用,還有就是函式呼叫的現場儲存都使用記憶體中的棧空間,而通過new關鍵字和構造器建立的物件放在堆空間,程式中的字面量(literal)如直接書寫的100,“hello”和常量都是放在靜態區中,棧空間操作起來最快但是棧很小,通常大量的物件都是放在堆空間,理論上整個記憶體沒有被其他進行使用的空間甚至硬碟上的虛擬記憶體都可以被當成堆空間來使用

    String str=new String("hello")

  上面的語句中變數str放在棧上,用new建立出來的字串物件放在堆中,而 “hello”這個字面量放在靜態區中

3.Perm Space 中儲存什麼資料? 會引起OutOfMemory嗎?

  Perm Space 儲存的是載入的class檔案,會引起OutOfMemory,出現異常可以設定-XX:PermSize 的大小,jdk8以後,字串常量不存放永久代,而是放在堆中,jdk8以後沒有永久代概念,而是元空間替代,元空間不存在虛擬機器中,而是使用本地記憶體

4.什麼是類的載入

   類的載入指的是將類的.class檔案中的二進位制資料讀入到記憶體中,將其放在執行時資料區的方法區內,然後在堆區建立一個java.lang.Class物件,用來封裝類在方法區內的資料結構。類的載入的最終產品是位於堆區中的Class物件,Class物件封裝了類在方法區內的資料結構,並且向Java程式設計師提供了訪問方法區內的資料結構的介面

   

 

 

  • 啟動類載入器:Bootstrap ClassLoader,負責載入存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被-Xbootclasspath引數指定的路徑中的,並且能被虛擬機器識別的類庫
  • 擴充套件類載入器:Extension ClassLoader,該載入器由sun.misc.Launcher$ExtClassLoader實現,它負責載入DK\jre\lib\ext目錄中,或者由java.ext.dirs系統變數指定的路徑中的所有類庫(如javax.*開頭的類),開發者可以直接使用擴充套件類載入器。
  • 應用程式類載入器:Application ClassLoader,該類載入器由sun.misc.Launcher$AppClassLoader來實現,它負責載入使用者類路徑(ClassPath)所指定的類,開發者可以直接使用該類載入器

雙親委派機制:類載入器收到類載入請求,自己不載入,向上委託給父類載入,父類載入不了,再自己載入。優勢就是避免Java核心API篡改。

自定義類載入的意義:

        載入特定路勁的class檔案,

       載入一個加密的網路class檔案

       熱部署載入class檔案

 如何打破雙親委派模型:

       不僅要繼承ClassLoader 類,還要重寫loadClass findClass發放

5.java物件建立過程

    1)JVM遇到一條新建物件的指令時首先去檢查這個指令的引數是否能在常量池中定義到一個類的符號引用,然後載入這類

    2)為物件分配記憶體。一個辦法是指標碰撞  一個是空閒列表 本地執行緒緩衝分配

    3)將除物件頭外的物件記憶體空間初始化0

    4)對物件頭進行必要的設定

6.如何判斷一個物件是否應該被回收

判斷物件是否存活一般有兩種方式:

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

   思考:哪些地方是GC ROOTS開始的地方?

             棧,方法區

7 引用的分類

       

  • 強引用:GC時不會被回收
  • 軟引用:描述有用但不是必須的物件,在發生記憶體溢位異常之前被回收
  • 弱引用:描述有用但不是必須的物件,在下一次GC時被回收
  • 虛引用(幽靈引用/幻影引用):無法通過虛引用獲得物件,用PhantomReference實現虛引用,虛引用用來在GC時返回一個通知。

  8調優命令

    

Sun JDK監控和故障處理命令有jps jstat jmap jhat jstack jinfo

  • jps,JVM Process Status Tool,顯示指定系統內所有的HotSpot虛擬機器程式。
  • jstat,JVM statistics Monitoring是用於監視虛擬機器執行時狀態資訊的命令,它可以顯示出虛擬機器程式中的類裝載、記憶體、垃圾收集、JIT編譯等執行資料。
  • jmap,JVM Memory Map命令用於生成heap dump檔案
  • jhat,JVM Heap Analysis Tool命令是與jmap搭配使用,用來分析jmap生成的dump,jhat內建了一個微型的HTTP/HTML伺服器,生成dump的分析結果後,可以在瀏覽器中檢視
  • jstack,用於生成java虛擬機器當前時刻的執行緒快照。
  • jinfo,JVM Configuration info 這個命令作用是實時檢視和調整虛擬機器執行引數。

9 調優工具

    

常用調優工具分為兩類,jdk自帶監控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。

  • jconsole,Java Monitoring and Management Console是從java5開始,在JDK中自帶的java監控和管理控制檯,用於對JVM中記憶體,執行緒和類等的監控
  • jvisualvm,jdk自帶全能工具,可以分析記憶體快照、執行緒快照;監控記憶體變化、GC變化等。
  • MAT,Memory Analyzer Tool,一個基於Eclipse的記憶體分析工具,是一個快速、功能豐富的Java heap分析工具,它可以幫助我們查詢記憶體洩漏和減少記憶體消耗
  • GChisto,一款專業分析gc日誌的工具

10 jstack 是幹什麼的?jstat? 如果線上程式週期性地出現卡頓,你懷疑可能是GC導致

        jstack 用來查詢java程式的堆疊資訊

        jvisualvm監控記憶體洩露,跟蹤垃圾回收,執行時記憶體,cpu分析,執行緒分析

 11 你有沒有遇到過OutOfMemory 問題? 你是怎麼來處理問題的? 處理過程中有些方式

      permgen space  heap space錯誤

      常見原因:

            1)記憶體載入的資料量太大,一次性從資料庫取太多資料

           2)集合類中有物件的引用,使用後未清空,GC不能進行回收

            3)程式碼中存在迴圈產生過多的重複物件

            4)啟動引數堆記憶體設定太小

12 jdk8以後Perm Space有哪些改動? MataSpace大小是無限的嗎?

  

    JDK 1.8後用元空間替代了 Perm Space;字串常量存放到堆記憶體中。

    MetaSpace大小預設沒有限制,一般根據系統記憶體的大小。JVM會動態改變此值。

    -XX:MetaspaceSize:分配給類後設資料空間(以位元組計)的初始大小(Oracle邏輯儲存上的初始高水位,the initial high-water-mark)。此值為估計值,MetaspaceSize的值設定的過大會延長垃圾回收時間。垃圾回收過後,引起下一次垃圾回收的類後設資料空間的大小可能會變大。

    -XX:MaxMetaspaceSize:分配給類後設資料空間的最大值,超過此值就會觸發Full GC,此值預設沒有限制,但應取決於系統記憶體的大小。JVM會動態地改變此值。

13 StackOverFlow 異常遇到過沒?一般你會猜測有什麼情況下被觸發?

    棧記憶體溢位,一般由棧記憶體的區域性變數過爆了,導致記憶體溢位,出現遞迴方法,引數個數過多,遞迴過深,遞迴沒有出口

14 JVM相關知識

  

 

 java記憶體模型規定了所有的變數都儲存在主記憶體中,每個執行緒還有自己的工作記憶體,執行緒的工作記憶體中儲存了該執行緒中是用到的變數的主存副本拷貝,執行緒對變數的所有操作都必須在工作記憶體中進行,而不能直接讀寫主記憶體,不同的執行緒之間無法直接訪問對方工作記憶體中的變數,執行緒間的傳遞均需要自己的工作記憶體和主存之間進行資料同步進行。

  指令重排序:

    

public class PossibleReordering {
static int x = 0, y = 0;
static int a = 0, b = 0;
 
public static void main(String[] args) throws InterruptedException {
    Thread one = new Thread(new Runnable() {
        public void run() {
            a = 1;
            x = b;
        }
    });
 
    Thread other = new Thread(new Runnable() {
        public void run() {
            b = 1;
            y = a;
        }
    });
    one.start();other.start();
    one.join();other.join();
    System.out.println(“(” + x + “,” + y + “)”);
}

執行結果可能為(1,0)、(0,1)或(1,1),也可能是(0,0)。因為,在實際執行時,程式碼指令可能並不是嚴格按照程式碼語句順序執行的。大多數現代微處理器都會採用將指令亂序執行(out-of-order execution,簡稱OoOE或OOE)的方法,在條件允許的情況下,直接執行當前有能力立即執行的後續指令,避開獲取下一條指令所需資料時造成的等待3。通過亂序執行的技術,處理器可以大大提高執行效率。而這就是指令重排。

記憶體屏障

    記憶體屏障 也叫記憶體柵欄,是一種CPU指令,用於控制特定條件下的重排序和記憶體可見性問題

  • LoadLoad屏障:對於這樣的語句Load1; LoadLoad; Load2,在Load2及後續讀取操作要讀取的資料被訪問前,保證Load1要讀取的資料被讀取完畢。
  • StoreStore屏障:對於這樣的語句Store1; StoreStore; Store2,在Store2及後續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。
  • LoadStore屏障:對於這樣的語句Load1; LoadStore; Store2,在Store2及後續寫入操作被刷出前,保證Load1要讀取的資料被讀取完畢。
  • StoreLoad屏障:對於這樣的語句Store1; StoreLoad; Load2,在Load2及後續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。它的開銷是四種屏障中最大的。 在大多數處理器的實現中,這個屏障是個萬能屏障,兼具其它三種記憶體屏障的功能。

happen-before 原子

  • 單執行緒happen-before原則:在同一個執行緒中,書寫在前面的操作happen-before後面的操作。 鎖的happen-before原則:同一個鎖的unlock操作happen-before此鎖的lock操作。
  • volatile的happen-before原則:對一個volatile變數的寫操作happen-before對此變數的任意操作(當然也包括寫操作了)。
  • happen-before的傳遞性原則:如果A操作 happen-before B操作,B操作happen-before C操作,那麼A操作happen-before C操作。
  • 執行緒啟動的happen-before原則:同一個執行緒的start方法happen-before此執行緒的其它方法。
  • 執行緒中斷的happen-before原則 :對執行緒interrupt方法的呼叫happen-before被中斷執行緒的檢測到中斷髮送的程式碼。
  • 執行緒終結的happen-before原則: 執行緒中的所有操作都happen-before執行緒的終止檢測。
  • 物件建立的happen-before原則: 一個物件的初始化完成先於他的finalize方法呼叫

15 JVM主要引數

 

-Xmx3550m: 最大堆大小為3550m。

-Xms3550m: 設定初始堆大小為3550m。

-Xmn2g: 設定年輕代大小為2g。

-Xss128k: 每個執行緒的堆疊大小為128k。

-XX:MaxPermSize: 設定持久代大小為16m

-XX:NewRatio=4: 設定年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。

-XX:SurvivorRatio=4: 設定年輕代中Eden區與Survivor區的大小比值。設定為4,則兩個Survivor區與一個Eden區的比值為2:4,一個Survivor區佔整個年輕代的1/6

-XX:MaxTenuringThreshold=0: 設定垃圾最大年齡。如果設定為0的話,則年輕代物件不經過Survivor區,直接進入年老代。

 

-XX:+UseParallelGC: 選擇垃圾收集器為並行收集器。

-XX:ParallelGCThreads=20: 配置並行收集器的執行緒數

-XX:+UseConcMarkSweepGC: 設定年老代為併發收集。

-XX:CMSFullGCsBeforeCompaction:由於併發收集器不對記憶體空間進行壓縮、整理,所以執行一段時間以後會產生“碎片”,使得執行效率降低。此值設定執行多少次GC以後對記憶體空間進行壓縮、整理。

-XX:+UseCMSCompactAtFullCollection: 開啟對年老代的壓縮。可能會影響效能,但是可以消除碎片

 

-XX:+PrintGC 輸出形式:

[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]

-XX:+PrintGCDetails 輸出形式:

[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs

 

16 怎麼列印出執行緒棧資訊

  • 輸入jps,獲得程式號。
  • top -Hp pid 獲取本程式中所有執行緒的CPU耗時效能
  • jstack pid命令檢視當前java程式的堆疊狀態
  • 或者 jstack -l > /tmp/output.txt 把堆疊資訊打到一個txt檔案。
  • 可以使用fastthread 堆疊定位,fastthread.io/

 

相關文章