jvm 從GC日誌分析堆內存

  在前面的文章中,我們只設置了整個堆的內存大小。但是我們知道,堆又分爲了新生代,年老代。他們之間的內存怎麼分配呢?新生代又分爲Eden和Survivor,他們的比例大小能改變嗎?其實這些都是可控的,以前沒有講到是因爲就算講了也只是講講而已,看不到實質性的東西。因此這章我們通過分析GC日誌來一步步講解如何細化設置堆內存。

  首先我們來了解幾個相關的參數:

  • -XX:+PrintGCDetails:用於告訴虛擬機回收垃圾的時候順便打印日誌
  • -Xloggc:路徑 :將打印出來的日誌信息保存至指定的路徑
  • -Xmn10M:設置新生代的內存大小
  • -XX:SurvivorRatio=8:1調整Eden和Survivor的比例爲8:1
      我們還是用前面的代碼例子來講:

      然後用參數-Xms20m -Xmx20m -Xmn10
    -XX:+PrintGCDetails -Xloggc:d:\gc1.log啓動。表示給堆分配20M,給新生代分配10M,打印GC日誌,並將其輸出至D盤的gc1.log文件中。運行後得到以下日誌,這是第一部分:

      現在我們來分析下每個部分代表的含義:

  • 0.090:就是虛擬機從啓動到現在經歷的時間。

  • GC:指的是停頓類型(留着下一章講)
  • PSYoungGen:發生GC的區域,這裏指的是年輕代。根據收集器的種類而定。
  • 7284K->1016K(9216K):該區域GC前當前區域所使用的容量–>該區域GC後已使用的容量(該區域的總容量),也就是新生代的容量。
  • 7284K->6139K(19456K):整個堆GC前當前區域所使用的容量–>整個堆GC後已使用的容量(整個堆的總容量)。
  • 0.0078481:這次GC所佔用的時間。

      我們再來看看第二部分:
    堆分析

      看圖畫紅線部分,表示當前的堆中新生代可用內存的大小(一個eden和一個Survivor視爲可用內存),紅色框下面則是年老區的大小,加上一共是20m,符合我們所設置的。

      紅色框的部分則是新生代中eden區和兩個Survivor區的大小,可以看出他們的比例是8:1,如果設置爲-XX:SurvivorRatio=3的話,結果如下

      到這裏以上幾個參數的作用以及分析就講完啦,小夥伴們可以打開自己的工具試一試,感受一下。以後碰到了內存泄漏或者內存不足的話就可以直接查看日誌來進行分析調優了!

Stop The World

什麼是停頓類型!經過前幾章的學習,我們知道垃圾回收首先是要經過標記的。對象被標記後就會根據不同的區域採用不同的收集方法。看上去很完美的一件事情,其實並不然。

  大家有沒有想過一件事情,當虛擬機完成兩次標記後,便確認了可以回收的對象。但是,垃圾回收並不會阻塞我們程序的線程,他是與當前程序併發執行的。所以問題就出在這裏,當GC線程標記好了一個對象的時候,此時我們程序的線程又將該對象重新加入了“關係網”中,當執行二次標記的時候,該對象也沒有重寫finalize()方法,因此回收的時候就會回收這個不該回收的對象。

  虛擬機的解決方法就是在一些特定指令位置設置一些“安全點”,當程序運行到這些“安全點”的時候就會暫停所有當前運行的線程(Stop The World 所以叫STW),暫停後再找到“GC Roots”進行關係的組建,進而執行標記和清除。

  這些特定的指令位置主要在:

  • 循環的末尾
  • 方法臨返回前 / 調用方法的call指令後
  • 可能拋異常的位置

      找到“GC Roots”也是要花很長的時間,然而這裏又有新的解決方法,就是通過採用一個OopMap的數據結構來記錄系統中存活的“GC Roots”,在類加載完成的時候,虛擬機就把對象內什麼偏移量上是什麼類型的數據計算出來保存在OopMap,通過解釋OopMap就可以找到堆中的對象,這些對象就是GC Roots。而不需要一個一個的去判斷某個內存位置的值是不是引用。這種方式也叫準確式GC。

      回到最開始的問題,那個停頓類型就是剛剛所說的STW,至於有GC和Full GC之分,還有Full GC (System)。個人認爲主要是Full GC時STW的時間相對GC來說時間很長,因爲Full GC針對整個堆以及永久代的,因此整個GC的範圍大大增加;還有就是他的回收算法就是我們之前說過的“標記–清除–整理”,這裏也會損耗一定的時間。所以我們在優化JVM的時候,減少Full GC的次數也是經常用到的辦法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章