JVM參數調優

說明 :原文參考螞蟻課堂餘勝軍老師。

 

Java虛擬機原理

所謂虛擬機,就是一臺虛擬的機器。他是一款軟件,用來執行一系列虛擬計算指令,大體上虛擬機可以分爲系統虛擬機和程序虛擬機, 大名鼎鼎的Visual Box、Vmare就屬於系統虛擬機,他們完全是對物理計算的仿真,提供了一個可以運行完整操作系統的軟件平臺。

 程序虛擬機典型代碼就是Java虛擬機,它專門爲執行單個計算程序而計算,在Java虛擬機中執行的指令我們成爲Java字節碼指令。無論是系統虛擬機還是程序虛擬機,在上面運行的軟件都被限制於虛擬機提供的資源中。

 Java發展至今,出現過很多虛擬機,做初Sun使用的一款叫ClassIc的Java虛擬機,到現在引用最廣泛的是HotSpot虛擬機,除了Sum以外,還有BEA的Jrockit,目前Jrockit和HostSopt都被oralce收入旗下,大有整合的趨勢。

 

注意:

注意區分java內存模型(JMM)和java內存結構(jvm內存結構)

java內存模型:描述多線程的可見性(主內存與線程本地內存)

java內存結構:JVM內存結構

 

Java內存結構

 

  1. 類加載子系統:負責從文件系統或者網絡加載Class信息,加載的信息存放在一塊稱之方法區的內存空間。
  2. 方法區:就是存放類的信息、常量信息、常量池信息、包括字符串字面量和數字常量等。
  3. Java堆:在Java虛擬機啓動的時候建立Java堆,它是Java程序最主要的內存工作區域,幾乎所有的對象實例都存放到

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

  1. 直接內存:JavaNio庫允許Java程序直接內存,從而提高性能,通常直接內存速度會優於Java堆。讀寫頻繁的場合可能會考慮使用。
  2. 每個虛擬機線程都有一個私有棧,一個線程的Java棧在線程創建的時候被創建,Java棧保存着局部變量、方法參數、返回值等。
  3. 本地方法棧,最大不同爲本地方法棧用於本地方法調用。Java虛擬機允許Java直接調用本地方法(通過使用C語言寫)
  4. 垃圾收集系統是Java的核心,也是不可少的,Java有一套自己進行垃圾清理的機制,開發人員無需手工清理。
  5. PC(Program Couneter)寄存器也是每個線程私有的空間, Java虛擬機會爲每個線程創建PC寄存器,在任意時刻,

一個Java線程總是在執行一個方法,這個方法稱爲當前方法,如果當前方法不是本地方法,PC寄存器總會執行當前正在被執行的指令,

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

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

 

堆、棧、方法區概念區別

 

Java堆

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

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

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

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

絕大多數情況下,對象首先分配在eden區在新生代被GC回收後,如果對象還存活,則進入s0或s1區,之後每經過一次新生代GC回收,如果對象存活則它的年齡就加1,對象達到一定的年齡後,則進入老年代

 

注:

新生代:剛創建的對象,先存放在新生代。

老年代:如果對象在頻繁的使用,未被GC回收,則當達到一定年齡後,對象將放入到老年代。

垃圾回收機制主要回收新生代

 

調優簡要說明:

  1. 在web系統中,儘量減少常量信息(因爲常量信息存放在方法區也叫永久區,是屬於線程共享的,垃圾回收機制不會回收),可能導致OOM。
  2. 儘量減少垃圾回收機制次數(在實際工作中,我們可以直接將初始的堆大小[Xms]與最大堆[Xmx]大小相等,這樣的好處是可以減少程序運行時垃圾回收次數,從而提高效率)
  3. 新生代回收次數比老年代多。

 

Java棧

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

局部變量表:用於存放方法參數和方法內部定義的局部變量

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

幀數據區:除了局部變量表和操作數據棧以外,棧還需要一些數據來支持常量池的解析,這裏幀數據區保存着訪問常量池的指針,方便程序訪問常量池,另外當函數返回或出現異常時虛擬機必須有一個異常處理表,方便發送異常的時候找到異常的代碼,因此異常處理表也是幀數據區的一部分。

 

Java方法區

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

比如類的字段、方法、常量池等。方法區的大小決定系統可以保存多少個類。如果系統定義太多的類和常量,導致方法區溢出

虛擬機同樣會拋出內存溢出的錯誤。方法區可以理解爲永久區。

 

虛擬機參數配置

 

什麼是虛擬機參數配置

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

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

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

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

 

堆的參數配置

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

-XX:+UseSerialGC      串行回收

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

-Xms               堆初始值

-Xmx               堆最大可用值

-Xmn               新生代堆最大可用值

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

含義:-XX:SurvivorRatio=eden/from=eden/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();

}

}

 

Tomcat內存溢出在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的增長空間。
發佈了203 篇原創文章 · 獲贊 484 · 訪問量 196萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章