JVM學習03-常用Java虛拟機參數
一、垃圾回收日志參數
-
-XX:+PrintGC
列印簡單GC日志
隻要GC就會列印日志。
[GC 1023K->565K(5632K), 0.0012699 secs] #日志說明:GC前堆空間使用量為1023K,GC後堆空間使用量為565K,目前可用堆空間的總和為5632K,本次GC時間為0.0012699 secs
-
列印詳細GC日志-XX:+PrintGCDetails
[GC[DefNew:9791K->9791K(9792K),0.0000350 secs][Tenured:16632K->13533K(21888K),0.4063120 secs] 26424K->13533k(31680K),[Perm : 2583k->2583k(21248K)],0.4064710 secs [Times:user=0.41 sys=0.00, real=0.40 secs]] #日志說明: #[DefNew:9791K->9791K(9792K),0.0000350 secs] 新生代回收 #[Tenured:16632K->13533K(21888K),0.4063120 secs] 老年代回收 #26424K->13533k(31680K) 堆回收:由GC前的26M到GC後的13M,堆總可用變為31M,但是這裡要注意,堆回收了13M,但是老年代隻回收了3M,剩下的其實是新生代的記憶體回收,雖然日志裡面顯示着新生代沒有回收,但是實際是被清空了的。 #[Perm : 2583k->2583k(21248K)] 永久區回收 #以上的日志是書上的日志,我自己的沒有老年代和永久區的日志,而且年輕代的名稱也不是DefNew,而是PSYoungGen [GC (Allocation Failure) [PSYoungGen: 2047K->512K(2560K)] 2047K->520K(9728K), 0.0009979 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 6 Heap PSYoungGen total 2560K, used 1243K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000) eden space 2048K, 35% used [0x00000007bfd00000,0x00000007bfdb6e98,0x00000007bff00000) from space 512K, 100% used [0x00000007bff00000,0x00000007bff80000,0x00000007bff80000) to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) ParOldGen total 7168K, used 8K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000) object space 7168K, 0% used [0x00000007bf600000,0x00000007bf602000,0x00000007bfd00000) Metaspace used 2709K, capacity 4486K, committed 4864K, reserved 1056768K class space used 289K, capacity 386K, committed 512K, reserved 1048576K
-
分别在每次GC前後分别列印堆資訊。效果如下-XX:+PrintHeapAtGC
{Heap before GC invocations=1 (full 0): PSYoungGen total 2560K, used 2047K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000) eden space 2048K, 99% used [0x00000007bfd00000,0x00000007bfeffff0,0x00000007bff00000) from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) to space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000) ParOldGen total 7168K, used 0K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000) object space 7168K, 0% used [0x00000007bf600000,0x00000007bf600000,0x00000007bfd00000) Metaspace used 2700K, capacity 4486K, committed 4864K, reserved 1056768K class space used 288K, capacity 386K, committed 512K, reserved 1048576K [GC (Allocation Failure) [PSYoungGen: 2047K->512K(2560K)] 2047K->520K(9728K), 0.0007641 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] Heap after GC invocations=1 (full 0): PSYoungGen total 2560K, used 512K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000) eden space 2048K, 0% used [0x00000007bfd00000,0x00000007bfd00000,0x00000007bff00000) from space 512K, 100% used [0x00000007bff00000,0x00000007bff80000,0x00000007bff80000) to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) ParOldGen total 7168K, used 8K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000) object space 7168K, 0% used [0x00000007bf600000,0x00000007bf602000,0x00000007bfd00000) Metaspace used 2700K, capacity 4486K, committed 4864K, reserved 1056768K class space used 288K, capacity 386K, committed 512K, reserved 1048576K }
-
輸出GC的發生時間,時間為虛拟機啟動後的時間偏移量。相當于-XX:+PrintGCTimeStamps
加了個時間-XX:+PrintGCDetails
# 這個0.123就是時間偏移量,虛拟機啟動後0.123秒發生了GC 0.123: [GC (Allocation Failure) [PSYoungGen: 2047K->512K(2560K)] 2047K->520K(9728K), 0.0015470 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 7 Heap PSYoungGen total 2560K, used 1353K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000) eden space 2048K, 41% used [0x00000007bfd00000,0x00000007bfdd25e8,0x00000007bff00000) from space 512K, 100% used [0x00000007bff00000,0x00000007bff80000,0x00000007bff80000) to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) ParOldGen total 7168K, used 8K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000) object space 7168K, 0% used [0x00000007bf600000,0x00000007bf602000,0x00000007bfd00000) Metaspace used 2708K, capacity 4486K, committed 4864K, reserved 1056768K class space used 289K, capacity 386K, committed 512K, reserved 1048576K
-
列印應用程式的執行時間-XX:+PrintGCApplicationConcurrentTime
-
列印應用程式由于GC而産生的停頓時間。-XX:+PrintGCApplicationStoppedTime
-
跟蹤系統内的軟引用、弱引用、虛引用Finallize隊列。-XX:+PrintReferenceGC
0.115: Application time: 0.0382099 seconds 0.115: [GC (Allocation Failure) 0.116: [SoftReference, 0 refs, 0.0000373 secs]0.116: [WeakReference, 9 refs, 0.0000084 secs]0.116: [FinalReference, 62 refs, 0.0000349 secs]0.116: [PhantomReference, 0 refs, 0 refs, 0.0000195 secs]0.116: [JNI Weak Reference, 0.0000093 secs][PSYoungGen: 2047K->496K(2560K)] 2047K->528K(9728K), 0.0018549 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 0.116: Total time for which application threads were stopped: 0.0019804 seconds, Stopping threads took: 0.0000121 seconds
Java從1.2版本開始引入了4種引用,這4種引用的級别由高到低依次為:
強引用 > 軟引用 > 弱引用 > 虛引用
(1)強引用(StrongReference)
強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當記憶體空間不足,Java虛拟機甯願抛出OutOfMemoryError錯誤,使程式異常終止,也不會靠随意回收具有強引用的對象來解決記憶體不足的問題。
(2)軟引用(SoftReference)
如果一個對象隻具有軟引用,則記憶體空間足夠,垃圾回收器就不會回收它;如果記憶體空間不足了,就會回收這些對象的記憶體。隻要垃圾回收器沒有回收它,該對象就可以被程式使用。軟引用可用來實作記憶體敏感的高速緩存。
軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛拟機就會把這個軟引用加入到與之關聯的引用隊列中。
(3)弱引用(WeakReference)
弱引用與軟引用的差別在于:隻具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的記憶體區域的過程中,一旦發現了隻具有弱引用的對象,不管目前記憶體空間足夠與否,都會回收它的記憶體。不過,由于垃圾回收器是一個優先級很低的線程,是以不一定會很快發現那些隻具有弱引用的對象。
弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛拟機就會把這個弱引用加入到與之關聯的引用隊列中。
(4)虛引用(PhantomReference)
“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
虛引用主要用來跟蹤對象被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個差別在于:虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的記憶體之前,把這個虛引用加入到與之 關聯的引用隊列中。
(5)FinalReference
對于重載了 Object 類的 finalize 方法的類執行個體化的對象(這裡稱為 f 對象),JVM 為了能在 GC 對象時觸發 f 對象的 finalize 方法的調用,将每個 f 對象包裝生成一個對應的FinalReference 對象,友善 GC 時進行處理。
FinalReference說明:FinalReference
-
指定日志目錄。-Xloggc
-Xloggc:log/gc.log
二、類加載/制裁的跟蹤
-
跟蹤類的加載和解除安裝-verbos:class
跟蹤類加載,動态類的加載非常隐蔽,它們由代碼邏輯控制,不出現在檔案系統中,跟蹤這些類,就需要使用-XX:+TraceClassLoading
等參數來觀察系統實際使用的類。-XX:+TraceClassLoading
跟蹤類的解除安裝-XX:+TraceClassUnloading
=-verbos:class
+-XX:+TraceClassLoading
測試代碼:-XX:+TraceClassUnloading
package cn.shutdown.demo.jvm.trace; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * -XX:+TraceClassUnloading -XX:+TraceClassLoading * <p> * -verbose:class * * @author Dmn */ public class UnloadClass implements Opcodes { public static void main(String args[]) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { //ClassWriter 以位元組碼的形式生成類的類通路器, 參數 // ClassWriter.COMPUTE_MAXS 如果必須自動計算最大堆棧大小和局部變量數,則為true 。 // ClassWriter.COMPUTE_FRAMES 如果堆棧映射幀必須從頭開始重新計算。 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); // 定義了一個 基于jdk1.7的 public類型的類,名為Example,繼承于Object cw.visit(V1_7, ACC_PUBLIC, "Example", null, "java/lang/Object", null); //定義了一個構造方法 MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mw.visitVarInsn(ALOAD, 0); mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); mw.visitInsn(RETURN); mw.visitMaxs(0, 0); mw.visitEnd(); // 生成main方法中的位元組碼指令 mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); //擷取該方法 mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); //加載字元串參數 mw.visitLdcInsn("Hello world!"); //調用該方法 mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"); mw.visitInsn(RETURN); mw.visitMaxs(0, 0); mw.visitEnd(); //生成class檔案對應的二進制流 byte[] code = cw.toByteArray(); System.out.println("\n\n================================================"); for (int i = 0; i < 10; i++) { //建立類加載器 UnloadClassLoader loader = new UnloadClassLoader(); //擷取了 ClassLoader類的 defineClass方法對象 Method m = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); //設定方法的通路權限為可通路 m.setAccessible(true); //調用 loader對象的 defineClass方法 //ClassLoader的defineClass方法的作用是:将位元組數組轉換為類Class的執行個體 //這樣就可以将 剛剛生成的class檔案的二進制流加載并轉化為Example類的執行個體 m.invoke(loader, "Example", code, 0, code.length); m.setAccessible(false); System.gc(); } } }
需要引用的pom依賴package cn.shutdown.demo.jvm.trace; public class UnloadClassLoader extends ClassLoader { }
<dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>5.0.4</version> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm-commons</artifactId> <version>5.0.4</version> </dependency>
關于ASM 參考文章,寫的非常清晰 Java技術專題-JVM研究系列(3)ASM庫生成和修改class檔案
運作後的效果:
可以看出日志輸出中有引的加載和解除安裝的日志記錄
[Opened /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] [Loaded java.lang.Object from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] [Loaded java.io.Serializable from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] [Loaded java.lang.Comparable from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 。。。省略部分輸出。。。 [Loaded cn.shutdown.demo.jvm.trace.UnloadClassLoader from file:/Users/dmn/IdeaProjects/demo/target/classes/] [Loaded java.lang.ClassFormatError from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] [Loaded java.io.IOException from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] [Loaded java.lang.AssertionStatusDirectives from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] [Loaded sun.reflect.NativeMethodAccessorImpl from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] [Loaded sun.reflect.DelegatingMethodAccessorImpl from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] #這裡就開始循環中的輸出了,從JVM定義的類中加載了Example [Loaded Example from __JVM_DefineClass__] [Loaded Example from __JVM_DefineClass__] #從JVM定義的類中卸Example [Unloading class Example 0x00000007c006a028] [Loaded Example from __JVM_DefineClass__] [Unloading class Example 0x00000007c006a828] [Loaded Example from __JVM_DefineClass__] [Unloading class Example 0x00000007c006a028] 。。。活力部分輸出。。。 [Loaded java.lang.Shutdown from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] [Loaded java.lang.Shutdown$Lock from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
三、系統參數檢視
-
列印虛拟機接受到的指令行的顯式參數-XX:+PrintVMOptions
-
列印傳遞給虛拟機的顯式和隐式參數(隐式參數未必通過指令行給出 可能由虛拟機自行設定)-XX:+PrintCommandLineFlags
-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+PrintClassHistogram - XX:+PrintCommandLineFlags -XX:+PrintFlagsFinal -XX:+PrintVMOptions -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC #-XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 這些都是隐式參數
-
檢視系統的詳細參數。-XX:+PrintFlagsFinal
[Global flags] intx ActiveProcessorCount = -1 {product} uintx AdaptiveSizeDecrementScaleFactor = 4 {product} uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product} uintx AdaptiveSizePausePolicy = 0 {product} uintx AdaptiveSizePolicyCollectionCostMargin = 50 {product} uintx AdaptiveSizePolicyInitializingSteps = 20 {product} 。。。。省略大部分。。。
四、堆參數配置
- 最大堆和初始堆設定
:初始堆-Xms
-Xmx
:最大堆
測試代碼:
運作結果:package cn.shutdown.demo.jvm; /** * -Xmx20m -Xms5m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC * @author dmn * @date 2021/6/7 */ public class HeapAlloc { public static void main(String[] args) { printMemory(""); //配置設定1M記憶體 byte[] b = new byte[1 * 1024 * 1024]; printMemory("配置設定了1M記憶體"); //配置設定4M記憶體 b = new byte[4 * 1024 * 1024]; printMemory("配置設定了4M記憶體"); } static void printMemory(String step) { System.out.println(step); System.out.println("maxMemory=" + Runtime.getRuntime().maxMemory() + " bytes"); System.out.println("freeMemory=" + Runtime.getRuntime().freeMemory() + " bytes"); System.out.println("toatlMemory=" + Runtime.getRuntime().totalMemory() + " bytes"); } }
在實際工作中,也可以直接将初始堆 -Xms與最大堆 -Xmx設定相等,好處是可以減少程式運作時進行垃圾回收的次數,進而提高程式的性能。-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC maxMemory=20316160 bytes freeMemory=5287536 bytes toatlMemory=6094848 bytes [GC (Allocation Failure) [DefNew: 788K->192K(1856K), 0.0012785 secs] 788K->363K(5952K), 0.0013054 secs] [Times: user=0.00 sys=0.01, real=0.00 secs] 配置設定了1M記憶體 maxMemory=20316160 bytes freeMemory=4640168 bytes toatlMemory=6094848 bytes [GC (Allocation Failure) [DefNew: 1249K->0K(1856K), 0.0016099 secs][Tenured: 1387K->1387K(4096K), 0.0015703 secs] 1420K->1387K(5952K), [Metaspace: 2697K->2697K(1056768K)], 0.0032377 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 配置設定了4M記憶體 maxMemory=20316160 bytes freeMemory=4708856 bytes toatlMemory=10358784 bytes Heap def new generation total 1920K, used 69K [0x00000007bec00000, 0x00000007bee10000, 0x00000007bf2a0000) eden space 1728K, 4% used [0x00000007bec00000, 0x00000007bec11498, 0x00000007bedb0000) from space 192K, 0% used [0x00000007bedb0000, 0x00000007bedb0000, 0x00000007bede0000) to space 192K, 0% used [0x00000007bede0000, 0x00000007bede0000, 0x00000007bee10000) tenured generation total 8196K, used 5483K [0x00000007bf2a0000, 0x00000007bfaa1000, 0x00000007c0000000) the space 8196K, 66% used [0x00000007bf2a0000, 0x00000007bf7fad20, 0x00000007bf7fae00, 0x00000007bfaa1000) Metaspace used 2703K, capacity 4486K, committed 4864K, reserved 1056768K class space used 289K, capacity 386K, committed 512K, reserved 1048576K Process finished with exit code 0 測試代碼運作的初始堆是5m,最大堆是20m,程式運作以後 1. 第一次檢視的記憶體結果 maxMemory=20316160 bytes freeMemory=5287536 bytes toatlMemory=6094848 bytes 2. 配置設定了1M記憶體以後,freeMemory減少了1M,變為4640168 bytes freeMemory=4640168 bytes 進行了一次垃圾回收的結果 [GC (Allocation Failure) [DefNew: 1249K->0K(1856K), 0.0016099 secs] 新生代可用空間為 1856K(大約是1.5M) [Tenured: 1387K->1387K(4096K), 0.0015703 secs] 老年代可用空間為 4096K (4M) 1420K->1387K(5952K) 堆的總可用空間為 5952K(近6M) [Metaspace: 2697K->2697K(1056768K)], 0.0032377 secs] 3. 配置設定4M記憶體後 因為從剛剛的GC記錄可以看到,新生代的可用空間隻有1.5M了,小于程式申請的4M空間,是以堆空間進行擴容,擴容後,總記憶體為約10M,剩餘記憶體為 4708856,約5M maxMemory=20316160 bytes freeMemory=4708856 bytes toatlMemory=10358784 bytes
- 新生代配置
-
:設定新生代大小。設定較大的新生代會減少老年代的大小,這個參數對系統性能及GC行為有很大影響,新生代大小一般設定為整個堆空間的1/3到1/4左右。-Xmn
-
:設定新生代中eden空間和from/to空間的比例關系。-XX:SurvivorRatio
含義: -XX:SurvivorRatio=eden/from=eden/to 使用方法: -XX:SurvivorRatio=2
-
:設定新生代和老年代的比例-XX:NewRatio
-XX:NewRatio=老年代/新生代
運作結果:package cn.shutdown.demo.jvm; /** * -Xmx20m -Xms20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails * @author dmn * @date 2021/6/15 */ public class NewSizeDemo { public static void main(String[] args) { byte[] b = null; for (int i = 0; i < 10; i++) { b = new byte[1 * 1024 * 1024]; } } }
Java HotSpot(TM) 64-Bit Server VM warning: NewSize (1536k) is greater than the MaxNewSize (1024k). A new max generation size of 1536k will be used. [GC (Allocation Failure) [PSYoungGen: 512K->496K(1024K)] 512K->512K(19968K), 0.0015693 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap PSYoungGen total 1024K, used 738K [0x00000007bfe80000, 0x00000007c0000000, 0x00000007c0000000) eden space 512K, 47% used [0x00000007bfe80000,0x00000007bfebc8d0,0x00000007bff00000) from space 512K, 96% used [0x00000007bff00000,0x00000007bff7c010,0x00000007bff80000) to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) ParOldGen total 18944K, used 10256K [0x00000007bec00000, 0x00000007bfe80000, 0x00000007bfe80000) object space 18944K, 54% used [0x00000007bec00000,0x00000007bf6040a0,0x00000007bfe80000) Metaspace used 2700K, capacity 4486K, committed 4864K, reserved 1056768K class space used 289K, capacity 386K, committed 512K, reserved 1048576K
- 從結果裡我們可以看到 ,青年代雖然輸出總大小為1024k,但是 eden,from,to的空間分别都為 512k,這個的原因可以看下我的另一篇文章Java HotSpot™ 64-Bit Server VM warning: NewSize (1536k) is greater than the MaxNewSize (1024k) 裡的說明,是jdk7與jdk8的差別導緻的,jdk8的青年代的最小值為1536k,因為參數給定的值是1024k,是以被預設設定為了1536k,正好是eden,from,to各512k。
另外,由于eden區無法容納任何一個程式中配置設定的1MB的數組,是以觸發了一次新生代的GC,對eden區進行了部分回收,同時,這個偏小的新生代無法為1MB數組預留白間,是以,所有的數組都配置設定在了老年代,老年代最終占用了10256K的空間。PSYoungGen total 1024K, used 738K [0x00000007bfe80000, 0x00000007c0000000, 0x00000007c0000000) eden space 512K, 47% used [0x00000007bfe80000,0x00000007bfebc8d0,0x00000007bff00000) from space 512K, 96% used [0x00000007bff00000,0x00000007bff7c010,0x00000007bff80000) to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000) ParOldGen total 18944K, used 10256K [0x00000007bec00000, 0x00000007bfe80000, 0x00000007bfe80000)
- 上述測試代碼如果使用
的JVM參數來運作的話,結果如下。-Xmx20m -Xms20m -Xmn7m -XX:SurvivorRatio=2 -XX:+PrintGCDetails
從結果看出,青年代被初始值設定為7M以後,因為配置設定1M記憶體 配置設定1M記憶體 配置設定1M記憶體 [GC (Allocation Failure) [PSYoungGen: 3900K->1520K(5632K)] 3900K->1560K(18944K), 0.0016175 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 配置設定1M記憶體 配置設定1M記憶體 配置設定1M記憶體 [GC (Allocation Failure) [PSYoungGen: 4672K->1520K(5632K)] 4712K->1568K(18944K), 0.0013520 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 配置設定1M記憶體 配置設定1M記憶體 配置設定1M記憶體 [GC (Allocation Failure) [PSYoungGen: 4663K->1520K(5632K)] 4711K->1568K(18944K), 0.0007291 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 配置設定1M記憶體 Heap PSYoungGen total 5632K, used 2626K [0x00000007bf900000, 0x00000007c0000000, 0x00000007c0000000) eden space 4096K, 27% used [0x00000007bf900000,0x00000007bfa14990,0x00000007bfd00000) from space 1536K, 98% used [0x00000007bfd00000,0x00000007bfe7c020,0x00000007bfe80000) to space 1536K, 0% used [0x00000007bfe80000,0x00000007bfe80000,0x00000007c0000000) ParOldGen total 13312K, used 48K [0x00000007bec00000, 0x00000007bf900000, 0x00000007bf900000) object space 13312K, 0% used [0x00000007bec00000,0x00000007bec0c000,0x00000007bf900000) Metaspace used 2702K, capacity 4486K, committed 4864K, reserved 1056768K class space used 289K, capacity 386K, committed 512K, reserved 1048576K Process finished with exit code 0
,是以eden區與from區比為2/1,是以空間大小為-XX:SurvivorRatio=2
每次程式配置設定會分區eden區的記憶體,配置設定三次以後,eden區的記憶體不足了,對eden區進行部分回收。由于程式每申請一次空間,也同時廢棄上一次申請的記憶體(上次申請的記憶體失去了引用),是以在新生代的GC中,有效回收了失效的内在,最終結果是:所有的内在配置設定都在新生代進行,通過GC保證了新生代有足夠的空間,而老年代沒有為這些數組預留任何空間,隻是在GC過程中,部分新生代對象晉升到老年代。eden space 4096K, 27% used [0x00000007bf900000,0x00000007bfa14990,0x00000007bfd00000) from space 1536K, 98% used [0x00000007bfd00000,0x00000007bfe7c020,0x00000007bfe80000) to space 1536K, 0% used [0x00000007bfe80000,0x00000007bfe80000,0x00000007c0000000)
- 使用參數
運作上述代碼,得到的輸出為:-Xmx20m -Xms20m -Xmn15m -XX:SurvivorRatio=2 -XX:+PrintGCDetails
這次執行中,新生代初始化15M的空間,eden區占用了12288K,滿足10M數組的配置設定 。是以所有的配置設定行為都在eden直接運作,且沒有觸發任何的GC行為,因為 from/to和老年代的使用率都為0。Heap PSYoungGen total 13824K, used 11469K [0x00000007bf100000, 0x00000007c0000000, 0x00000007c0000000) eden space 12288K, 93% used [0x00000007bf100000,0x00000007bfc336f8,0x00000007bfd00000) from space 1536K, 0% used [0x00000007bfe80000,0x00000007bfe80000,0x00000007c0000000) to space 1536K, 0% used [0x00000007bfd00000,0x00000007bfd00000,0x00000007bfe80000) ParOldGen total 5120K, used 0K [0x00000007bec00000, 0x00000007bf100000, 0x00000007bf100000) object space 5120K, 0% used [0x00000007bec00000,0x00000007bec00000,0x00000007bf100000) Metaspace used 2702K, capacity 4486K, committed 4864K, reserved 1056768K class space used 289K, capacity 386K, committed 512K, reserved 1048576K
不同的堆的分布情況,對系統執行會産生一定影響。在實際工作中,應該根據系統的特點做合理的設定,基本的政策是:盡可能将對象預留在新生代,減少老年代的GC次數。
- 使用參數
運作測試代碼,輸出如下:-Xmx20M -Xms20M -XX:NewRatio=2 -XX:+PrintGCDetails
堆大小為20M,老年代和新生代比為2:1,是以老年代大小為13824K,新生代大小為 6144K。新生代大小不夠10M的數組配置設定,是以産生新生代的GC,新生代GC時,from/to空間不足以容納任何一個1MB的數組,影響了新生代的正常回收,故新生代回收時需要老年代進行空間擔保。配置設定1M記憶體[[email protected] 配置設定1M記憶體[[email protected] 配置設定1M記憶體[[email protected] 配置設定1M記憶體[[email protected] 發生GC的時候,這個對象與引用b還有關聯,是以這個會放到from/to區,但是空間不足,是以給放到了老年代 [GC (Allocation Failure) [PSYoungGen: 5014K->512K(6144K)] 5014K->1560K(19968K), 0.0014339 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 配置設定1M記憶體[[email protected] 配置設定1M記憶體[[email protected] 配置設定1M記憶體[[email protected] 配置設定1M記憶體[[email protected] 配置設定1M記憶體[[email protected] 發生GC的時候,這個對象與引用b還有關聯,是以這個會放到from/to區,但是空間不足,是以給放到了老年代 [GC (Allocation Failure) [PSYoungGen: 5742K->496K(6144K)] 6790K->2580K(19968K), 0.0011273 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 配置設定1M記憶體[[email protected] Heap PSYoungGen total 6144K, used 1743K [0x00000007bf980000, 0x00000007c0000000, 0x00000007c0000000) eden space 5632K, 22% used [0x00000007bf980000,0x00000007bfab7df0,0x00000007bff00000) from space 512K, 96% used [0x00000007bff80000,0x00000007bfffc010,0x00000007c0000000) to space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000) ParOldGen total 13824K, used 2084K [0x00000007bec00000, 0x00000007bf980000, 0x00000007bf980000) object space 13824K, 15% used [0x00000007bec00000,0x00000007bee09030,0x00000007bf980000) Metaspace used 2703K, capacity 4486K, committed 4864K, reserved 1056768K class space used 289K, capacity 386K, committed 512K, reserved 1048576K Process finished with exit code 0
預設的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過參數 –XX:NewRatio 來指定 ,1:2隻是一個大概的值,比如說我配置設定Xms20M,輸出的比例是 6:13.5 ,Xms10M,輸出比例為2.5:7,是一個大概的1:2),即:新生代 ( Young ) = 1/3 的堆空間大小。老年代 ( Old ) = 2/3 的堆空間大小。其中,新生代 ( Young ) 被細分為 Eden 和 兩個 Survivor 區域,這兩個 Survivor 區域分别被命名為 from 和 to,以示區分。
預設的,Eden : from : to = 8 : 1 : 1 ( 可以通過參數 –XX:SurvivorRatio 來設定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。
JVM 每次隻會使用 Eden 和其中的一塊 Survivor 區域來為對象服務,是以無論什麼時候,總是有一塊 Survivor 區域是空閑着的。
是以,新生代實際可用的記憶體空間為 9/10 ( 即90% )的新生代空間。
JVM老年代和新生代的比例
-
- 堆溢出處理
在内在溢出時導出整個堆資訊-XX:+HeapDumpOnOutOfMemoryError
指定導出堆的存放路徑-XX:HeapDumpPath
-XX:OnOutOfMemoryError
出現OOM時觸發操作,用法如下,OOM時調用 printStack.sh腳本。
測試代碼:
運作結果:import java.util.ArrayList; import java.util.List; /** * * -XX:+PrintGCDetails -Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=DumpOOM.dump * @author dmn */ public class DumpOOM { public static void main(String[] args) { List l = new ArrayList<>(); for (int i = 0; i < 100; i++) { l.add(new byte[1 * 1024 * 1024]); System.out.println("配置設定了1M記憶體"); } } }
以上的運作結果,我沒看懂的一點就是,為什麼老年代的空間是 13824k,按說,初始化的堆記憶體是5m,這樣新生代的預設記憶體是1.5M,新老比1:2,老年代就是3M左右,然後程式運作以後,因為新生代的eden區是1024K、from區和to區是512K,理論上裝不下 1M的對象,就把對象直接給幹到了老年代去了,按輸出結果看出來老年代空間是13824k,是以裝了13個1M的對象以後,就裝不下了,就OOM了,但是有個問題啊,新生代是1.5M,老年代是14M,加起來也才 16M,如果算上那個Metaspace的 2.7M的話,倒是大概能有個20M的記憶體,但是方法區/中繼資料是所有線程共享的記憶體區域,用于儲存系統的類資訊,類的字段、方法、常量池等。是與堆、棧并列存在的一塊記憶體區域,這塊的記憶體應該不會算在堆記憶體的20M裡面的,那少的那4M左右的記憶體去哪了呢?後面再把這個坑填上。配置設定了1M記憶體 配置設定了1M記憶體 配置設定了1M記憶體 配置設定了1M記憶體 配置設定了1M記憶體 配置設定了1M記憶體 配置設定了1M記憶體 配置設定了1M記憶體 配置設定了1M記憶體 配置設定了1M記憶體 配置設定了1M記憶體 配置設定了1M記憶體 配置設定了1M記憶體 配置設定了1M記憶體 [GC (Allocation Failure) [PSYoungGen: 765K->512K(1536K)] 14077K->13856K(15360K), 0.0006640 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 512K->480K(1536K)] 13856K->13832K(15360K), 0.0006287 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 480K->0K(1536K)] [ParOldGen: 13352K->13674K(13824K)] 13832K->13674K(15360K), [Metaspace: 2696K->2696K(1056768K)], 0.0040420 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 13674K->13674K(15360K), 0.0007653 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 13674K->13662K(13824K)] 13674K->13662K(15360K), [Metaspace: 2696K->2696K(1056768K)], 0.0048541 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] java.lang.OutOfMemoryError: Java heap space Dumping heap to DumpOOM.dump ... Heap dump file created [14589700 bytes in 0.025 secs] Heap PSYoungGen total 1536K, used 31K [0x00000007bf980000, 0x00000007bfc80000, 0x00000007c0000000) eden space 1024K, 3% used [0x00000007bf980000,0x00000007bf987c68,0x00000007bfa80000) from space 512K, 0% used [0x00000007bfa80000,0x00000007bfa80000,0x00000007bfb00000) to space 512K, 0% used [0x00000007bfc00000,0x00000007bfc00000,0x00000007bfc80000) ParOldGen total 13824K, used 13662K [0x00000007bec00000, 0x00000007bf980000, 0x00000007bf980000) object space 13824K, 98% used [0x00000007bec00000,0x00000007bf957930,0x00000007bf980000) Metaspace used 2727K, capacity 4486K, committed 4864K, reserved 1056768K class space used 292K, capacity 386K, committed 512K, reserved 1048576K
五、非堆記憶體的參數配置
-
方法區配置
jdk1.6、jdk1.7等版本
初始永久區大小-XX:PermSize
-XX:MaxPermSize
配置最大永久區大小
jdk1.8 永久區移除,改為中繼資料區存放類的中繼資料,預設情況下,中繼資料區隻受系統可用記憶體限制,可以使用
指定永久區的最大可用值。-XX:MaxMetaspaceSize
- 棧配置
指定線程棧大小-Xss
- 直接記憶體配置
設定最大可用直接記憶體,如不設定 ,預設值為最大堆空間,即-Xmx,當直接記憶體使用量達到-XX:MaxDirectMemorySize
-XX:MaxDirectMemorySize
時,會觸發垃圾回收
直接記憶體适合申請次數較少,通路較頻繁的場合,如果記憶體空間本身需要頻繁申請,則不适合使用直接記憶體。
通路頻繁場合的測試代碼:
頻繁申請記憶體場合測試代碼:import java.nio.ByteBuffer; /** * -server 模式下 差異明顯 */ public class AccessDirectBuffer { public static void main(String[] args) { AccessDirectBuffer alloc = new AccessDirectBuffer(); alloc.bufferAccess(); alloc.directAccess(); alloc.bufferAccess(); alloc.directAccess(); } /**直接記憶體通路*/ public void directAccess() { long starttime = System.currentTimeMillis(); //申請500個位元組的直接記憶體 ByteBuffer b = ByteBuffer.allocateDirect(500); for (int i = 0; i < 100000; i++) { for (int j = 0; j < 99; j++) //存 b.putInt(j); //翻轉 b.flip(); for (int j = 0; j < 99; j++) //取 b.getInt(); b.clear(); } long endtime = System.currentTimeMillis(); System.out.println("testDirectWrite:" + (endtime - starttime)); } /**堆記憶體通路*/ public void bufferAccess() { long starttime = System.currentTimeMillis(); //申請 500個位元組的記憶體空間 ByteBuffer b = ByteBuffer.allocate(500); for (int i = 0; i < 100000; i++) { for (int j = 0; j < 99; j++) //存 b.putInt(j); //翻轉 b.flip(); for (int j = 0; j < 99; j++) //取 b.getInt(); b.clear(); } long endtime = System.currentTimeMillis(); System.out.println("testBufferWrite:" + (endtime - starttime)); } }
import java.nio.ByteBuffer; /** * 直接記憶體配置設定較慢 */ public class AllocDirectBuffer { public static void main(String[] args) { AllocDirectBuffer alloc = new AllocDirectBuffer(); alloc.bufferAllocate(); alloc.directAllocate(); alloc.bufferAllocate(); alloc.directAllocate(); } public void directAllocate() { long starttime = System.currentTimeMillis(); for (int i = 0; i < 200000; i++) { //申請直接記憶體 ByteBuffer b = ByteBuffer.allocateDirect(1000); } long endtime = System.currentTimeMillis(); System.out.println("directAllocate:" + (endtime - starttime)); } public void bufferAllocate() { long starttime = System.currentTimeMillis(); for (int i = 0; i < 200000; i++) { //申請堆記憶體 ByteBuffer b = ByteBuffer.allocate(1000); } long endtime = System.currentTimeMillis(); System.out.println("bufferAllocate:" + (endtime - starttime)); } }
六、虛拟機工作模式
虛拟機系統會根據目前計算機環境自動選擇運作模式,使用
-version
參數可以檢視目前的模式
-client
Client模式
-server
Server模式,與Client模式比, Server模式啟動比較慢,會收集更多系統性能資訊,使用更複雜的優化算法對程式進行優化。系統啟動後執行速度遠快于Client模式。
使用
-XX:+PrintFlagsFinal
參數檢視Client模式和Server模式給定的預設參數,如以下可以看到,我的mac電腦貌似
-server
和
-client
都是用Server模式運作的。64位系統中虛拟機更傾向于使用Server模式運作。
~% java -XX:+PrintFlagsFinal -server -version | grep -E ' CompileThreshold|MaxHeapSize'
intx CompileThreshold = 10000 {pd product}
uintx MaxHeapSize := 4294967296 {product}
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)
~% java -XX:+PrintFlagsFinal -client -version | grep -E ' CompileThreshold|MaxHeapSize'
intx CompileThreshold = 10000 {pd product}
uintx MaxHeapSize := 4294967296 {product}
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)