JVM调参,看这一篇就够了

2023-05-16

文章目录

    • JVM相关参数调试
    • 内存相关
    • 垃圾回收器相关
      • 配置方式
      • 命令
      • 参考

JVM相关参数调试

通过实战的 方式来进行参数调试,观察结果才能真正理解含义,下面将通过一段代码,来一个一个参数的进行测试。

代码示例

后面的代码都是通过对下面这段代码进行参数调试来验证相关参数的意义。

public class JConsoleTest {

    public byte[] bytes;

    private final static int count = 1000000;

    /**
     * 构造bytes数组,占用内存
     */
    public JConsoleTest() {
        bytes = new byte[128 * 1024];
    }

    /**
     * 测试内存的变化情况
     */
    public static void main(String[] args) {
        try {
            //为了能看到效果
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("start..");
        List<JConsoleTest> list = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            try {
                Thread.sleep(100);
                //不断创建
                list.add(new JConsoleTest());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

内存相关

  • 堆内存大小设置

    -Xmx3550m:设置JVM最大可用内存为3550M。

    -Xms3550m:设置JVM初始内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。

    配置100m进行测试

    通过Java VisualVM进行查看

    查看堆内存:已经达到100m

    内存溢出了:

    Jconsole查看结果:

  • 栈内存大小

    -Xss256k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右

    private static int count;
   
   public static void main(String[] args) {
   
           try {
               method3();
           } catch (Exception e) {
               e.printStackTrace();
           } finally {
   
               System.out.println(count);
           }
   
       }
   
   private static void method3() throws Exception {
           count++;
           method3();
       }

验证一下:

  • -Xss1024k


递归32113次

  • -Xss256k


递归3857次

  • 设置年轻代大小

    -Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

    设置-Xmn40M,堆内存总共为-Xms100m,使用jmap -heap <pid>观察一下结果:

    参数含义:

       Heap Configuration:
       MinHeapFreeRatio          # 最小堆使用比例
       MaxHeapFreeRatio          # 最大堆可用比例
       MaxHeapSize               # 最大堆空间大小
       NewSize                   # 新生代分配大小
       MaxNewSize               # 最大新生代可分配大小
       OldSize                   # 老年代大小
       NewRatio                  # 新生代比例
       SurvivorRatio             # 新生代与 Survivor 比例
       MetaspaceSize             # 元空间大小
       CompressedClassSpaceSize  # Compressed Class Space 空间大小限制
       MaxMetaspaceSize          # 最大元空间大小
       G1HeapRegionSize         # G1 单个 Region 大小
    
  • 设置年轻代

    -XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5。

    设置为-XX:NewRatio=4,年轻代与年老代所占比值为20m:80m,1:4了

  • 设置年轻代中Eden区与Survivor区的大小比值

    -XX:SurvivorRatio=3:设置年轻代中Eden区与Survivor区的大小比值。设置为3,则两个Survivor区与一个Eden区的比值为2:3,一个Survivor区占整个年轻代的1/5

    如图:关于survivor区和eden区

    配置-XX:SurvivorRatio=3后:

    两个Survivor区各4m,Eden占12m,一个Survivor区占4m占年轻代1/5

  • 设置元空间初始大小以及最大可分配大小

    -XX:MetaspaceSize-XX:MaxMetaspaceSize

    设置-XX:MaxMetaspaceSize=8m,运行下面的代码:

   public class 元空间 {

    public static void main(String[] args) {
        int j = 0;
        try {
            TestClassLoader testClassLoader=new TestClassLoader();
            for (int i = 0; i < 10000; i++, j++) {
                //ClassWriter作用是生成类的二进制字节码
                ClassWriter wa = new ClassWriter(0);
                //版本号,public,类名,包名,父类,接口
                wa.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                //返回byte
                byte[] code = wa.toByteArray();
                //执行了类的加载
                testClassLoader.defineClass1("Class" + i, code, 0, code.length);
            }
        } finally {
            System.out.println(j);
        }
    }
}

结果:

将for循环增加到20000,设置-XX:MaxMetaspaceSize=16m,结果:

到了Java8,永久代被干掉了,有了“meta space”的概念,存储jvm中的元数据,包括byte code,class等信息。Java8在UseCompressedOops之外,额外增加了一个新选项叫做UseCompressedClassPointer。这个选项打开后,class信息中的指针也用32bit的Compressed版本。
而这些指针指向的空间被称作“Compressed Class Space”。默认大小是1G,但可以通过“CompressedClassSpaceSize”调整。

如果你的java程序引用了太多的包,有可能会造成这个空间不够用,于是会看到java.lang.OutOfMemoryError: Compressed class space

这时,一般调大CompreseedClassSpaceSize就可以了

参考: JVM方法区(元空间)大小设置(-XX:MetaspaceSize和-XX:MaxMetaspaceSize)

  • 设置垃圾最大年龄

    -XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。

    设置用于自适应 GC 大小调整的最大使用期限。最大值为 15。并行(吞吐量)收集器的默认值为 15,CMS 收集器的默认值为 6。

  • 新生代可容纳的最大对象

    -XX:PretenureSizeThreshold=1000000 :大于此值的对象直接会分配到老年代,设置为0则没有限制。
    避免在Eden区和Survivor区发生大量的内存复制,该参数只对Serial和ParNew收集器有效,Parallel Scavenge并不认识该参数

  • 进入老年代的GC年龄

    • 进入老年代最小的GC年龄

    -XX:InitialTenuringThreshol=7 :年轻代对象转换为老年代对象最小年龄值,默认值7,对象在坚持过一次Minor GC之后,年龄就加1,每个对象在坚持过一次Minor GC之后,年龄就增加1

    • 进入老年代最大的GC年龄

    -XX:MaxTenuringThreshold=15 :年轻代对象转换为老年代对象最大年龄值,设置用于自适应 GC 大小调整的最大使用期限。最大值为 15。并行(吞吐量)收集器的默认值为 15,CMS 收集器的默认值为 6

垃圾回收器相关

  • JVM使用的垃圾收集器查看方法

    -XX:+PrintCommandLineFlags

    控制台查看

  • 各个版本JDK默认的垃圾回收器

    JDK1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

    JDK1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

    JDK1.9 默认垃圾收集器G1

    Parallel垃圾回收器总结

  • 打印GC内容

    -XX:+PrintGC

  • 生成GC滚动日志记录 ,当需要对GC排查问题时候需要对此日志分析。

    -Xloggc:/opt/app/ard-user/ard-user-gc-%t.log 设置日志目录和日志名称

    -XX:+UseGCLogFileRotation 开启滚动生成日志

    -XX:NumberOfGCLogFiles=5 滚动GC日志文件数,默认0,不滚动

    -XX:GCLogFileSize=20M GC文件滚动大小,需开启UseGCLogFileRotation

    -XX:+PrintGCDetails 开启记录GC日志详细信息(包括GC类型、各个操作使用的时间),并且在程序运行结束打印出JVM的内存占用情况

    -XX:+ PrintGCDateStamps 记录系统的GC时间

    -XX:+PrintGCCause 产生GC的原因(默认开启)

    配置参数:
    -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation -XX:+PrintHeapAtGC -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=20M -Xloggc:D://ard-user-gc-%t.log

    在D盘生成gc日志文件:

    日志内容:

  • 开启指定垃圾回收器

    -XX:+UseSerialGC:虚拟机运行在Client模式下的默认值,Serial+Serial Old。

    -XX:+UseParNewGC:ParNew+Serial Old,在JDK1.8被废弃,在JDK1.7还可以使用。

    -XX:+UseConcMarkSweepGC:ParNew+CMS+Serial Old,指定使用CMS后,会默认使用ParNew作为新生代收集器

    -XX:+UseParallelGC:虚拟机运行在Server模式下的默认值,Parallel Scavenge+Serial Old(PS Mark Sweep)。

    -XX:+UseParallelOldGC:Parallel Scavenge+Parallel Old。

    -XX:+UseG1GC:G1+G1。

    -XX:ParallelGCThreads:指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;

    配置-XX:+UseConcMarkSweepGC

    观察:

  • 配置GC文件路径

    -Xloggc:/data/gclog/gc.log: 固定路径名称生成

    -Xloggc:/home/GCEASY/gc-%t.log :根据时间生成

  • 滚动生成日志

    日志文件达到一定大小后,生成另一个文件。须配置Xloggc

    开启 -XX:+UseGCLogFileRotation

    关闭 -XX:-UseGCLogFileRotation

    -XX:NumberOfGCLogFiles=4 :滚动GC日志文件数,默认0,不滚动

    -XX:GCLogFileSize=100k :GC文件滚动大小,需配置UseGCLogFileRotation,设置为0表示仅通过jcmd命令触

  • 打印详细的GC日志

    开启 -XX:+PrintGCDetails

    关闭 -XX:-PrintGCDetails

  • 打印应用暂停时间

    -XX:+PrintGCApplicationStoppedTime

  • 打印存活实例年龄信息

    -XX:+PrintTenuringDistribution

    开启 -XX:+HeapDumpOnOutOfMemoryError
    关闭 -XX:-HeapDumpOnOutOfMemoryError

    可以通过jinfo -flag [+|-]HeapDumpOnOutOfMemoryError <pid>jinfo -flag HeapDumpOnOutOfMemoryError=<value> <pid> 来动态开启或设置值。

    配置后gc日志出现:

  • 设置文件路径

    -XX:HeapDumpPath=D://jvm.dump:设置Dump保存的路径

    当HeapDumpOnOutOfMemoryError开启的时候,dump文件的保存路径,默认为工作目录下的OutOfMemory异常时输出文件:抛出内存溢出错误时导出堆信息到指定文件

    配置后

    通过JConsole进行gc后生成dump文件:

  • 在Full GC时生成dump文件

    -XX:+HeapDumpBeforeFullGC :实现在Full GC前dump

    -XX:+HeapDumpAfterFullGC :实现在Full GC后dump。

    配置 -XX:+HeapDumpAfterFullGC查看

    点击GC一次,就生成一次

    生成文件:

    文件内容:

配置方式

上面idea启动参数在idea中配置方式:Edit-Configuration中选择add VM options

命令

  • 查看JVM所有参数

    java -XX:+PrintFlagsInitial

  • 如果忘记了某个指令的全名,可根据部分单词匹配查找

    java -XX:+PrintFlagsInitial |grep GC

参考

所有参数:

Java1.7 HotSpot VM Options

Java1.8及以上 HotSpot VM Options

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JVM调参,看这一篇就够了 的相关文章

随机推荐