JVM監控篇(一)- 先了解一下JVM

1- JVM是什麼

  • Java(Java Virtual Machine)虛擬機,是Java運行環境的一部分。

1.1 JVM由以下幾個部分構成

在這裏插入圖片描述

類加載器(Class Loader)

負責加載class文件,將class文件字節碼內容加載到內存中,並將這些內容轉換成方法區中的運行時數據結構。ClassLoader只負責class文件的加載,至於它是否可以運行,則由Execution Engine 決定。

運行時數據區(Runtime Data Area)

  • 所有的數據和程序都是在運行數據區存放。由下面五個部分構成:
  • java棧

1)即棧內存,是Java程序的運行區,是在線程創建時創建,它的生命期和線程的生命期一致,線程結束棧內存也就釋放,對於棧來說不存在垃圾回收問題,只要線程一結束,該棧就“消失”了。

2)8種基本類型的變量+對象的引用變量+實例方法都是在函數的棧內存中分配。

一個JVM實例只存在一個堆內存,堆內存的大小是可以調節的。類加載器讀取了類文件後,需要把類、方法、常變量放到堆內存中,以方便執行器執行。堆內存包含3個區:

1)永久存儲區:
又叫永久代。永久存儲區是一個常駐內存區域,用於存放JDK自身所攜帶的Class,Interface的元數據,也就是說它存儲的是運行環境必須的類信息,被裝載進此區域的數據是不會被垃圾回收器回收掉的,關閉JVM纔會釋放此區域所佔用的內存。Java8以前叫永久代,java8以後改名爲元空間。元空間與永久代之間最大的區別在於:永久帶使用的JVM的堆內存,但是java8以後的元空間並不在虛擬機中而是使用本機物理內存。

2)新生區:
又叫新生代。新生區是類的誕生、成長、消亡的區域,一個類在這裏產生,應用,最後被垃圾回收器收集,結束生命。新生區又分爲兩部分:伊甸區(Eden space)和倖存者區(Survivor pace),所有的類都是在伊甸區被new出來的。倖存區有兩個: 0區(Survivor 0 space)和1區(Survivor 1 space)。當伊甸園的空間用完時,程序又需要創建對象,JVM的垃圾回收器將對伊甸園區進行垃圾回收,將伊甸園區中的不再被其他對象所引用的對象進行銷燬。然後將伊甸園中的剩餘對象移動到倖存0區。若倖存0區也滿了,再對該區進行垃圾回收,然後移動到1區。那如果1區也滿了呢?再移動到養老區。

3)養老區:
也叫老年代。用於保存從新生區篩選出來的JAVA對象,一般池對象都在這個區域活躍。

在這裏插入圖片描述

  • 方法區

方法區是被所有線程共享的,該區域保存所有字段和方法字節碼,以及一些特殊方法如構造函數,接口代碼也在此定義。

  • 程序計數器

每個線程都有一個程序計數器,就是一個指針,指向方法區中的方法字節碼(用來存儲指向下一條指令的地址),由執行引擎讀取下一條指令。

  • 本地方法棧

與Java棧基本類似,區別在於Java棧爲虛擬機執行的java方法服務,而本地方法棧則是爲Native方法服務。

執行引擎(Execution Engine)

執行引擎負責解釋命令,提交操作系統執行。

本地接口(Native Interface)

本地接口的作用是融合不同的編程語言爲 Java 所用,它的初衷是融合 C/C++程序,Java 誕生的時候是 C/C++橫行的時候,要想立足,必須有調用 C/C++程序,於是就在內存中專門開闢了一塊區域處理標記爲native的代碼,它的具體做法是 Native Method Stack中登記 native方法,在Execution Engine 執行時加載native libraies。

本地方法庫(Native Libraies)

負責登記native方法,以便在Execution Engine 執行時加載本地方法庫。

垃圾回收子系統

垃圾回收器可以對方法區、java堆和直接內存進行回收。其中,java堆是垃圾收集器的工作重點。和C/C++不同,java中所有的對象空間釋放都是隱式的,也就是說,java中沒有類似free()或者delete()這樣的函數釋放指定的內存區域。對於不再使用的垃圾對象,垃圾回收系統會在後臺默默工作,默默查找、標識並釋放垃圾對象,完成包括java堆、方法區和直接內存中的全自動化管理。

1.2 GC垃圾回收

如何判斷哪些是垃圾

  • 引用計數法

爲每一個創建的對象分配一個引用計數器,用來存儲該對象被引用的個數。當該個數爲零,意味着沒有人再使用這個對象,可以認爲“對象死亡”。每當有一個地方去引用它時候,引用計數器就增加1。

  • 可達性分析

基本思路是把所有引用的對象想象成一棵樹,從樹的根結點 GC Roots (如Java棧中的本地變量,方法區中靜態屬性引用的對象等等)出發,持續遍歷找出所有連接的樹枝對象,這些對象則被稱爲“可達”對象,或稱“存活”對象。不能到達的則被可回收對象。

在這裏插入圖片描述

垃圾回收算法

  • 標記-清除算法

在標記階段首先通過根節點(GC Roots),標記所有從根節點開始的對象,未被標記的對象就是未被引用的垃圾對象。然後,在清除階段,清除所有未被標記的對象。

在這裏插入圖片描述

優點:存活對象較多的情況下比較高效,適用於年老代(即舊生代)

缺點:容易產生內存碎片,且需要掃描整個空間兩次

在這裏插入圖片描述

  • 複製算法

從根集合節點進行掃描,標記出所有的存活對象,並將這些存活的對象複製到一塊兒新的內存(圖中下邊的那一塊兒內存)上去,之後將原來的那一塊兒內存(圖中上邊的那一塊兒內存)全部回收掉。現在的商業虛擬機都採用這種收集算法來回收新生代。

在這裏插入圖片描述

優點:存活對象較少的情況下比較高效,掃描了整個空間一次(標記存活對象並複製移動)。適用於年輕代(即新生代):基本上98%的對象是"朝生夕死"的,存活下來的會很少

缺點:需要一塊兒空的內存空間,需要複製移動對象

在這裏插入圖片描述

  • 標記-整理算法

標記-壓縮算法是一種老年代的回收算法,它在標記-清除算法的基礎上做了一些優化。

首先也需要從根節點開始對所有可達對象做一次標記,但之後,它並不簡單地清理未標記的對象,而是將所有的存活對象壓縮到內存的一端。之後,清理邊界外所有的空間。這種方法既避免了碎片的產生,又不需要兩塊相同的內存空間,因此,其性價比比較高。

在這裏插入圖片描述

標記整理算法解決了內存碎片的問題,也規避了複製算法只能利用一半內存區域的弊端。標記整理算法對內存變動更頻繁,需要整理所有存活對象的引用地址,在效率上比複製算法要差很多。一般是把Java堆分爲新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集算法。

在這裏插入圖片描述

  • 分代收集算法

分代收集算法就是目前虛擬機使用的回收算法,它解決了標記整理不適用於老年代的問題,將內存分爲各個年代。一般情況下將堆區劃分爲老年代(Tenured Generation)和新生代(Young Generation),在堆區之外還有一個代就是永久代(Permanet Generation)。

在不同年代使用不同的算法,從而使用最合適的算法,新生代存活率低,可以使用複製算法。而老年代對象存活率搞,沒有額外空間對它進行分配擔保,所以只能使用標記清除或者標記整理算法。

垃圾回收機制

在這裏插入圖片描述

不設置垃圾回收機制時,默認使用的一般是+UseParallelGC ,即Parallel Scavenge收集器和Serial old收集器( java -XX:+PrintCommandLineFlags -version 可以查看默認使用的垃圾收集器)

  • Serial收集器

Serial(串行)收集器收集器是最基本、歷史最悠久的垃圾收集器了(新生代採用複製算法,老年代採用標誌整理算法)。

進行垃圾收集時,必須暫停所有工作線程,直到完成。

單線程收集器,主要針對新生代

  • ParNew收集器

ParNew收集器其實就是Serial收集器的多線程版本,除了使用多線程進行垃圾收集外,其餘行爲和Serial收集器完全一樣。

除了Serial收集器外,目前只有它能與CMS收集器配合工作。

參數設置 +UseParNewGC
//指定垃圾收集的線程數量,ParNew默認開啓的收集線程與CPU的數量相:
“-XX:ParallelGCThreads”

  • CMS(Concurrent Mark Sweep)收集器

CMS收集器是一種以獲取最短回收停頓時間爲目標的收集器。它非常適合在注重用戶體驗的應用上使用。

針對老年代,基於"標記-清除"算法(不進行壓縮操作,會產生內存碎片),併發收集、低停頓,需要更多的內存。

對CPU資源比較敏感(併發程序的特點)。在併發階段,它雖然不會導致用戶線程停頓,但會因爲佔用了一部分線程(或者說CPU資源)而導致應用程序變慢,總吞吐量會降低。

CMS所需要的內存空間比其他垃圾收集器大; 可以使用"-XX:CMSInitiatingOccupancyFraction",設置CMS預留老年代內存空間。

參數設置: +UseConcMarkSweepGC

此外,jdk9開始,廢棄了CMS,但還能用;jdk14直接刪除了CMS。

  • Parallel Scavenge收集器

屬於新生代收集器,使用複製算法,且是並行的多線程收集器。主要適用於不需要太多交互的任務。

關注點是吞吐量,即減少垃圾收集時間,讓用戶代碼獲得更長的運行時間;

吞吐量=運行用戶代碼的時間/(運行用戶代碼時間+垃圾收集時間。比如,虛擬機總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。

  • Serial Old收集器

Serial收集器的老年代版本,它同樣是一個單線程收集器。採用"標記-整理"算法,主要用於Client模式。

用於server模式的話,主要有兩大用途:一種用途是在JDK1.5以及以前的版本中與Parallel Scavenge收集器搭配使用,另一種用途是作爲CMS收集器的後備方案。

  • Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法。

  • G1收集器

G1主要針對多核CPU以及大容量內存的機器,是jdk9以後的默認回收器,取代了CMS、Parallel + Parallel Old組合,被稱爲全功能的垃圾收集器。

jdk8中不是默認的,使用參數設置 -XX:+UseG1GC。

並行性:G1回收期間,可以用多個GC線程同時工作,有效利用多核計算能力,此時用戶線程被暫停

併發性:G1有與應用程序交替執行的能力,部分工作可以和應用程序同時執行,因此在GC時不會完全阻塞應用程序

G1回收器可以採用應用線程承擔後臺運行的GC工作,也就是當GC線程處理速度慢時,系統會調用應用程序線程幫助加速GC過程

G1依舊會區分年輕代和老年代,年輕代依舊分爲伊甸園區和倖存者區。但從堆結構上看,它不要求整個伊甸園區、整個年輕代或老年代都是連續的,也不堅持固定大小和固定數量

G1將堆空間分爲若干區域,這些區域中包含了邏輯上的老年代和年輕代,回收以區域爲單位。區域之間採用複製算法,整體上可看成是採用了標記-壓縮算法。

在小內存應用上CMS的表現大概率會優於G1,而G1在大內存應用上則會發揮其優勢。平衡點在6-8GB之間。

1.3 JDK、JRE、JVM三者的關係是什麼

JDK是Java程序員常用的開發包、目的就是用來編譯和調試Java程序的。

JRE是指Java運行環境,也就是我們的寫好的程序必須在JRE才能夠運行。

JVM負責將字節碼解釋成爲特定的機器碼進行運行。

此外,在運行過程中,Java源程序需要通過編譯器編譯爲.class文件,否則JVM不認識。

2- 爲什麼監控JVM

1)爲排查問題提供科學可靠的線索
2)結合jvm監控數據可以對應用程序功能及性能進行一定程度的優化
3)監控哪些數據:
GC的次數,一次GC所需要的時間
GC各個時代的數據
進程佔用的CPU
進程佔用的內存
堆內存
線程數
類加載情況
業務名

3- JVM監控方案調研

  • 如何採集數據 —— 如何存儲歷史數據 —— 如何展示數據 —— 如何告警

1)傳統的監控軟件,如zabbix也可以通過配置 zabbix java gateway 對jvm進行監控,但是在k8s集羣場景下就不如prometheus靈活好用。

2)Open-Falcon也可以通過MxBeans採集jvm數據達到監控的效果,但是該軟件功能並不完善,社區運營也相對欠缺。

3)pinpoint+HBase方案,除了能監控jvm,還可以做程序調用的鏈路追蹤,可以快速啓動。

4)skywalking+elasticsearch方案,除了能監控jvm,還可以做程序調用的鏈路追蹤。

5)當下流行的prometheus既可以在傳統架構下對jvm進行監控,也能夠在k8s集羣場景下完成jvm監控作業。還可以結合influxdb+grafana對歷史監控數據進行展示。

接下來,詳細記錄一下【prometheus+jmx】 對jvm進行監控的方案~

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