Java JVM參數調優設定

2020-08-12 11:00:08

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. PC(Program 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虛擬機器,就可以在系統執行時列印相關日誌,用於分析實際

虛擬機器參數設定,其實就是圍繞着堆、棧、方法區、進行設定。

堆的參數設定

參數 作用
-XX:+PrintGC 每次觸發GC的時候列印相關日誌
-XX:+UseSerialGC 序列回收
-XX:+PrintGCDetails 更詳細的GC日誌
-Xms 堆初始值
-Xmx 堆最大可用值
-Xmn 新生代堆最大可用值
-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的增長空間