天地维杰网

人如秋鸿来有信,事若春梦了无痕


  • 首页

  • Redis

  • java

  • linux

  • 日常问题

  • Spring和Springboot

  • Mac相关

  • 中间件

  • 架构

  • python

  • 前端

  • jvm

  • c语言

  • web3

  • 归档

  • 关于

  • 搜索
close

JVM学习03-常用Java虚拟机参数

时间: 2021-06-07   |   分类: java   jvm     |   原创   |   阅读: 7692 字 ~16分钟

一、垃圾回收日志参数

  1. -XX:+PrintGC 打印简单GC日志

只要GC就会打印日志。

   [GC 1023K->565K(5632K), 0.0012699 secs]
   #日志说明:GC前堆空间使用量为1023K,GC后堆空间使用量为565K,当前可用堆空间的总和为5632K,本次GC时间为0.0012699 secs
  1. -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
       
  2. -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
    }
  3. -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
  4. -XX:+PrintGCApplicationConcurrentTime 打印应用程序的执行时间

  5. -XX:+PrintGCApplicationStoppedTime 打印应用程序由于GC而产生的停顿时间。

  6. -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

  1. -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设置相等,好处是可以减少程序运行时进行垃圾回收的次数,从而提高程序的性能。

  1. 新生代配置

    • -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的空间。

2) 上述测试代码如果使用-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过程中,部分新生代对象晋升到老年代。

3) 使用参数-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次数。

4) 使用参数-Xmx20M -Xms20M -XX:NewRatio=2 -XX:+PrintGCDetails运行测试代码,输出如下:

   分配1M内存[B@45ee12a7
   分配1M内存[B@330bedb4
   分配1M内存[B@2503dbd3
   分配1M内存[B@4b67cf4d  发生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内存[B@7ea987ac
   分配1M内存[B@12a3a380
   分配1M内存[B@29453f44
   分配1M内存[B@5cad8086
   分配1M内存[B@6e0be858  发生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内存[B@61bbe9ba
   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老年代和新生代的比例

  1. 堆溢出处理

-XX:+HeapDumpOnOutOfMemoryError 在内在溢出时导出整个堆信息

-XX:HeapDumpPath 指定导出堆的存放路径

-XX:OnOutOfMemoryError 出现OOM时触发操作,用法如下,OOM时调用 printStack.sh脚本。

   "-XX:OnOutOfMemoryError=/Users/dmn/IdeaProjects/demo/printStack.sh %p"

测试代码:

   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指定永久区的最大可用值。

  1. 栈配置

-Xss指定线程栈大小

  1. 直接内存配置

-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)
#java# #jvm# #虚拟机# #参数#
kafka日志采集配置记录
CENTOS安装zookeeper
  • 文章目录
  • 站点概览
不与天斗Domino

不与天斗Domino

Programmer & Architect

183 日志
15 分类
224 标签
    • 一、垃圾回收日志参数
    • 二、类加载/制裁的跟踪
    • 三、系统参数查看
    • 四、堆参数配置
    • 五、非堆内存的参数配置
    • 六、虚拟机工作模式
© 2013 - 2023 天地维杰网 京ICP备13019191号-1
Powered by - Hugo v0.63.2
Theme by - NexT
0%