天天看點

JVM-基本概念

基本概念

JVM-基本概念
  1. 類加載子系統:負責從檔案系統或者網絡中加載Class資訊,加載的資訊存放在一塊稱之為方法區的記憶體空間。
  2. 方法區:就是存放類資訊、常量資訊、常量池資訊、包括字元串字面量和數字常量等。
  3. java堆:在java虛拟機啟動的時候建立java堆,它就是java程式最主要的記憶體工作區域,幾乎所有的對象執行個體都存放在java堆中,堆空間是所有線程共享的。
  4. 直接記憶體:java的NIO庫允許java程式直接使用記憶體,進而提高性能,通常直接記憶體速度會優于java堆。讀寫頻繁的場合可能考慮使用。
  5. java棧:每個虛拟機線程都有一個私有棧,一個線程的java棧線上程建立的時候被建立,java棧中儲存着局部變量、方法參數、java的方法調用、傳回值等。
  6. 本地方法棧:本地方法棧和java棧非常類似,最大不同為本地方法棧用于本地方法調用,java虛拟機允許java直接調用本地方法(通常使用c編寫)。
  7. 垃圾收集系統:垃圾收集系統是java的核心,也是比不可少的,java有一套自己進行垃圾收集的機制,開發人員無需手工清理。
  8. PC寄存器:PC寄存器也是每個線程私有的空間,java虛拟機會為每個線程建立PC寄存器,在任意時刻,一個java線程總是在執行一個方法,這個方法被稱為目前方法,如果目前方法不是本地方法,PC寄存器就會執行目前正在被執行的指令,如果是本地方法,則PC寄存器值為undefined,PC寄存器中存放如目前環境指針、程式計數器、操作棧指針、計算變量指針等資訊。
  9. 執行引擎:虛拟機最核心的元件就是執行引擎,它負責執行虛拟機的位元組碼,一般是先進行編譯成機器碼之後執行。

堆、棧、方法區概念和聯系

堆解決的是資料存儲的問題,即資料怎麼放、放在哪兒。棧解決程式運作問題,即程式如何運作,或者說如何處理資料。方法區則是輔助堆棧的快永久區(Perm),解決堆棧資訊的産生,是先決條件。我們建立一個新的對象,User:那麼User類的一些資訊(類資訊、靜态資訊都取決于方法區中),User類被執行個體化出來之後,被存儲到java堆中,一塊記憶體空間。當我們去使用的時候,都是使用的是User對象的引用,形如User user = new User();這裡的user就是存放在java棧中的,即User真實對象的一個引用。

JVM-基本概念

java堆

java堆是和java應用程式關系最密切的記憶體空間,幾乎所有的對象都存放在其中,并且java堆完全是自動化管理的,通過垃圾回收機制,垃圾對象會自動清理,不需要顯示地釋放。根據垃圾收集機制不同,java堆有可能擁有不同的結構。最為常見的就是将整個java堆分為新生代和老年代。其中新生代存放新生的對象或者年齡不大的對象,老年代則存放老年對象。新生代分為eden區、S0區、S1區,S0和S1也被稱為From和To區域,他們是兩塊大小相等并且可以互換角色的空間。絕大多數情況下,對象首先配置設定在eden區,在一次新生代回收後,如果對象還存活,則會計入s0或者s1區,之後每經過一次新生代回收,如果對象存活則年齡就加1,當對象達到一定的年齡後,則進入老年代。

JVM-基本概念

java棧

java棧是一塊線程私有的記憶體空間,一個棧,一般由三部分組成:局部變量表、操作數棧和幀資料區。

  • 局部變量表:用于報錯函數的參數及局部變量。
  • 操作數棧:主要儲存計算過程的中間結果,同時作為計算過程中變量臨時的存儲空間。
  • 幀資料區:處理局部變量表和操作數棧以外,棧還需要一些資料來支援常量池的解析,這裡幀資料區儲存着通路常量池的指針,友善程式通路常量池,另外,當函數傳回或者出現異常時,虛拟機必須有一個異常處理表,友善發送異常的時候找到異常的代碼,是以異常處理表也是幀資料的一部分。

java方法區

java方法區和堆一樣,方法區是一塊所有線程共享的記憶體區域,它儲存系統的類資訊,比如類的字段、方法、常量池等。方法區的大小決定了系統可以儲存多少個類,如果系統定義太多的類,容易導緻方法區溢出。虛拟機同樣會抛出記憶體溢出錯誤,方法區可以了解為永久代區(Perm)

虛拟機參數

在虛拟機運作過程中,如果可以跟蹤系統的運作狀态,那麼對于問題的故障排查會有一定的幫助,為此,虛拟機提供了一些跟蹤系統狀态的參數,使用給定的參數執行java虛拟機,就可以在系統運作時列印相關的日志,用于分析實際問題。我們進行虛拟機參數的配置,其實主要就是圍繞着堆、棧、方法區進行配置。

堆配置設定參數(一)

  • -XX:+PrintGC:使用這個參數,虛拟機啟動之後,隻要遇到GC就會列印日志。
  • -XX:+UseSerialGC:配置串行回收器。
  • -XX:+PrintGCDetails:可以檢視詳細資訊,包括各個區的情況。
  • -Xms:設定java程式啟動時初始化堆大小。
  • -Xmx:設定java程式能獲得的最大堆大小。
  • -Xmx20m -xms5m -XX:+PrintCommandLineFlags:可以隐式或者顯示傳給虛拟機的參數輸出。
public class Test01 {

	public static void main(String[] args) {
		//1.-XX:+PrintGC -Xms5m -Xmx20m -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+PrintCommandLineFlags
		//檢視GC資訊
		System.out.println("max memory: " + Runtime.getRuntime().maxMemory());
		System.out.println("free memory: " + Runtime.getRuntime().freeMemory());
		System.out.println("total memory: " + Runtime.getRuntime().totalMemory());
		
		byte[] b1 = new byte[1*1024*1024]; //1M
		
		System.out.println("配置設定了1M");
		System.out.println("max memory: " + Runtime.getRuntime().maxMemory());
		System.out.println("free memory: " + Runtime.getRuntime().freeMemory());
		System.out.println("total memory: " + Runtime.getRuntime().totalMemory());
		
		byte[] b2 = new byte[4*1024*1024]; //4M
		
		System.out.println("配置設定了4M");
		System.out.println("max memory: " + Runtime.getRuntime().maxMemory());
		System.out.println("free memory: " + Runtime.getRuntime().freeMemory());
		System.out.println("total memory: " + Runtime.getRuntime().totalMemory());
	}

}
/*
列印結果:
-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC 
max memory: 20316160
free memory: 5285968
total memory: 6094848
[GC (Allocation Failure) [DefNew: 789K->191K(1856K), 0.0014625 secs] 789K->527K(5952K), 0.0015014 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
配置設定了1M
max memory: 20316160
free memory: 4471800
total memory: 6094848
[GC (Allocation Failure) [DefNew: 1249K->0K(1856K), 0.0012294 secs][Tenured: 1551K->1551K(4096K), 0.0039679 secs] 1585K->1551K(5952K), [Metaspace: 2599K->2599K(1056768K)], 0.0052516 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
配置設定了4M
max memory: 20316160
free memory: 4540624
total memory: 10358784
Heap
 def new generation   total 1920K, used 68K [0x00000000fec00000, 0x00000000fee10000, 0x00000000ff2a0000)
  eden space 1728K,   3% used [0x00000000fec00000, 0x00000000fec113e8, 0x00000000fedb0000)
  from space 192K,   0% used [0x00000000fedb0000, 0x00000000fedb0000, 0x00000000fede0000)
  to   space 192K,   0% used [0x00000000fede0000, 0x00000000fede0000, 0x00000000fee10000)
 tenured generation   total 8196K, used 5647K [0x00000000ff2a0000, 0x00000000ffaa1000, 0x0000000100000000)
   the space 8196K,  68% used [0x00000000ff2a0000, 0x00000000ff823ef8, 0x00000000ff824000, 0x00000000ffaa1000)
 Metaspace       used 2605K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 288K, capacity 386K, committed 512K, reserved 1048576K
 */
           

總結:在實際工作中,我們可以直接将初始的堆大小與最大堆設定相等,這樣的好處是可以減少程式運作時的垃圾回收次數,進而提高性能。

堆配置設定參數(二)

新生代的配置:

  • Xmn:可以設定新生代的大小,設定一個比較大的新生代會減少老年代的大小,這個參數對系統性能以及GC行為有很大的影響,新生代大小一般會設定為整個堆空間的1/3到1/4左右。
  • XX:SurvivoRatio:用來設定新生代中eden空間和from/to空間的比例。含義:-XXSurvivoRatio=eden/from=eden/to
public class Test02 {

	public static void main(String[] args) {
		//1.-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC(eden 2 = from 1 + to 1)
		
		byte[] b = null;
		//連續申請記憶體
		for(int i = 0; i < 10; i++){
			b = new byte[1*1024*1024];
		}
	}

}
/*
 [GC (Allocation Failure) [DefNew: 509K->256K(768K), 0.0011554 secs] 509K->436K(20224K), 0.0011969 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 768K, used 497K [0x00000000fec00000, 0x00000000fed00000, 0x00000000fed00000)
  eden space 512K,  47% used [0x00000000fec00000, 0x00000000fec3c7b0, 0x00000000fec80000)
  from space 256K, 100% used [0x00000000fecc0000, 0x00000000fed00000, 0x00000000fed00000)
  to   space 256K,   0% used [0x00000000fec80000, 0x00000000fec80000, 0x00000000fecc0000)
 tenured generation   total 19456K, used 10420K [0x00000000fed00000, 0x0000000100000000, 0x0000000100000000)
   the space 19456K,  53% used [0x00000000fed00000, 0x00000000ff72d138, 0x00000000ff72d200, 0x0000000100000000)
 Metaspace       used 2603K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 288K, capacity 386K, committed 512K, reserved 1048576K
 * */
           

總結:不同的堆分布情況,對系統執行會産生一定的影響,在實際工作中,應該根據系統的特點做合理的配置,基本政策:盡可能将對象預留在新生代,減少老年代的GC次數。除了可以設定新生代的絕對大小(-Xmn),還可以使用(-XX:NewRatio)設定新生代和老年代的比例:-XX:NewRatio=老年代/新生代。

堆溢出處理

在java程式運作的過程中,如果堆空間不足,則會抛出記憶體溢出的錯誤(Out of Memory)OOM,一旦這類問題發生在生産環境,可能引起嚴重的業務中斷,java虛拟機提供了-XX:+HeapDumpOnOutOfMemoryError,使用該參數可以在記憶體溢出時導出整個堆資訊,與之配合使用的參數是-XX:HeapDumpPath,可以設定導出堆溢出時快照資訊。記憶體分析工具:Eclipse Memory Analyzer

public class Test03 {

	public static void main(String[] args) {
		//-Xms2m -Xmx2m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/Test03.dump
		Vector v = new Vector();
		for(int i = 0; i < 5; i++){
			v.add(new Byte[1*1024*1024]);
		}
	}

}
/*
列印結果:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to d:/Test03.dump ...
Heap dump file created [1218914 bytes in 0.009 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.jt.jvm.Test03.main(Test03.java:11)
 * */
           

棧配置

java虛拟機提供了參數-Xss來指定線程的最大棧空間,整個參數業務直接決定了函數可調用的最大深度。

public class Test04 {

	//-Xss1m
	//-Xss5m
	
	//棧調用深度
	private static int count;
	
	public static void recursion(){
		count++;
		recursion();
	}
	
	public static void main(String[] args) {
		try{
			recursion();
		} catch (Throwable t){
			System.out.println("調用最大深度: " + count);
			t.printStackTrace();
		}
	}
}
/*
列印結果:

調用最大深度: 22327
java.lang.StackOverflowError
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
	at com.jt.jvm.Test04.recursion(Test04.java:13)
 * */

           

方法區

和java堆一樣,方法去是一塊所有線程共享的記憶體區域,它用于儲存系統的類資訊,方法區(永久帶)可以儲存多少資訊可以對其進行配置,在預設情況下,-XX:MaxPerSize為64MB,如果系統運作時産生大量的類,就需要設定一個相對合适的方法區,以避免出現永久區記憶體溢出問題。-XX:PermSize=64M -XX:MaxPermSize=64M。

直接記憶體配置

直接記憶體也是java程式中非常重要的組成部分,特别是廣泛的NIO中,直接記憶體跳過java堆,使java程式可以直接通路原生堆空間,是以在一定程度上加快了記憶體空間的通路速度。但是說直接記憶體一定就可以提高記憶體通路速度也不見得,具體情況具體分析。相關配置參數:

  • -XX:MaxDirectMemorySize:如果不設定預設值為最大堆空間,即-Xmx。直接記憶體使用達到上限是,就會觸發垃圾收集,如果不能有效的釋放空間,也會引起系統的OOM。

垃圾回收概念和算法、及對象的分代轉換

垃圾回收概念和其算法

垃圾回收(Garbage Collection,簡稱GC),GC中的垃圾,特指存于記憶體中、不會再被使用的對象,而回收就是相當于把垃圾“倒掉”。垃圾回收有很多種算法:如引用計數法、标記壓縮法、複制算法、分代、分區思想。

垃圾收集算法

  • 引用計數法:比較古老而經典的垃圾收集算法,其核心就是在對象被其他所引用時計數器加1,而當引用時效時則減1,但是這種方式有非常嚴重的問題:無法處理循環引用的情況、還有就是每次進行加減操作比較浪費系統性能。
  • 标記清除法:就是分為标記和清除兩個階段進行處理記憶體中的對象,當然這種方式也有非常大的弊端,就是空間碎片問題,垃圾回收後的空間不是連續的,不連續的記憶體空間的工作效率要低于連續的記憶體空間。
  • 複制算法:其核心思想就是将記憶體空間分為兩塊,每次隻使用其中一塊,在垃圾回收時,将正在使用的記憶體中的存留對象複制到未被使用的記憶體塊中,之後去清除之前正在使用的記憶體塊中所有的對象,反複去交換兩個記憶體的角色,完成垃圾收集(JVM中新生代的from和to空間就是使用這個算法)。
  • 标記壓縮法:标記壓縮法在标記清除法基礎之上做了優化,把存活的對象壓縮到記憶體一端,而後進行垃圾清理(JVM中老年代使用的就是标記壓縮法)。
  • 分代算法:就是根據對象的特點把記憶體分成N塊,而後根據每個記憶體的特點用不同的算法。對于新生代和老年代來說,新生代回收頻率很高,但是每次回收耗時很短,而老年代回收頻率低,但是耗時相對較長,是以應該盡量減少老年代的GC。
  • 分區算法:其主要就是将記憶體分為N個小的獨立空間,每個小空間都可以獨立使用,這樣細粒度的控制一次回收多少個和回收哪些小空間,而不是對整個空間進行GC,進而提升性能,并減少GC的停頓時間。

垃圾回收時的停頓現象

垃圾回收器的任務是識别和回收垃圾對象進行記憶體清理,為了讓垃圾回收器可高效的執行,大部分情況下,會要求系統進入一個停頓的狀态。停頓的目的是終止所有應用線程,隻有這樣系統才不會有新的垃圾産生,同時停頓保證了系統狀态在某一個瞬間的一緻性,也有益于更好地标記垃圾對象。是以在垃圾回收時,都會産生應用程式的停頓。

對象如何進入老年代

一般而言對象首次建立都會被放置在新生代的eden區,如果沒有GC介入,則對象不會離開eden區,那麼eden區的對象如何進入老年代?一般而言,隻要對象的年齡達到一定的大小,就會自動離開新生代進入老年代,對象年齡是由對象經曆的GC次數決定的,在新生代每次GC之後,如果對象沒有被回收則年齡加1,虛拟機提供了一個參數來控制新生代對象的最大年齡,當超過這個年齡範圍就會晉升到老年代(-XX:MaxTenuringThreshold,預設情況下為15)。

public class Test05 {

	public static void main(String[] args) {
		//測試進入老年代
		//參數:-Xmx1024M -Xms1024M -XX:+UseSerialGC -XX:MaxTenuringThreshold=15 -XX:+PrintGCDetails
		//-XX:+PrintHeapAtGC
		for(int i= 0; i < 20; i++){
			for(int j = 0; j < 300; j++){
				byte[] b = new byte[1024*1024];
			}
		}
	}

}
/*
列印結果:

[GC (Allocation Failure) [DefNew: 279004K->526K(314560K), 0.0015339 secs] 279004K->526K(1013632K), 0.0015796 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279475K->525K(314560K), 0.0012435 secs] 279475K->525K(1013632K), 0.0012833 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279481K->525K(314560K), 0.0009651 secs] 279481K->525K(1013632K), 0.0010096 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279495K->525K(314560K), 0.0008946 secs] 279495K->525K(1013632K), 0.0009284 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279504K->525K(314560K), 0.0009202 secs] 279504K->525K(1013632K), 0.0009519 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279510K->525K(314560K), 0.0008993 secs] 279510K->525K(1013632K), 0.0009322 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279514K->525K(314560K), 0.0008899 secs] 279514K->525K(1013632K), 0.0009224 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279516K->525K(314560K), 0.0008523 secs] 279516K->525K(1013632K), 0.0008822 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279518K->525K(314560K), 0.0008304 secs] 279518K->525K(1013632K), 0.0008574 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279519K->525K(314560K), 0.0009348 secs] 279519K->525K(1013632K), 0.0009664 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279519K->525K(314560K), 0.0008783 secs] 279519K->525K(1013632K), 0.0009078 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279520K->525K(314560K), 0.0009014 secs] 279520K->525K(1013632K), 0.0009318 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279520K->525K(314560K), 0.0008655 secs] 279520K->525K(1013632K), 0.0008963 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279520K->525K(314560K), 0.0009985 secs] 279520K->525K(1013632K), 0.0010280 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279520K->525K(314560K), 0.0009160 secs] 279520K->525K(1013632K), 0.0009519 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 279520K->0K(314560K), 0.0010939 secs] 279520K->525K(1013632K), 0.0011251 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 278995K->0K(314560K), 0.0056553 secs] 279520K->525K(1013632K), 0.0056895 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [DefNew: 278995K->0K(314560K), 0.0002506 secs] 279520K->525K(1013632K), 0.0002831 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 278995K->0K(314560K), 0.0002425 secs] 279520K->525K(1013632K), 0.0002750 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 278995K->0K(314560K), 0.0002339 secs] 279520K->525K(1013632K), 0.0002651 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 278995K->0K(314560K), 0.0002292 secs] 279520K->525K(1013632K), 0.0002600 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [DefNew: 278995K->0K(314560K), 0.0002638 secs] 279520K->525K(1013632K), 0.0002942 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 314560K, used 23463K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000)
  eden space 279616K,   8% used [0x00000000c0000000, 0x00000000c16e9f98, 0x00000000d1110000)
  from space 34944K,   0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000)
  to   space 34944K,   0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000)
 tenured generation   total 699072K, used 525K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000)
   the space 699072K,   0% used [0x00000000d5550000, 0x00000000d55d3720, 0x00000000d55d3800, 0x0000000100000000)
 Metaspace       used 2596K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 288K, capacity 386K, committed 512K, reserved 1048576K
 * */
           

總結:根據設定MaxTenuringThreshold參數,可以指定新生代對象經過多少次回收後進入老年代。另外,大對象(新生代eden區無法裝入時,也會直接進入老年代)。JVM裡有個參數可以設定對象的大小超過指定的大小之後,直接晉升老年代(-XX:PretenureSizeThreshold)。

public class Test06 {

	public static void main(String[] args) {
		//-XX:PretenureSizeThreshold = 1024*1000 < 1024*1024
		//參數:-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1024000
		/*Map<Integer,byte[]> map = new HashMap<>();
		for(int i=0; i < 5; i++){
			byte[] b = new byte[1024*1024];//1M
			map.put(i, b);
		}*/
				
	}

}
/*
列印結果:

Heap
 def new generation   total 9216K, used 983K [0x00000000fe200000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 8192K,  12% used [0x00000000fe200000, 0x00000000fe2f5fb8, 0x00000000fea00000)
  from space 1024K,   0% used [0x00000000fea00000, 0x00000000fea00000, 0x00000000feb00000)
  to   space 1024K,   0% used [0x00000000feb00000, 0x00000000feb00000, 0x00000000fec00000)
 tenured generation   total 20480K, used 5120K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
   the space 20480K,  25% used [0x00000000fec00000, 0x00000000ff100050, 0x00000000ff100200, 0x0000000100000000)
 Metaspace       used 2597K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 288K, capacity 386K, committed 512K, reserved 1048576K
  */
           
public class Test06 {

	public static void main(String[] args) {
		//這種現象原因:虛拟機對于體積不大的對象會優先把資料配置設定到TLAB區域中,是以就失去了老年代的配置設定機會
		//參數:-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000 -XX:-UseTLAB(增加此參數後優先達到指定大小優先老年代配置設定)
		Map<Integer,byte[]> map = new HashMap<>();
		for(int i=0; i < 5*1024; i++){
			byte[] b = new byte[1024];//1k
			map.put(i, b);
		}
	}

}
/*
 列印結果2(未增加-XX:-UseTLAB):
 
 Heap
 def new generation   total 9216K, used 6587K [0x00000000fe200000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 8192K,  80% used [0x00000000fe200000, 0x00000000fe86ef90, 0x00000000fea00000)
  from space 1024K,   0% used [0x00000000fea00000, 0x00000000fea00000, 0x00000000feb00000)
  to   space 1024K,   0% used [0x00000000feb00000, 0x00000000feb00000, 0x00000000fec00000)
 tenured generation   total 20480K, used 0K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
   the space 20480K,   0% used [0x00000000fec00000, 0x00000000fec00000, 0x00000000fec00200, 0x0000000100000000)
 Metaspace       used 2610K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 288K, capacity 386K, committed 512K, reserved 1048576K
  
 列印結果3(增加-XX:-UseTLAB):

Heap
 def new generation   total 9216K, used 801K [0x00000000fe200000, 0x00000000fec00000, 0x00000000fec00000)
  eden space 8192K,   9% used [0x00000000fe200000, 0x00000000fe2c86a0, 0x00000000fea00000)
  from space 1024K,   0% used [0x00000000fea00000, 0x00000000fea00000, 0x00000000feb00000)
  to   space 1024K,   0% used [0x00000000feb00000, 0x00000000feb00000, 0x00000000fec00000)
 tenured generation   total 20480K, used 5421K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
   the space 20480K,  26% used [0x00000000fec00000, 0x00000000ff14b668, 0x00000000ff14b800, 0x0000000100000000)
 Metaspace       used 2609K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 288K, capacity 386K, committed 512K, reserved 1048576K
 * */
           

總結:使用PretenureSizeThreshold可以進行指定進入老年代的對象的大小,但是要注意TLAB區域優先配置設定空間。

TLAB

TLAB全稱是Thread Local Allocation Buffer即線程本地配置設定緩存,從名字上看是一個線程專用的記憶體配置設定區域,是為了加速對象配置設定而産生的。每一個線程都會産生一個TLAB,該線程獨享的工作區域,java虛拟機使用這種TLAB區來避免多線程沖突問題,提高了對象配置設定的效率。TLAB空間一般不會太大,當大對象無法在TLAB配置設定時,則會直接配置設定到堆上。

  • -XX:+UseTLAB:使用TLAB。
  • -XX:+TLABSize:設定TLAB大小。
  • -XX:TLABRefillWasterFraction:設定維護進入TLAB空間的單個對象大小,他是一個比值,預設為64,即如果對象大于整個空間的1/64,則會在堆上建立對象。
  • -XX:+PrintTLAB:檢視TLAB資訊。
  • -xx:ResizeTLAB:自調整TLABRefillWasterFraction閥值。

對象建立流圖

一個對象建立在什麼位置,我們的JVM會有一個比較細節的流程,根據資料的大小,參數的設定,決定如何建立配置設定,以及其位置。

JVM-基本概念

垃圾收集器

在java中,垃圾收集器不僅僅隻有一種,什麼情況下該使用哪種,對性能又有什麼影響,這都是我們需要了解的。

  • 串行垃圾回收器
  • 并行垃圾回收器
  • CMS回收器
  • G1回收器

串行回收器

串行回收器是指使用單線程進行垃圾回收的回收器。每次回收時,串行回收器隻有一個工作線程,對于并行能力較弱的計算機來說,串行回收器的專注性和獨占性往往有更好的性能表現。串行回收器可以在新生代和老年代使用,根據作用于不同的堆空間,分為新生代串行回收器和老年代串行回收器。使用-XX:+UseSerialGC參數可以設定使用新生代串行回收器和老年代串行回收器。

并行回收器(ParNew回收器)

并行垃圾回收器在串行回收器基礎上做了改進,它可以使用多個線程同時進行垃圾回收,對于計算能力強的計算機而言,可以有效的縮短垃圾回收所需的實際時間。ParNew回收器是一個工作在新生代的垃圾收集器,它隻是簡單的将串行回收器多線程話,它的回收政策和算法和串行回收器一樣。使用-XX:+UseParNewGC,新生代ParNew回收器,老年代則使用串行回收器ParNew回收器工作是的線程數量可以使用-XX:ParallelGCThreads參數指定,一般最好和計算機的CPU相當避免過多的線程影響性能。

并行回收器(ParallelGC回收器)

新生代ParallelGC回收器,使用了複制算法的手機器,也是多線程獨占形式的收集器,但ParallelGC回收器有個非常重要的特點,就是它非常關注系統的吞吐量。提供了兩個非常關鍵的參數來控制系統的吞吐量。

  • -XX:MaxGCPauseMills:設定最大垃圾收集停頓時間,可用把虛拟機在GC停頓的時間控制在MaxGCPauseMills範圍内,如果希望減少GC停頓時間可以将MaxGCPauseMills設定得很小,但是會導緻頻繁GC,進而增加GC的總時間,降低了吞吐量。是以根據實際情況設定該值。
  • -XX:GCTimeRatio:設定吞吐量大小,它是一個0到100之間的整數,預設情況下他的取值是99,那麼系統将花費不超過1/(1+n)的時間用于垃圾回收,也就是1/(1+99)=1%的時間。

    另外還可以指定-XX:UseAdaptiveSizePollicy打開自适應模式,在這種模式下,新生代的大小、eden、from/to的比例,以及晉升老年代的對象年齡參數會被自動調整,以達到在堆大小、吞吐量和停頓時間之間的平衡點。

并行回收器(ParallelOldGC回收器)

老年代ParallelOldGC回收器也是一種多線程的回收器,和新生代的ParallelGC回收器一樣,也是一種關注吞吐量的回收器,它使用了标記壓縮算法進行實作。

  • -XX:+UseParallelOldGC進行設定。
  • -XX:+ParallelGCThreads也可以設定垃圾收集時的線程數量。

CMS回收器

CMS全稱為:Concurrent Mark Sweep意為并發标記清除,它使用的是标記清除法,主要關注系統停頓時間。使用-XX:+UseConcMarkSweepGC進行設定。使用-XX:ConcGCThreads設定并發線程數量。CMS并不是獨占的回收器也就是說CMS回收過程中,應用程式仍然在不停的工作,又會有新的垃圾不斷産生,所有在使用CMS的過程中應該確定應用程式的記憶體足夠可同。CMS不會等到應用程式飽和的時候才去回收垃圾,二十在某一個閥值的時候開始回收,預設為69,也就是說當老年代的空間使用率達到68%的時候,會執行CMS回收。如果記憶體使用率增長很快,在CMS執行的過程中,已經出現了記憶體不足的情況,此時CMS回收就會失敗,虛拟機将啟動老年代串行回收器進行垃圾回收,這會導緻應用程式中斷,直到垃圾回收完成之後才會正常工作,這個過程GC的停頓時間可能較長,是以-XX:CMSInitiatingOccupancyFraction的設定要根據實際的情況。之前我們在學習算法的時候說過,标記清除法有個缺點就是存在記憶體碎片的問題,那麼CMS有個參數設定-XX:+UseCMSCompactAtFullCollection可以使CMS回收完成之後進行一次碎片整理,-XX:CMSFullGCsBeforeCompaction參數可以設定進行多少次CMS回收之後,對記憶體進行一次壓縮。

G1回收器

G1回收器(Garbage-First)是在jdk1.7中提出的垃圾回收器,從長期目标來看是為了取代CMS回收器,G1回收器擁有獨特的垃圾回收政策,G1屬于分代垃圾回收器,區分新生代和老年代,依然有eden區和from/to區,它并不要求整個eden區或者整個新生代、老年代的空間都連續,它使用了分區算法。

  • 并行性:G1回收期間可多線程同時工作。
  • 并發性:G1擁有與程式交替執行的能力,部分工作可與應用程式同時執行,在整個GC期間不會完全阻塞應用程式。
  • 分代GC:G1依然是一個分代收集器,但是他是兼顧新生代和老年代一起工作,之前的垃圾收集器或者是在新生代工作,或者是在老年代工作,是以這是一個很大的不同。
  • 空間整理:G1在回收過程中,不會像CMS那樣在若幹次GC後需要進行碎片整理,G1采用了有效的複制對象的方式,減小空間碎片。
  • 可預見性:由于分區的原因,G1可以隻選取部分分區進行回收,縮小回收的範圍,提升性能。
  • 使用-XX:+UseG1GC應用G1收集器
  • 使用-XX:MaxGCPauseMillis指定最大停頓時間。
  • 使用-XX:ParallelGCThreads設定并回收的線程數量。