天天看點

JVM學習03-常用Java虛拟機參數JVM學習03-常用Java虛拟機參數

JVM學習03-常用Java虛拟機參數

一、垃圾回收日志參數

  1. -XX:+PrintGC

    列印簡單GC日志

    隻要GC就會列印日志。

    [GC 1023K->565K(5632K), 0.0012699 secs]
    #日志說明:GC前堆空間使用量為1023K,GC後堆空間使用量為565K,目前可用堆空間的總和為5632K,本次GC時間為0.0012699 secs
               
  2. -XX:+PrintGCDetails

    列印詳細GC日志
    [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
    
               
  3. -XX:+PrintHeapAtGC

    分别在每次GC前後分别列印堆資訊。效果如下
    {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
    }
               
  4. -XX:+PrintGCTimeStamps

    輸出GC的發生時間,時間為虛拟機啟動後的時間偏移量。相當于

    -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
               
  5. -XX:+PrintGCApplicationConcurrentTime

    列印應用程式的執行時間
  6. -XX:+PrintGCApplicationStoppedTime

    列印應用程式由于GC而産生的停頓時間。
  7. -XX:+PrintReferenceGC

    跟蹤系統内的軟引用、弱引用、虛引用Finallize隊列。
    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

  8. -Xloggc

    指定日志目錄。

    -Xloggc:log/gc.log

二、類加載/制裁的跟蹤

  1. -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();
            }
        }
    }
               
    package cn.shutdown.demo.jvm.trace;
    
    public class UnloadClassLoader extends ClassLoader {
    }
               
    需要引用的pom依賴
    <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]
               

三、系統參數檢視

  1. -XX:+PrintVMOptions

    列印虛拟機接受到的指令行的顯式參數
  2. -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  這些都是隐式參數
    
    
               
  3. -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}
    
    。。。。省略大部分。。。
               

四、堆參數配置

  1. 最大堆和初始堆設定

    -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");
        }
    
    }
               
    運作結果:
    -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
    
               
    在實際工作中,也可以直接将初始堆 -Xms與最大堆 -Xmx設定相等,好處是可以減少程式運作時進行垃圾回收的次數,進而提高程式的性能。
  2. 新生代配置
    • -Xmn

      :設定新生代大小。設定較大的新生代會減少老年代的大小,這個參數對系統性能及GC行為有很大影響,新生代大小一般設定為整個堆空間的1/3到1/4左右。
    • -XX:SurvivorRatio

      :設定新生代中eden空間和from/to空間的比例關系。
      含義:
      -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
               
    1. 從結果裡我們可以看到 ,青年代雖然輸出總大小為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。
    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) 
               
    另外,由于eden區無法容納任何一個程式中配置設定的1MB的數組,是以觸發了一次新生代的GC,對eden區進行了部分回收,同時,這個偏小的新生代無法為1MB數組預留白間,是以,所有的數組都配置設定在了老年代,老年代最終占用了10256K的空間。
    1. 上述測試代碼如果使用

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

      的JVM參數來運作的話,結果如下。
    配置設定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
    
               
    從結果看出,青年代被初始值設定為7M以後,因為

    -XX:SurvivorRatio=2

    ,是以eden區與from區比為2/1,是以空間大小為
    eden space 4096K, 27% used [0x00000007bf900000,0x00000007bfa14990,0x00000007bfd00000)
     from space 1536K, 98% used [0x00000007bfd00000,0x00000007bfe7c020,0x00000007bfe80000)
     to   space 1536K, 0% used [0x00000007bfe80000,0x00000007bfe80000,0x00000007c0000000)
               
    每次程式配置設定會分區eden區的記憶體,配置設定三次以後,eden區的記憶體不足了,對eden區進行部分回收。由于程式每申請一次空間,也同時廢棄上一次申請的記憶體(上次申請的記憶體失去了引用),是以在新生代的GC中,有效回收了失效的内在,最終結果是:所有的内在配置設定都在新生代進行,通過GC保證了新生代有足夠的空間,而老年代沒有為這些數組預留任何空間,隻是在GC過程中,部分新生代對象晉升到老年代。
    1. 使用參數

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

      運作上述代碼,得到的輸出為:
    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
               
    這次執行中,新生代初始化15M的空間,eden區占用了12288K,滿足10M數組的配置設定 。是以所有的配置設定行為都在eden直接運作,且沒有觸發任何的GC行為,因為 from/to和老年代的使用率都為0。
    不同的堆的分布情況,對系統執行會産生一定影響。在實際工作中,應該根據系統的特點做合理的設定,基本的政策是:盡可能将對象預留在新生代,減少老年代的GC次數。
    1. 使用參數

      -Xmx20M -Xms20M -XX:NewRatio=2 -XX:+PrintGCDetails

      運作測試代碼,輸出如下:
    配置設定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
    
    
               
    堆大小為20M,老年代和新生代比為2:1,是以老年代大小為13824K,新生代大小為 6144K。新生代大小不夠10M的數組配置設定,是以産生新生代的GC,新生代GC時,from/to空間不足以容納任何一個1MB的數組,影響了新生代的正常回收,故新生代回收時需要老年代進行空間擔保。

    預設的,新生代 ( 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老年代和新生代的比例

  3. 堆溢出處理

    -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記憶體");
            }
        }
    }
    
               
    運作結果:
    配置設定了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
               
    以上的運作結果,我沒看懂的一點就是,為什麼老年代的空間是 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左右的記憶體去哪了呢?後面再把這個坑填上。

五、非堆記憶體的參數配置

  1. 方法區配置

    jdk1.6、jdk1.7等版本

    -XX:PermSize

    初始永久區大小

    -XX:MaxPermSize

    配置最大永久區大小

    jdk1.8 永久區移除,改為中繼資料區存放類的中繼資料,預設情況下,中繼資料區隻受系統可用記憶體限制,可以使用

    -XX:MaxMetaspaceSize

    指定永久區的最大可用值。
  2. 棧配置

    -Xss

    指定線程棧大小
  3. 直接記憶體配置

    -XX:MaxDirectMemorySize

    設定最大可用直接記憶體,如不設定 ,預設值為最大堆空間,即-Xmx,當直接記憶體使用量達到

    -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)