天天看点

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)