1. 程式人生 > >JVM參數調優與垃圾回收機制

JVM參數調優與垃圾回收機制

相對 完全 相關 相同 cat 屬於 跟蹤系統 col 組成

自動內存管理機制

Java虛擬機原理

所謂虛擬機,就是一臺虛擬的機器。他是一款軟件,用來執行一系列虛擬計算指令,大體上虛擬機可以分為

系統虛擬機和程序虛擬機, 大名鼎鼎的Visual Box、Vmare就屬於系統虛擬機,他們完全是對物理計算的仿真,

提供了一個可以運行完整操作系統的軟件平臺。

程序虛擬機典型代碼就是Java虛擬機,它專門為執行單個計算程序而計算,在Java虛擬機中執行的指令我們成為Java

自己碼指令。無論是系統虛擬機還是程序虛擬機,在上面運行的軟件都被限制於虛擬機提供的資源中。

Java發展至今,出現過很多虛擬機,做初Sun使用的一款叫ClassIc的Java虛擬機,到現在引用最廣泛的是HotSpot虛擬

機,除了Sum意外,還有BEA的Jrockit,目前Jrockit和HostSopt都被oralce收入旗下,大有整合的趨勢。

Java內存結構

技術分享圖片

技術分享圖片

1、 類加載子系統:負責從文件系統或者網絡加載Class信息,加載的信息存放在一塊稱之方法區的內存空間。

2、 方法區:就是存放類的信息、常量信息、常量池信息、包括字符串字面量和數字常量等。

3、 Java堆:在Java虛擬機啟動的時候建立Java堆,它是Java程序最主要的內存工作區域,幾乎所有的對象實例都存放到

Java堆中,堆空間是所有線程共享。

4、 直接內存:JavaNio庫允許Java程序直接內存,從而提高性能,通常直接內存速度會優於

Java堆。讀寫頻繁的場合可能會考慮使用。

5、 每個虛擬機線程都有一個私有棧,一個線程的Java棧在線程創建的時候被創建,Java棧保存著局部變量、方法參數、同事Java的方法調用、

返回值等。

6、 本地方法棧,最大不同為本地方法棧用於本地方法調用。Java虛擬機允許Java直接調用本地方法(通過使用C語言寫)

7、 垃圾收集系統是Java的核心,也是不可少的,Java有一套自己進行垃圾清理的機制,開發人員無需手工清理,下一節課詳細講。

8、 PCProgram Couneter)寄存器也是每個線程私有的空間, Java虛擬機會為每個線程創建PC寄存器,在任意時刻,

一個Java線程總是在執行一個方法,這個方法稱為當前方法,如果當前方法不是本地方法,

PC寄存器總會執行當前正在被執行的指令,

如果是本地方法,則PC寄存器值為Underfined,寄存器存放如果當前執行環境指針、程序技術器、操作棧指針、計算的變量指針等信息。

9、 虛擬機核心的組件就是執行引擎,它負責執行虛擬機的字節碼,一般戶先進行編譯成機器碼後執行。

、棧、方法區概念區別

Java

堆內存用於存放由new創建的對象和數組在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。在堆中產生了一個數組或者對象後,還可以在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,在棧中的這個特殊的變量就變成了數組或者對象的引用變量,以後就可以在程序中使用棧內存中的引用變量來訪問堆中的數組或者對象,引用變量相當於為數組或者對象起的一個別名,或者代號。

根據垃圾回收機制的不同,Java堆有可能擁有不同的結構,最為常見的就是將整個Java堆分為

新生代和老年代。其中新聲帶存放新生的對象或者年齡不大的對象,老年代則存放老年對象。

新生代分為den區、s0區、s1區,s0和s1也被稱為from和to區域,他們是兩塊大小相等並且可以互相角色的空間。

絕大多數情況下,對象首先分配在eden區,在新生代回收後,如果對象還存活,則進入s0或s1區,之後每經過一次

新生代回收,如果對象存活則它的年齡就加1,對象達到一定的年齡後,則進入老年代。

技術分享圖片

Java

Java棧是一塊線程私有的空間,一個棧,一般由三部分組成:局部變量表、操作數據棧和幀數據區

局部變量表:用於報錯函數的參數及局部變量

操作數棧:主要保存計算過程的中間結果,同時作為計算過程中的變量臨時的存儲空間。

幀數據區:除了局部變量表和操作數據棧以外,棧還需要一些數據來支持常量池的解析,這裏幀數據區保存著

訪問常量池的指針,方便計程序訪問常量池,另外當函數返回或出現異常時賣虛擬機子必須有一個異常處理表,方便發送異常

的時候找到異常的代碼,因此異常處理表也是幀數據區的一部分。

技術分享圖片

Java方法

Java方法區和堆一樣,方法區是一塊所有線程共享的內存區域,他保存系統的類信息。

比如類的字段、方法、常量池等。方法區的大小決定系統可以保存多少個類。如果系統

定義太多的類,導致方法區溢出。虛擬機同樣會拋出內存溢出的錯誤。方法區可以理解

為永久區。

虛擬機參數配置

什麽虛擬機參數配置

在虛擬機運行的過程中,如果可以跟蹤系統的運行狀態,那麽對於問題的故障

排查會有一定的幫助,為此,在虛擬機提供了一些跟蹤系統狀態的參數,使用

給定的參數執行Java虛擬機,就可以在系統運行時打印相關日誌,用於分析實際

問題。我們進行虛擬機參數配置,其實就是圍繞著堆、棧、方法區、進行配置。

說下 熟悉那些jvm參數調優

的參數配置

-XX:+PrintGC 每次觸發GC的時候打印相關日誌

-XX:+UseSerialGC 串行回收

-XX:+PrintGCDetails 更詳細的GC日誌

-Xms 堆初始值

-Xmx 堆最大可用值

-Xmn 新生代堆最大可用值

-XX:SurvivorRatio 來設置新代中eden空間from/to空間的比例.

-XX:SurvivorRatio=eden/from=den/to

總結:在實際工作中,我們可以直接將初始的堆大小與最大堆大小相等,

這樣的好處是可以減少程序運行時垃圾回收次數,從而提高效率。

-XX:SurvivorRatio 來設置新代中eden空間from/to空間的比例.

設置最大堆內存

參數: -Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags

public class JvmDemo01 {

    public static void main(String[] args) throws InterruptedException {
        byte[] b1 = new byte[1 * 1024 * 1024];
        System.out.println("分配了1m");
        jvmInfo();        
        Thread.sleep(3000);
        byte[] b2 = new byte[4 * 1024 * 1024];
        System.out.println("分配了4m");
        Thread.sleep(3000);
        jvmInfo();

    }
    static private String toM(long maxMemory) {
        float num = (float) maxMemory / (1024 * 1024);
        DecimalFormat df = new DecimalFormat("0.00");// 格式化小數
        String s = df.format(num);// 返回的是String類型
        return s;
    }
    static private void jvmInfo() {
        // 最大內存
        long maxMemory = Runtime.getRuntime().maxMemory();
        System.out.println("maxMemory:" + maxMemory + ",轉換為M:" + toM(maxMemory));
        // 當前空閑內存
        long freeMemory = Runtime.getRuntime().freeMemory();
        System.out.println("freeMemory:" +freeMemory+",轉換為M:"+toM(freeMemory));
        // 已經使用內存
        long totalMemory = Runtime.getRuntime().totalMemory();
        System.out.println("totalMemory:" +totalMemory+",轉換為M"+toM(totalMemory));
    }
}


設置新
與老年代優化參數

-Xmn 新生代大小,一般設為整個堆的1/3到1/4左右

-XX:SurvivorRatio 設置新生代中eden區和from/to空間的比例關系n/1

設置新生代比例參數

參數: -Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC

public class JvmDemo02 {

     public static void main(String[] args) {
        //-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
         byte [] b = null;
         for (int i = 0; i < 10; i++) {
            b =new byte[1*1024*1024];
        }
         
    }


設置新老年代代參數

-Xms20m -Xmx20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC

-XX:NewRatio=2

總結:不同的堆分布情況,對系統執行會產生一定的影響,在實際工作中,

應該根據系統的特點做出合理的配置,基本策略:盡可能將對象預留在新生代,

減少老年代的GC次數。

除了可以設置新生代的絕對大小(-Xmn),可以使用(-XX:NewRatio)設置新生代和老年

代的比例:-XX:NewRatio=老年代/新生代

內存溢出解決辦法

設置內存大小

錯誤原因: java.lang.OutOfMemoryError: Java heap space 內存溢出

解決辦法:設置堆內存大小 -Xms1m –Xmx10m -XX:+HeapDumpOnOutOfMemoryError

public static void main(String[] args) throws InterruptedException {
        List<Object> list = new ArrayList<>();
        Thread.sleep(3000);
        jvmInfo();
        for (int i = 0; i < 10; i++) {
            System.out.println("i:"+i);
            Byte [] bytes=    new Byte[1*1024*1024];
            list.add(bytes);
            jvmInfo();
        }
        System.out.println("添加成功...");
    }


設置棧內存大小

錯誤原因: java.lang.StackOverflowError 內存溢出

棧溢出 產生於遞歸調用,循環遍歷是不會的,但是循環方法裏面產生遞歸調用, 也會發生棧溢出。

解決辦法:設置線程最大調用深度

-Xss5m 設置最大調用深度

public class JvmDemo04 {
     private static int count;
     public static void count(){
        try {
             count++;
             count(); 
        } catch (Throwable e) {
            System.out.println("最大深度:"+count);
            e.printStackTrace();
        }
     }
     public static void main(String[] args) {
         count();
    }
}


T
omcat內存溢出catalina.sh 修改JVM內存大小

JAVA_OPTS="-server -Xms800m -Xmx800m -XX:PermSize=256m -XX:MaxPermSize=512m -XX:MaxNewSize=512m"

JVM參數調優總結

JVM啟動參數中,可以設置跟內存、垃圾回收相關的一些參數設置,默認情況不做任何設置JVM會工作的很好,但對一些配置很好的Server和具體的應用必須仔細調優才能獲得最佳性能。通過設置我們希望達到一些目標:

  • GC的時間足夠的小
  • GC的次數足夠的少
  • 發生Full GC(生代老年代)的周期足夠的長

前兩個目前是相悖的,要想GC時間小必須要一個更小的堆,要保證GC次數足夠少,必須保證一個更大的堆,我們只能取其平衡。

1)針對JVM堆的設置,一般可以通過-Xms -Xmx限定其最小、最大值,為了防止垃圾收集器在最小、最大之間收縮堆而產生額外的時間,我們通常把最大、最小設置為相同的值
2)年輕代和年老代將根據默認的比例(1:2)分配堆內存,可以通過調整二者之間的比率NewRadio來調整二者之間的大小,也可以針對回收代,比如年輕代,通過 -XX:newSize -XX:MaxNewSize來設置其絕對大小。同樣,為了防止年輕代的堆收縮,我們通常會把-XX:newSize -XX:MaxNewSize設置為同樣大小

3)年輕代和年老代設置多大才算合理?這個我問題毫無疑問是沒有答案的,否則也就不會有調優。我們觀察一下二者大小變化有哪些影響

  • 更大的年輕代必然導致更小的年老代,大的年輕代會延長普通GC的周期,但會增加每次GC的時間;小的年老代會導致更頻繁的Full GC
  • 更小的年輕代必然導致更大年老代,小的年輕代會導致普通GC很頻繁,但每次的GC時間會更短;大的年老代會減少Full GC的頻率
  • 如何選擇應該依賴應用程序對象生命周期的分布情況:如果應用存在大量的臨時對象,應該選擇更大的年輕代;如果存在相對較多的持久對象,年老代應該適當增大。但很多應用都沒有這樣明顯的特性,在抉擇時應該根據以下兩點:(A)本著Full GC盡量少的原則,讓年老代盡量緩存常用對象,JVM的默認比例1:2也是這個道理 (B)通過觀察應用一段時間,看其他在峰值時年老代會占多少內存,在不影響Full GC的前提下,根據實際情況加大年輕代,比如可以把比例控制在1:1。但應該給年老代至少預留1/3的增長空間

JVM參數調優與垃圾回收機制