JVM內存分配及調優方案(基於JDK1.8)


1.前言

Java作為目前最通用的編程語言之一,而Java底層的JVM是Java編程語言的核心。不管是在企業應用系統,移動終端還是大數據領域都有很大的市場占有率。Java的扁平快受到越來越多的開發青睞,但與C/C++相比,Java語言也有些不足的地方,比如在垃圾回收機制上。什么叫垃圾回收,簡單來如,不管是對於Java還是C/C++而言,一切皆對象,當創建對象后,就要分配隊或棧占用資源。對象實例化后,不可能一直被引用。這里就會有一個問題,對象核實才不會被引用,不被引用后是否立即進行回收。這個在Java和C/C++中是兩種不同的方案的。C/C++需要開發者自己去甄別哪個對象不被引用,然后對這個對象進行回收釋放資源。但在Java中是進行自動垃圾回收機制的,雖然Java中通過對象的finalize()方法保留了C/C++這個特性,但Java還是建議開發者將垃圾回收這個工作交給JVM。所以真正的想要提升開發java程序或應用系統的性能,就必須對JVM有深刻理解,這樣才會有優化方案。本篇博文以Jdk1.8介紹JVM的核心及常用的一些優化方案。

2.JVM布局

內存是非常重要的系統資源,是硬盤和CPU的中間倉庫及橋梁,承載着操作系統和應用程序的實時運行。JVM 內存布局規定了 Java 在運行過程中內存申請、分配、管理的策略 ,保證了 JVM 的高效穩定運行。

2.1.按內存布局

2.2.按線程是否共享布局

2.3.堆區(Heap)

為什么先說堆,以2.1的內存布局為例(紅色標記),如果你做過Java開發並對JVM有些了解就會知道堆是內存區域中最大的一塊區域,它被所有線程共享,幾乎存儲着幾乎所有的實例對象,幾乎所有的實例對象都會在堆上分配(這里為什么要用幾乎而不是確定的,因為隨着JIT編譯器的發展與逃逸分析技術逐漸成熟,棧上分配、標量替換優化技術將會導致一些微妙的變化發生,所有的對象都分配在堆上也漸漸變得不是那么“絕對”了)。既然是幾乎所有對象創建的地方,那么堆自然是垃圾收集器管理的主要區域,因此也被稱為GC堆。

a.堆的默認分配

b.可以通過命令查看分配的比例
$:java -XX:+PrintFlagsFinal –version

這個命令會輸出幾百行,我們這里去看和堆內存分配相關的兩個參數

uintx 	InitialSurvivorRatio=8
uintx 	NewRatio=2

參數詳情:
InitialSurvivorRatio 新生代Eden/Survicor空間的初始比例
NewRatio Old區/Young區的內存比例

因為新生代是由Eden + S0 + S1組成的,所以按照上述默認比例,如果eden區內存大小是40M,那么兩個survivor區就是5M,整個young區就是50M,然后可以算出Old區內存大小是100M,堆區總大小就是150M。

c.堆區的調整

根據Java虛擬機規范的規定,Java堆可以處於物理上不連續的內存空間中,只要邏輯上是連續的即可,就像我們的磁盤空間一樣。在實現時,既可以實現成固定大小的,也可以在運行時動態地調整。
通過設置如下參數,可以設定堆區的初始值和最大值,比如 -Xms256M-Xmx1024M,其中 -X這個字母代表它是JVM運行時參數, ms是 memory start的簡稱,中文意思就是內存初始值, mx 是 memory max的簡稱,意思就是最大內存。

2.4.元數據區

元空間的本質和永久代類似,都是對JVM規范中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存限制。
從2.1的流程圖及2.2的堆區介紹中可以知道一個Java程序,幾乎所有對象都在堆中。對於元數據區,該區是常量池,方法元信息,類元信息的存儲及占用資源空間的區域。對於這類信息,一個Java程序啟動這些信息所占用的資源量是固定的,相對於堆元數據對程序的性能影響較小。
元數據空間的優化參數:
-XX:MetaspaceSize 分配給Metaspace的初始大小
-XX:MaxMetaspaceSize 分配給Metaspace的最大值,超過就會觸發Full GC,辭職默認沒有限制,但取決於系統的內存大小,JVM會動態該變該值
-XX:MinMetaspaceSize 在GC之后,最小的Metaspace生育空間容量的百分比,減少該值會導致GC
-XX:MaxMetaspaceFreeRatio 在GC之后,最大的Metaspace生育空間容量的百分比,減少為釋放空間會導致 GC

3.對象的生命周期

從上面的介紹中可以知道Java中對象所處的位置,但這還不夠,只有真正了解一個對象的生命周期和內存分配流程才能更好的做優化。

絕大部分對象在Eden區生成,當Eden區裝填滿的時候,會觸發 YoungGarbageCollection,即 YGC。垃圾回收的時候,在Eden區實現清除策略,沒有被引用的對象則直接回收。依然存活的對象會被移送到Survivor區。Survivor區分為so和s1兩塊內存空間。每次 YGC的時候,它們將存活的對象復制到未使用的那塊空間,然后將當前正在使用的空間完全清除,交換兩塊空間的使用狀態。如果 YGC要移送的對象大於Survivor區容量的上限,則直接移交給老年代。一個對象也不可能永遠呆在新生代,就像人到了18歲就會成年一樣,在JVM中 -XX:MaxTenuringThreshold參數就是來配置一個對象從新生代晉升到老年代的閾值。默認值是15, 可以在Survivor區交換14次之后,晉升至老年代。

4.利用jvisualvm對JVM GC進行監控

4.1.安裝Visual GC插件

在jdk的bin目錄下有一個jvisualvm.exe,這個程序是jvm自帶的對jvm的全方位監控,但需要安裝對應的插件。這里只介紹對GC的監控。
運行jvisualvm.exe

工具->插件->設置

這里更改插件中心的代理,一般配置為一下代碼:
https://visualvm.github.io/archive/uc/8u40/updates.xml.gz
但需要對應你的jdk版本

配置好后再插件中選擇可用插件,勾選Visual GC點擊安裝

4.2.利用jvisualvm對java程序進行JVM GC監控

a.程序
package com.surfilter;

import java.util.LinkedList;
import java.util.List;

public class ChDDLDemo {
    public static void main(String args[]) throws Exception{
        System.out.println("HelloGC!");
        List list = new LinkedList();
        for(;;) {
            byte[] b = new byte[1024*1024];
            Thread.sleep(500);
            list.add(b);
        }
    }
}

該程序的邏輯十分簡單,再一個死循環中不斷的new新的對象,這個程序最終是會出現異常的。

b.啟動程序運用Visual GC監控

當程序啟動后可以看到本地有這個程序的類(當然也可以進行遠程的java程序的監控),雙擊這個類,可以看到該程序的一些信息,點擊Visual GC,會出現以下信息

監控是實時的,可以看到Metaspace是保持不變的,Old區,Eden區,S0,S1區都是在實時更新的,同時在右側依次往下可以看到編譯的時間,GC的回收的時序關系,S0區的內存時序關系,S1區的內存時序關系,Old區的時序關系以及Metaspace的時序關系。從監控圖的實時狀態中可以看出與對象的周期架構完全符合,等待該程序出異常,來查看這個監控在出現異常和正常執行之間的區別

現在來看Visual GC的監控圖

可以看出Old在上升直到滿了導致程序異常,而且整個過程都在頻繁的進行GC。如果在生產環境的程序GC出現如上圖的情況,肯定性能會打折扣,深圳導致生產環境程序崩潰。如果要避免這些問題,第一要素是使堆在每個區都有空間,第二要素是使程序不會過多的進行GC。這就是JVM優化調優的核心。針對於第二點要從代碼規范層面上着手,避免過多的不用對象。第二點則是針對於JVM的參數調優,在實際的開發中,兩種情況都會有,但第一種可能更為常見,接下來具體將針對的JVM的參數調優。

5.JVM調優

調優更多的實際應用中進行的,正如某位大神說的,沒有業務場景的調優都是耍流氓。這里只是提供一些調優的方案。
如上2.3的c對堆區的大小調整和對元數據的資源占用的調整,還有很多的調優方案

5.1.調優的步驟

a.熟悉業務場景(沒有最好的垃圾回收器,只有最合適的垃圾回收器)
b.了解程序的響應時間、停頓時間
c.調研吞吐量 = 用戶時間 / 用戶時間 + GC時間
d.選擇回收器組合
e.計算內存需求
f.設定年代大小、升級年齡
g.設定日志參數

-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCCause
觀察日志情況

5.2.兩個核心的JVM優化方案

a.盡量減少YoungGC,以減少代碼停頓
b.盡量減少FullGC,以減少系統停頓

在4.2中監控可以看出GC的次數非常多,GC分為YoungGC和FullGC,可見YoungGC和FullGC是非常多的,一天最多 FullGC 一次,最好在系統空閑期(如深夜)

5.3.參數級調優

選項 -Xms300M -Xmx300M -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:PermSize=100M -XX:MaxPermSize=100M 含義為:

  • 永久代固定尺寸為 100M;
  • 整個堆固定尺寸為 300M,其中“老年代 / 新生代”為-XX:NewRatio=2,所以老年代為 200M,新生代為 100M;
  • 新生代總共 100M,其中“Eden / Survivor0”為-XX:SurvivorRatio=8,所以 Eden 為 80M,Survivor0=Survivor1=10M。
    可以對以上參數適當的加大
    官方資料:http://www.oracle.com/technetwork/articles/java/vmoptions-jsp-140102.html
    實際的參數級調優方案很多,可以參照oracle官網


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM