JVM+GC 面試題

MinorGC 的過程(複製算法)

複製->清空->互換java

  1. Eden、SurvivorFrom 複製到 SurvivorTo,年齡+1
    • 首先,Eden 區滿的時候回出發第一次 GC,把存活的對象拷貝到 SurvivorFrom 區,當 Eden 區再次出發 GC 的時候會掃描 Eden 區和 From 區,對這兩個區域進行垃圾回收。
    • 通過此次回收後還存活的對象,則直接複製到 To 區,同時將這些對象的年齡+1。若是有對象年齡已經達到了老年的標準,複製到老年代。
  2. 清空 Eden、SurvivorFrom
    • 清空 Eden 和 SurvivorFrom 中的對象
  3. SurvivorTo 和 SurvivorFrom 互換
    • SurvivorTo 和 SurvivorFrom 互換,原來的 SurvivorTo 稱爲下一次 GC 時的 SurvivorFrom 區。
    • 部分對象會在 From 和 To 區域中來回複製,如此交換15次(JVM參數 MaxTenuringThreshold決定,默認參數是15),最終若是仍是存活,就存入老年代。

GC Roots

垃圾:內存中已經再也不被使用到的空間就是垃圾。linux

可達性分析算法:基本思路是經過一系列名爲 GC Roots 的對象做爲起始點,從這些對象開始向下搜索,若是一個對象到 GC Roots 沒有任何引用鏈相連,說明此對象不可用。ios

GC Roots 是一組必須活躍的引用。算法

能夠做爲 GC Roots 的對象的有:緩存

  1. 虛擬機棧(棧幀中的局部變量區,也稱局部變量表)中引用的對象。
  2. 方法區中類靜態屬性引用的對象。
  3. 方法區中常量引用的對象。
  4. 本地方法棧中 JNI ( Native 方法 )引用的對象。

JVM 參數

JVM參數類型

1. 標配參數:在 jdk 各個版本之間保持穩定bash

- java -version
- java -help
- java -showversion
複製代碼

2. X參數服務器

-Xint:解釋執行
-Xcomp: 第一次使用就編譯成本地代碼
-Xmixed: 默認,先編譯後執行
複製代碼

3. XX參數網絡

  1. Boolean 類型多線程

    • -XX:+/- 屬性
    • +表示開啓,-表示關閉
    • 示例 以是否打印GC收集細節爲例:
      • 首先使用 jps -l 查看當前運行進程 id併發

      • 而後使用 jinfo -flag PrintGCDetails 進程id來查看

      • 若是開啓,結果應該是-XX:+PrintGCDetails,不然-XX:-PrintGCDetails

  2. KV類型

    • -XX:key=value
    • 以元空間大小 MetaspaceSize爲例:一樣使用上述的方法,使用jinfo -flag MetaspaceSize 進程id來查看。
    • -Xms等價於-XX:InitialHeapSize-Xmx等價於-XX:MaxHeapSize

如何查看JVM參數默認值

  1. 使用java -XX:+PrintFlagsInital查看jvm初始參數
  2. 使用java -XX:+PrintFlagsFinal -version查看修改更新的參數。參數中使用 = 說明是未修改的參數,使用:=說明是人爲或jvm修改過的參數。
  3. 使用java -XX:+PrintCommandLineFlags

JVM經常使用參數

JDK 1.8 以後永久代取消了,由元空間取代。新生代由 Eden + SurvivorFrom + SurvivorTo 組成,大小用-Xmn設置。JVM 堆由新生代和老年代共同組成,由-Xms-Xmx設置大小。元空間再也不屬於堆的一部分。

元空間和永久代的區別在於:永久代使用 JVM 的堆內存,元空間不在虛擬機中而是使用本機物理內存

默認狀況下,元空間的大小僅受本地內存限制,類的元數據放入 native memory,字符串池和類的靜態變量放入 java 堆中,這樣能夠加載多少類的元數據就再也不由 MaxPermSize 控制,而由系統的實際可用空間來控制。

  1. -Xms
    • 等價於 -XX:InitialHeapSize
    • 初始大小內存,默認爲物理內存1/64
  2. -Xmx
    • 等價於 -XX:MaxHeapSize
    • 最大分配內存,默認爲物理內存 1/4
  3. -Xss
    • 等價於-XX:ThreadStackSize
    • 設置單個線程棧的大小
    • Linux 默認爲 1024KB
    • OS X 默認爲 1024KB
    • Windows 的默認值取決於虛擬內存
  4. -Xmn
    • 設置新生代大小:默認是堆空間1/3
  5. -XX:MetaspaceSize
    • 元空間和永久代相似,都是對方法區的實現。元空間不在虛擬機中,而是使用本地內存。默認狀況下,元空間的大小僅受本地內存限制。
    • 可是這並不意味元空間不會有OOM,由於其默認內存大小是有限的
  6. -XX:+PrintGCDetails
    • 輸出詳細GC手機日誌信息
[GC [PSYoungGen: 2048K -> 496K(2560K)] 2048K->777K(9728k)]
複製代碼
  • [GC [PSYoungGen:表示GC類型
  • 2048K表示 YoungGC 前新生代內存佔用
  • 496K表示 YoungGC 後新生代內存佔用
  • (2560K)表示新生代總大小
  • 2048K 表示 YoungGC 前 JVM 堆內存佔用
  • 777K 表示 YoungGC 後 JVM 堆內存佔用
  • 9728K 表示 JVM 堆總大小
[Full GC (System) [PSYoungGen:3408K->0K(296688k)] [PSOldGen:0K->3363K(682688K)]3408K->3363K(981376K)[Metaspace:10638K->10638K(131072K)]]
複製代碼

能夠看到 Full GC 中,分別有 新生代、老年代、堆總內存以及元空間各自的GC前、GC後以及總大小。

  1. -XX:SurvivorRatio
    • 設置新生代中 Eden 和 S0/S1 的比例
    • 默認爲8,表示三者比例爲 8:1:1
  2. -XX:NewRatio
    • 設置新生代和老年代在堆中的佔比
    • 設置的值就是設置老年代比新生代的比值
  3. -XX:MaxTenuringThreshold
    • 設置垃圾最大年齡,默認是15
    • java8 中能設置的閾值是 0 ~ 15
    • 設置爲較小值,適用於老年代比較多的應用。
    • 設置爲較大值,能夠增長對象在新生代存活的時間,增長在新生代回收的機率。

四種引用

SoftReference, WeakReference 和 PhantomReference 三個類都繼承自 Reference 類,而 Reference 類又是 Object 類的子類。

強引用

當內存不足,JVM 開始垃圾回收,可是強引用的對象,即便出現了 OOM 也不會對該對象進行回收。把一個對象賦給一個引用變量,這個引用變量就是一個強引用,只要還有強引用指向一個對象,該對象就處於可達狀態,不會被 JVM 回收。

軟引用

對於只有軟引用的對象來講,系統充足時不會被回收,系統內存不足時會被回收。軟引用一般用在對內存敏感的程序中,好比高速緩存就用到軟引用。

適用場景(緩存):假設有一個應用大量讀取本地圖片,每次讀取都會IO影響性能,一次性所有加載到內存可能會內存溢出。

可使用 HashMap 保存圖片的路徑和圖片對象關聯的軟引用之間的映射關係,內存不足時, JVM 會自動回收這些緩存圖片對象所佔用的空間,有效避免了OOM的問題。

Map<String, SoftReference<Bitmap>> imageCache = new HashMap<>();
複製代碼

弱引用

只要GC一運行,無論JVM的內存空間是否足夠,都會回收該對象佔用的內存。

WeakHashMap

相比於 HashMap, WeakHashMap 中的元素,當 key 被回收後,Map 中相應的鍵值對將再也不存在。

public class WeakHashMapDemo {
    public static void main(String[] args) {
        myHashMap();
        System.out.println("======================");
        myWeakHashMap();
    }

    private static void myWeakHashMap() {
        WeakHashMap<Integer,String> map=new WeakHashMap<>();

        Integer k=new Integer(1);
        String v="str";

        map.put(k,v);
        System.out.println(map);

        k=null;
        System.out.println(map);

        System.gc();
        System.out.println(map);
    }

    private static void myHashMap() {
        HashMap<Integer,String> map=new HashMap<>();

        Integer k=new Integer(1);
        String v="str";

        map.put(k,v);
        System.out.println(map);

        k=null;
        System.out.println(map);

        System.gc();
        System.out.println(map);
    }
}
複製代碼

運行結果

{1=str}
{1=str}
{1=str}
======================
{1=str}
{1=str}
{}
複製代碼

引用隊列

public class ReferenceQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        Object o1=new Object();
        ReferenceQueue<Object> referenceQueue=new ReferenceQueue<>();
        WeakReference<Object> weakReference=new WeakReference<>(o1,referenceQueue);
        System.out.println(o1);
        System.out.println(weakReference.get());
        System.out.println(referenceQueue.poll());

        System.out.println();
        o1=null;
        System.gc();
        Thread.sleep(1000);

        System.out.println(o1);
        System.out.println(weakReference.get());
        System.out.println(referenceQueue.poll());

    }
}
複製代碼
java.lang.Object@1b6d3586
java.lang.Object@1b6d3586
null

null
null
java.lang.ref.WeakReference@4554617c
複製代碼

弱引用、軟引用、虛引用在 gc 以前,會被放到引用隊列中。

虛引用

虛引用顧名思義就是形同虛設,它並不會決定對象的聲明週期。若是一個對象僅僅持有虛引用,就和沒有引用同樣,隨時可能被回收。虛引用不能單獨使用,也不能經過它訪問對象,必須和引用隊列聯合使用。

虛引用的主要做用是跟蹤對象被垃圾回收的狀態,僅僅是提供了一種確保對象被 finalize 之後,作某些事情的機制。其意義在於說明一個對象已經進入 finalization 階段,能夠被回收,用來實現比 finalization 機制更加靈活的回收操做。

設置虛引用的惟一目的,就是在這個對象被回收的時候收到一個系統通知或者後序添加進一步的處理。

public class PhantomReferenceDemo {
    public static void main(String[] args) throws InterruptedException {
        Object o1=new Object();
        ReferenceQueue<Object> referenceQueue=new ReferenceQueue<>();
        PhantomReference<Object> phantomReference=new PhantomReference<>(o1,referenceQueue);

        System.out.println(o1);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());

        System.out.println();
        o1=null;
        System.gc();
        Thread.sleep(1000);

        System.out.println(o1);
        System.out.println(phantomReference.get());
        System.out.println(referenceQueue.poll());
    }
}
複製代碼

運行結果:

java.lang.Object@1b6d3586
null
null

null
null
java.lang.ref.PhantomReference@4554617c
複製代碼

能夠看到,虛引用的 get() 返回值永遠是 null。

總結

在建立引用的時候能夠指定關聯的隊列,當 GC 釋放對象內存的時候,會將引用加入到引用隊列。若是程序發現某個虛引用已經被加入到引用隊列,那麼就能夠在所引用的對象的內存被回收以前採起必要行動,至關於一種通知機制。經過這種方式,JVM容許咱們在對象被銷燬後,作一些咱們想作的事情。

OOM

java.lang.StackOverflowError

當遞歸調用中,棧的調用深度過深,致使棧溢出錯誤。 繼承關係以下:

Throwable -> Error -> VirtualMachineError -> StackoverflowError
複製代碼

java.lang.OutOfMemoryError: Java heap space

當對象過多,或者存在大對象等狀況下,致使堆內容超過堆的最大尺寸,致使堆溢出。

java.lang.OutOfMemoryError: GC overhead limit exceeded

GC回收時間過程會拋出此類異常。過長的定義是超過 98% 的時間用來作 GC 而且回收了不到 2% 的堆內存。連續屢次 GC 都只回收了不到 2% 的極端狀況下才會拋出。

假如不拋出 GC overhead limit 錯誤會發生的狀況: GC 清理出的內存很快再次被填滿,破事 GC 再次執行,造成惡性循環,CPU 使用率一直是 100%,可是 GC 卻沒有任何成果。

public class GCOverheadDemo {
    public static void main(String[] args) {
        int i = 0;
        List<String> list = new ArrayList<>();
        
        while (true) {
            list.add(String.valueOf(++i).intern());
        }
    }
}
複製代碼

java.lang.OutOfMemoryError: Direct buffer memory

寫 NIO 程序常用 ByteBuffer 來讀取或者寫入數據。它可使用 Native 函數庫直接分配堆外內存,而後經過一個存儲在 Java 堆裏邊的 DirectByteBuffer 對象做爲這塊內存的引用進行操做。這樣在一些場景能顯著提升性能,由於避免了在 Java 堆和 Native 堆中來回複製數據。

ByteBuffer.allocate(capability)是分配JVM堆內存,屬於GC管轄範圍,因爲須要拷貝因此速度相對較慢。

ByteBuffer.allocateDirect(capability)是分配 OS 本地內存,不屬於 GC 管轄範圍,因爲不須要內存拷貝因此速度相對較快。

若是不斷分配本地內存,堆內存不多使用,那麼 JVM 就不須要執行 GC,DirectByteBuffer 對象就不會被回收,致使堆內存充足,可是本地內存已經被用光了。

java.lang.OutOfMemoryError: Unable to create new native thread

在高併發請求服務器時,會出現該異常。

致使的緣由:

  1. 應用建立了太多的線程,一個應用進程建立多個線程,超過了系統承載的極限
  2. 服務器不容許應用程序建立這麼多線程,linux 系統默認單個進程能夠建立的線程數是 1024。

解決辦法:

  1. 想辦法下降建立線程的數量
  2. 確實須要建立不少線程的應用,能夠修改linux服務器配置,擴大其限制

java.lang.OutOfMemoryError: metaspace

MetaSpace 是方法區在 HotSpot 中的實現,它並不在虛擬機內存中而是使用本地內存,也就是類的元數據被存儲在 MetaSpace 的 native memory 中。

其中存放了:虛擬機加載的類的信息,常量池,靜態變量和即時編譯的代碼。

垃圾回收器

垃圾回收器的四種思想

Serial

串行垃圾回收器,爲單線程環境設計且只使用一個線程進行垃圾回收,會暫停全部的用戶線程,因此不適合服務器環境。

過程就是:程序運行->單GC線程->程序運行

Parallel

並行垃圾回收器,多個垃圾收集線程並行工做,此時用戶線程是暫停的,適用於科學計算、大數據處理等弱交互場景。

過程:程序運行->多GC線程並行運行->程序運行

CMS

併發垃圾回收器,用戶線程和GC線程同時執行,不須要停頓用戶線程,適用於對響應時間有要求的場景(強交互)。

過程:初始標記(暫停,單線程)->併發標記(GC線程和用戶線程同時執行)->最終標記(暫停,多GC線程)->清除

G1

G1垃圾回收器將堆內存分割成不一樣的區域(Region)而後併發的對其進行垃圾回收。

查看默認的垃圾回收器

使用java -XX:+PrintCommandLineFlags -version查看默認的垃圾回收器。

在 java8 中,默認的是 -XX:+UseParallelGC

默認垃圾回收器有哪些

java 的 gc 類型有如下幾種:

  • UseSerialGC
  • UseParallelGC
  • UseConcMarkSweepGC
  • UseParNewGC
  • UseParallelOldGC
  • UseG1GC

在 jvm 中的 7 種垃圾回收器中,Serial Old 已經被廢棄,因此在 gc 的源代碼中,有如上六種。

七種垃圾回收器

在新生代的有:

  1. Serial
  2. Parallel Scavenge
  3. ParNew

在老年代的有:

  1. Serial Old
  2. Parallel Compacting
  3. CMS

兩者均可以使用的是 G1。

DefNew: Default New Generation
Tenured: Old
ParNew: Parallel New Generation
PSYoungGen: Parallel Scavenge
ParOldGen: Parallel Old Generation
複製代碼

新生代串行收集器: Serial

一個單線程的收集器,在GC的時候,必須暫停其餘全部的工做線程直到 GC 結束。

在單CPU環境下,沒有線程交互的開銷能夠得到最高的GC效率,所以 Serial 垃圾收集器是 JVM 在 Client 模式下默認的新生代垃圾收集器。

JVM參數:-XX:+UseSerialGC 開啓後,新生代使用 Serial 老年代使用 Serial Old,新生代和老年代都使用串行回收收集器,新生代使用複製算法,老年代使用標記-整理算法。

GC 日誌中,新生代使用DefNew,老年代使用Tenured

新生代並行收集器: ParNew

使用多線程進行垃圾回收,垃圾收集時,會 STW 暫停其餘線程直到 GC 結束。

ParNew 收集器是 Serial 的並行多線程版本,常見的應用場景是配合老年代的 CMS 工做。是不少 jvm 運行在 Server 模式下的 新生代的默認垃圾收集器

JVM 參數:-XX:+UseParNewGC。啓用後,新生代使用 ParNew ,老年代使用 Serial Old,新生代使用複製算法,老年代使用標記-整理算法。 要注意的是, ParNew + Serial Old 已經再也不被推薦。

GC 日誌中,新生代使用ParNew,老年代使用Tenured

新生代並行收集器: Parallel Scavenge

Parallel Scavenge 收集器相似於 ParNew 也是一個新生代垃圾收集器,使用複製算法,是並行的多線程的垃圾收集器,是吞吐量優先的收集器。

它重點關注的是吞吐量(運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間))。高吞吐量意味着高效利用CPU的時間,適用於在後臺運算而不須要太多交互的任務

Parallel Scavenge 和 ParNew 收集器的一個重要區別是自適應調節策略。自適應調節策略是指虛擬機會根據當前系統的運行狀況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或最大的吞吐量。

JVM 參數:-XX:+UseParallelGC,與使用-XX:+UseParallelOldGC效果相同,這兩個收集器默認搭配使用,因此會互相激活。開啓後,新生代使用複製算法,老年代使用標記-整理算法。

啓用該收集器後,GC 日誌中,新生代輸出PSYoungGen,老年代輸出ParOldGen

在 Java 8 中,該收集器爲默認的收集器。

老年代並行收集器 : Parallel Old

Parallel Old 收集器是 Parallel Scavenge 的老年代版本,使用多線程的標記-整理算法,在 JDK 1.6開始提供。

在 JDK 1.6 以前,新生代使用 Parallel Scavenge 只能搭配老年代的 Serial Old,只能保證新生代的吞吐量優先。

在 JDK 1.8 及之後,默認搭配爲 Parallel Scavenge + Parallel Old ,保證了總體的吞吐量優先。

JVM 參數:-XX:+UseParallelOldGC ,開始後,新生代使用 Parallel Scavenge,老年代使用 Parallel Old。

啓用該收集器後,GC 日誌中,新生代輸出PSYoungGen,老年代輸出ParOldGen

老年代併發標記清除收集器:CMS

CMS(Councurrent Mark Sweep)是一種以獲取最短回收停頓時間爲目標的收集器,適合應用在互聯網或B/S系統的服務器上,這類應用中是服務器的相應速度,但願系統停頓時間最短。CMS 適合堆內存大、CPU 核數多的服務器端應用。

JVM 參數:-XX:+UseConcMarkSweepGC,開啓該參數後會自動開啓-XX:+UseParNewGC。使用 ParNew (新生代) + CMS + Serail Old 的收集器組合,其中 Serial Old 做爲 CMS 出錯的後備收集器。

  1. 初始標記:標記 GC Roots 直接關聯的對象,速度很快,須要 STW。
  2. 併發標記:進行 GC Roots 跟蹤的過程,和用戶線程一塊兒工做,不須要暫停工做線程,標記所有對象。
  3. 從新標記:修正在併發標記期間,由於用戶程序繼續運行致使標記產生變更的對象的記錄,須要STW。
  4. 併發清除:清除 GC Roots 不可達對象,和用戶線程一塊兒工做,不須要暫停工做線程。

因爲耗時最長的併發標記和併發清除過程當中,GC 線程和用戶一塊兒併發工做,因此整體上看 CMS 的 GC 和用戶線程一塊兒併發執行。

優勢:併發收集、停頓低。

缺點:

  • 併發執行,對CPU資源壓力大: 併發執行, CMS 在收集時會增長對堆內存的佔用。所以 CMS 必需要在老年代堆內存用盡以前完成垃圾回收,不然 CMS 回收失敗時,將觸發擔保機制, Serial Old 會以 STW 的方式進行 GC ,形成較大停頓時間
  • 使用標記清除算法致使大量碎片

老年代串行收集器:Serial Old

Serial Old 是 Serial 收集器的老年代版本,是個單線程收集器,使用標記-整理算法,是 Client 默認的老年代收集器。

在 Server 模式下:

  • 在 JDK 1.5 以前與新生代 Parallel Scavenge 搭配。
  • 做爲老年代使用 CMS 收集器的後備垃圾收集方案。

如何選擇垃圾收集器

  • 單CPU或小內存、單機程序:-XX:+UseSerailGC
  • 多CPU,須要最大吞吐量,如後臺計算型應用:-XX:+UseParallelGC-XX:+UseParallelOldGC
  • 多CPU,追求低停頓時間,須要快速響應的應用:-XX:+UseConcMarkSweepGC
參數 新生代垃圾收集器 新生代算法 老年代垃圾收集器 老年代算法
-XX:+UseSerialGC SerialGC 複製 SerailOldGC 標記整理
-XX:+UseParNewGC ParNew 複製 SerailOldGC 標記整理
-XX:+UseParallelGC/-XX:+UseParallelOldGC Parallel Scavenge 複製 Parallel Old 標記整理
-XX:+UseConcMarkSweepGC ParNew 複製 CMS+Serial Old 標記清除

G1 收集器

garbage-first heap+metaspace

G1之前收集器的特色

  1. 年輕代和老年代是各自獨立且連續的內存塊
  2. 年輕代使用 eden + s0 + s1 的複製算法
  3. 老年代收集必須掃描整個老年代區域
  4. 都以儘量少而快速執行 GC 爲設計原則

G1 介紹

G1 是一種服務器端的垃圾收集器,應用在多處理器和大容量內存環境中,在實現高吞吐量的同時,儘量的知足垃圾收集暫停時間的要求。而且具備以下特性:

  • 像 CMS 收集器同樣,能與應用程序線程併發執行
  • 整理空閒空間更快
  • 須要更多的時間預測 GC 停頓時間
  • 不但願犧牲大量的吞吐性能
  • 不須要更大的 Java Heap

G1 的設計目標是取代 CMS 收集器,同 CMS 相比:

  • G1 有整理內存過程,不會產生不少內存碎片
  • G1 的 STW 更可控,在停頓時間上添加了預測機制,用戶能夠指按期望停頓時間

G1 的主要改變是 Eden, Survivor 和 Tenured 等內存區域再也不是連續的,而是變成了一個個大小同樣的 region。

G1 的特色

  1. G1 能充分利用多 CPU 的硬件優點,儘可能縮短 STW
  2. G1 總體採用標記整理算法,局部經過複製算法,不會產生內存碎片
  3. G1 把內存劃分爲多個獨立的 Region
  4. G1 邏輯上保留了新生代和老年代,可是再也不是物理隔離的,而是一部分 Region 的集合(而且不要求 Region 是連續的),會採用不一樣的 GC 方式處理不一樣的區域。
  5. G1 只有邏輯上的分代概念,每一個分區均可能隨着 G1 的運行在不一樣代之間切換。

Region

將堆劃分紅了 Region,避免了全內存區域的 GC 操做。G1 並不要求對象的存儲是物理上連續的,只須要邏輯上連續便可,每一個分區能夠按需在新生代和老年代之間切換。

每一個 Region 大小範圍在 1MB-32MB,最多能夠設置 2048 個區域(默認也是2048),所以能支持的最大內存爲 64 GB。

G1 仍然屬於分代收集器。這些 Region 的一部分包含新生代,新生代的垃圾(Eden)收集採用 STW 的方式,將存活對象拷貝到老年代或者 Survivor 空間。Region 的另外一部分屬於老年代, G1 經過將對象從一個區域複製到另一個區域,完成清理工做,所以不會有 CMS 內存碎片問題的存在了。

在 G1 中,還有一種特殊的區域,稱爲 Humongous 區域,若是一個對象佔用的空間超過了 Region 的 50% 以上,就被認爲是巨型對象,會直接分配在老年代。G1 劃分了一個 Humongous 區,用來存放巨型對象。若是一個 Humongous Region 裝不下一個巨型對象,G1會尋找連續的 Humongous Region 來存儲,爲了能找到連續的 H 區,有時候不得不啓動 Full GC。

回收步驟

Young GC: 針對 Eden 區進行收集, Eden 區耗盡後被處罰,主要是小區域收集 + 造成連續的內存塊,避免內存碎片。

  • Eden 區的數據移動到 Survivor 區,若是 Survivor 區空間不夠,晉升到 Old 區
  • Survivor 區數據移動到新的 Survivor 區,部分晉升到 Old 區
  • GC 結束,應用程序繼續執行

步驟:

  1. 初始標記
  2. 併發標記
  3. 最終標記
  4. 篩選回收:根據時間來進行價值最大化的回收

微服務生產部署和調參優化

生產環境服務器變慢

  1. 查看整機狀況
    • 使用 top 命令查看CPU 佔用率、內存佔用率以及第一行的 load average
    • load average 後的三個數字分別記錄了一分鐘、五分鐘、以及十五分鐘的系統平均負載
    • 還可使用 uptime命令,是系統性能命令的精簡版
  2. 查看 CPU 狀況
    • 使用vmstat -n 2 3表明每2秒採樣一次,一共採樣3次
    • procs 中
      • r表明運行和等待 CPU 時間片的進程數,整個系統的運行隊列不該超過總核數的2倍,不然表明系統壓力過大
      • b表明等待資源如磁盤 I/O,網絡 I/O 的進程數
    • cpu 中
      • us 表明用戶進程消耗 CPU 時間百分比,若是長期大於 50% 要優化程序
      • sy 表明內核進程消耗 CPU 時間百分比
      • us + sy 參考值爲 80% ,若是和大於 80% ,可能存在 CPU 不足
    • 使用mpstat -P ALL 2查看全部 CPU 核信息
    • 使用pidstat -u 1 -p pid查看每一個進程使用 CPU 的用量分解信息
  3. 查看內存信息
    • 使用free -m來查看內存狀況,單位爲 mb
    • 應用程序可用內存/系統物理內存 應該在 20%~70%
    • 使用pidstat -p pid -r 採樣間隔秒數
  4. 查看硬盤信息
    • 使用df -h
  5. 查看磁盤IO
    • 使用iostat -xdk 2 3
      • rkB/s 每秒讀取數據量
      • wkB/s 每秒寫入數據量
      • svctm I/O請求的平均服務時間,單位爲ms
      • util 一秒有百分之多少的時間用於 I/O 操做
    • 使用pidstat -d 採樣間隔秒 -p pid
  6. 查看網絡IO
    • 使用 ifstat 採樣間隔秒

CPU 佔用太高的分析

  1. 使用 top 命令找出 CPU 佔比最高的進程
  2. ps -ef 或者 jps 進一步定位該進程信息
  3. 定位到具體線程或代碼
    • 使用ps -mp pid -o THREAD,tid,time
      • -m 顯示全部線程
      • -p pid 進程使用 cpu的時間
      • -o 後是用戶自定義格式
  4. 將須要的線程 id 轉換爲 16 進制(英文小寫格式)
  5. jstack 進程id | grep tid -A60
相關文章
相關標籤/搜索