性能測試能力提升-JVM GC原理

特斯汀軟件測試 發佈 2022-05-26T03:33:54.985752+00:00

本篇文章,我們將主要介紹JVM GC原理相關的知識:什麼是JVM。JVM GC分代管理和流轉過程。 永久代空間不足:Permanet Generation中存放的為一些class的信息、常量、靜態變量等數據,當系統中要加載的類、反射的類和調用的方法較多時,Permanet Generation可能會被占滿,在未配置為採用CMS GC的情況下也會執行Full GC。

本篇文章,我們將主要介紹JVM GC原理相關的知識:

  • 什麼是JVM
  • 什麼是GC
  • JVM GC收集器介紹
  • JVM體系結構中GC作用區域
  • JVM GC分代管理和流轉過程

什麼是JVM

JVM概念

JVM是java Virtual Machine(Java虛擬機)的縮寫。

1.1、什麼是Java虛擬機呢?Java虛擬機的好處是什麼呢?

答:虛擬機是一種抽象化的計算機,通過在實際的計算機上仿真模擬各種計算機功能來實現的。Java虛擬機有自己完善的硬體架構,如處理器、堆棧、寄存器等,還具有相應的指令系統。Java虛擬機屏蔽了與具體作業系統平台相關的信息,使得Java程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平台上不加修改地運行。

簡單來說JVM是用來解析和運行Java程序的。

Java語言的一個非常重要的特點就是與平台的無關性。而使用Java虛擬機是實現這一特點的關鍵。一般的高級語言如果要在不同的平台上運行,至少需要編譯成不同的目標代碼。而引入Java語言虛擬機後,Java語言在不同平台上運行時不需要重新編譯。Java語言使用Java虛擬機屏蔽了與具體平台相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平台上不加修改地運行。Java虛擬機在執行字節碼時,把字節碼解釋成具體平台上的機器指令執行。這就是Java的能夠**「一次編譯,到處運行」**的原因。

圖解JVM

從Java平台的邏輯結構上來看,我們可以從下圖來了解JVM:

在這裡插入圖片描述從上圖能清晰看到Java平台包含的各個邏輯模塊,也能了解到jdk與JRE的區別,對於JVM自身的物理結構,請看下圖:

什麼是GC

名詞解釋:
Garbage Collections 字面意思是垃圾回收器,釋放垃圾占用的空間。

GC的作用:
讓創建的對象不需要像c、c++那樣delete、free掉 。對於c、c++的開發人員來說內存是開發人員分配的,也就是說還要對內存進行維護和釋放。

對於Java程式設計師來說,一個對象的內存分配是在虛擬機的自動內存分配機制的幫助下,不再需要為每一個new操作去寫配對的delete/free代碼,而且不容易出現內存泄露和內存溢出問題,但是,如果出現了內存泄露和內存溢出問題,而開發者又不了解虛擬機是怎麼分配內存的話,那麼定位錯誤和排除錯誤將是一件很困難的事情。

GC工作原理:
當應用線程在運行,並在運行過程中創建新對象,若這時內存空間不足,JVM就會強制地調用GC線程,以便回收內存用於新的分配。若GC一次之後仍不能滿足內存分配的要求,JVM會再進行兩次GC作進一步的嘗試,若仍無法滿足要求,則 JVM將報「out of memory」(內存溢出)的錯誤,Java應用將停止。

JVM GC收集器介紹

有哪些垃圾收集器?

串行垃圾回收器(Serial)

JVM第一個垃圾收集器,JDK 1.3.1之前都是有這個收集器。可以作用新生代和老年代。

算法:新生代-使用複製算法,老年代-使用標記-整理算法。

特點:串行單線程、不支持並發、會導致"stop the world"。

//配置如下使用
-XX:+UseSerialGC

並發垃圾回收器(parNew)

為了解決 serial拉圾收集器引起的停機問題,在serial基礎上開發了多線程版本,但是parNew是針對client的版本。

特點:多線程、唯一能夠與cms搭配使用的收集器;

算法:年輕代採用複製算法、年老代採用標記-整理

//強制指定使用ParNew;
-XX:+UseParNewGC
//指定垃圾收集的線程數量,ParNew默認開啟的收集線程與CPU的數量相同;
-XX:ParallerGCThreads

並行垃圾收集器(Parallel Scavenge)

Parallel Scavenge是後個多線程新生代收集器,使用的算法是複製算法。實現方式是當垃圾達到一個可控制的吞吐量(Throughput)則啟動垃圾回收。

特點:作用於新生代、老年代由Serial搭配使用,可以有效的減少停頓時間提升用戶體驗。

算法:新生代複製算法,老年代標記整理

//注意:啟動後不需要手工指定新生代的大小(-Xmn)、Eden和Survivor區的比例(-XX:SurvivorRatio)、晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細節參數了
-XX:+UseAdaptiveSizePolicy
//使用Parallel收集器+老年代串行
-XX:+UseParallelGC
//啟用並行壓縮
-XX:+UseParallelOldGC。

並發垃圾回收器 CMS(Concurrent Mark Sweep)

cms主要是解決停頓時間的問題而開發的一種,低停頓、並發收集器,基於標記清除,可以作用於新生代和老年代。

CMS收集器的運行過程分為下列4步:

初始標記:標記GC Roots能直接到的對象。速度很快但是仍存在Stop The World問題。

並發標記:進行GC Roots Tracing 的過程,找出存活對象且用戶線程可並發執行。

重新標記:為了修正並發標記期間因用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄。仍然存在Stop The World問題。

並發清除:對標記的對象進行清除回收。

特點:作用於新生代、老年代、代停頓、並發收集;

算法:標記-清除;

缺點:無法處理浮動垃圾、對CPU資源非常敏感、會造成空間碎片化;

//新生代使用並行收集器,老年代使用CMS+串行收集器
-XX:+UseConcMarkSweepGC
//設定CMS的線程數量
-XX:ParallelCMSThreads

G1垃圾回收器(G1)

特點:作用於新生代、老年代、空間連續減少垃圾碎片、縮小回收範圍減少全局停頓、並發收集;

算法:分區算法;

缺點:無法處理浮動垃圾、對CPU資源非常敏感、會造成空間碎片化;

G1收集器的階段分以下幾個步驟:

1、初始標記(它標記了從GC Root開始直接可達的對象);

2、並發標記(從GC Roots開始對堆中對象進行可達性分析,找出存活對象);

3、最終標記(標記那些在並發標記階段發生變化的對象,將被回收);

4、篩選回收(首先對各個Regin的回收價值和成本進行排序,根據用戶所期待的GC停頓時間指定回收計劃,回收一部分Region)。

//啟用G1垃圾回收器 -XX:+UseG1GC

查看jdk所用內存垃圾回收器

通過命令:

java -XX:+PrintCommandLineFlags -version

堆大小的計算?

JVM體系結構中GC作用區域

堆(heap):
它是JVM用來存儲對象實例以及數組值的區域,可以認為Java中所有通過new創建的對象的內存都在此分配,Heap中的對象的內存需要等待GC進行回收。

1.堆是JVM中所有線程共享的,因此在其上進行對象內存的分配均需要進行加鎖,這也導致了new對象的開銷是比較大的

2.Sun Hotspot JVM為了提升對象內存分配的效率,對於所創建的線程都會分配一塊獨立的空間TLAB(Thread Local Allocation Buffer),其大小由JVM根據運行的情況計算而得,在TLAB上分配對象時不需要加鎖,因此JVM在給線程的對象分配內存時會儘量的在TLAB上分配,在這種情況下JVM中分配對象內存的性能和C基本是一樣高效的,但如果對象過大的話則仍然是直接使用堆空間分配

3.TLAB僅作用於新生代的Eden Space,因此在編寫Java程序時,通常多個小的對象比大的對象分配起來更加高效。

方法區(Method Area)

1.在Sun JDK中這塊區域對應的為PermanetGeneration,又稱為持久代。

2.方法區域存放了所加載的類的信息(名稱、修飾符等)、類中的靜態變量、類中定義為final類型的常量、類中的Field信息、類中的方法信息,當開發人員在程序中通過Class對象中的getName、isInterface等方法來獲取信息時,這些數據都來源於方法區域。同時方法區域也是全局共享的,在一定的條件下它也會被GC,當方法區域需要使用的內存超過其允許的大小時,會拋出OutOfMemory的錯誤信息。

JVM GC分代管理和流轉過程

GC分為永久代(Perm區域,對應方法區)、新生代(Eden,S0,S1區域)、老年代(Old區域)。

永久代:存放的為一些class的信息、常量、靜態變量等數據。當系統中要加載的類、反射的類和調用的方法較多時可能占滿,觸發GC。
新生代:存放生命周期較短的對象,GC頻率高。
老年代:存放新生代中經歷多次GC依然存活的對象,新建的對象也有可能直接在老生代分配(比如大對象),取決於具體GC的實現。GC頻率相對較低。

GC類型主要分為:
MinorGC/YoungGC:只是新生代(Eden,S0,S1)的垃圾收集
MajorGC/OldGC:只是老年代的垃圾收集。
FullGC:收集整個java堆(新生代、老年代)和方法區(永久代)的垃圾收集。

注意:只有CMS收集器會有單獨收集老年代的行為,其他收集器均無此行為,老年代空間不足會直接觸發Full GC。而針對新生代的MinorGC,各個收集器均支持。

總之,單獨發生收集行為的只有新生代,除了CMS收集器,都不支持單獨回收老年代。這也是為什麼我們經常提到的只有Young GC和Full GC,而很少提到Old GC的原因。

流轉過程:
EdenSpace(Eden區一次Young GC)=》FromSpace(S0區一次Young GC)=》ToSpace(S1區一次Young GC)=》對象移至老生代=》老年代空間不足=》觸發Full GC


觸發Full GC的常見情況

1. System.gc()方法的調用:此方法的調用是建議JVM進行Full GC,雖然只是建議而非一定,但很多情況下它會觸發 Full GC,從而增加Full GC的頻率,也即增加了間歇性停頓的次數。強烈影響系統性能。建議能不使用此方法就別使用,讓虛擬機自己去管理它的內存,可通過通過-XX:+ DisableExplicitGC來禁止RMI調用System.gc。

2. 老年代空間不足:如果某個(些)對象(原來在內存中存活的對象或者新創建的對象)由於以上原因需要被移動到老年代中,而老年代中沒有足夠空間容納這個(些)對象,那麼會觸發一次Full GC。

3. 永久代空間不足:Permanet Generation(方法區)中存放的為一些class的信息、常量、靜態變量等數據,當系統中要加載的類、反射的類和調用的方法較多時,Permanet Generation可能會被占滿,在未配置為採用CMS GC的情況下也會執行Full GC。

Full GC的執行時間比Minor GC要長很多。因此,如果Full GC花費了太多的時間(比如超過1秒),一些連接的部分可能會發生超時錯誤。要儘量減少觸發Full GC。

希望本文對你有所幫助~~如果對軟體測試、接口測試、自動化測試、面試經驗交流感興趣可以私聊我。免費領取最新軟體測試大廠面試資料和Python自動化、接口、框架搭建學習資料!技術大牛解惑答疑,同行一起交流。

關鍵字: