JVM参数配置与应用测试

半腔热情 提交于 2021-02-01 01:55:37

JVM参数配置

在我们整个JVM调优中,JVM的参数配置也必不可少,当我们使用给定的一些参数启动JVM,就可以在系统运行时打印相关日志,有利于出现分析实际问题。

  • -XX:+PrintGC :使用这个参数,虚拟机启动后,只要遇到GC就会打印日志。其中-XX说明增加配置,+代表启用配置,如果不写或者写减号代表不启用配置
  • -XX:+UseSerialGC :配置串行回收器,垃圾回收会有单独的一个线程去负责垃圾回收,串行垃圾回收器是垃圾回收中的一种。
  • -XX:+PrintGCDetails :打印GC详细信息,包括各个区的情况
  • -Xms:设置java程序启动时初始堆大小
  • -Xmx:设置java程序能获得的最大堆大小
  • -XX:+PrintCommandLineFlags:可以将隐式或者显示传给虚拟机的参数输出
  • -XX:+HeapDumpOnOutOfMemoryError :发生OOM时生成dump文件
  • -XX:HeapDumpPath=/You/path:生成的dump文件存放路径

在实际工作中,我们可以直接将初始的堆大小与最大堆大小设置相等, 这样的好处是可以减少程序运行时的垃圾回收次数,从而提高性能

1. Heap内存分配测试

  1. 编写测试类
public class JvmMemoryDistributeDemo {
    public static void main(String[] args) {

        // 运行时参数:-Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags

        // 查看GC信息
        System.err.println("maxMemory:"+Runtime.getRuntime().maxMemory());
        System.err.println("freeMemory:"+Runtime.getRuntime().freeMemory());
        System.err.println("totalMemory:"+Runtime.getRuntime().totalMemory());

        byte[] bytes = new byte[1 * 1024 * 1024];
        System.err.println("*************分配了1MB的内存*************");
        System.err.println("maxMemory:"+Runtime.getRuntime().maxMemory());
        System.err.println("freeMemory:"+Runtime.getRuntime().freeMemory());
        System.err.println("totalMemory:"+Runtime.getRuntime().totalMemory());

        byte[] bytes4 = new byte[4 * 1024 * 1024];
        System.err.println("*************分配了4MB的内存*************");
        System.err.println("maxMemory:"+Runtime.getRuntime().maxMemory());
        System.err.println("freeMemory:"+Runtime.getRuntime().freeMemory());
        System.err.println("totalMemory:"+Runtime.getRuntime().totalMemory());
    }
}
  1. 配置运行时JVM参数
  • 在方法上鼠标右键,选择修改配置

  • 添加JVM参数

  • 应用保存

  1. 运行结果
# 打印JVM配置参数
[0.004s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.
-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC 
# 使用串行收集器
[0.010s][info   ][gc] Using Serial 
# 堆空间地址,大小为20MB
[0.010s][info   ][gc,heap,coops] Heap address: 0x00000007fec00000, size: 20 MB, Compressed Oops mode: Zero based, Oop shift amount: 3
# 第一次GC ,是Young GC ,由于分配失败 ,执行暂停年轻代
[0.173s][info   ][gc,start     ] GC(0) Pause Young (Allocation Failure)
# 串行收集 ,收集前为1640K,收集后为192K
[0.174s][info   ][gc,heap      ] GC(0) DefNew: 1640K->192K(1856K)
# 老年代收集前为0K,收集后为679K
[0.174s][info   ][gc,heap      ] GC(0) Tenured: 0K->679K(4096K)
# 元数据区没有变化
[0.174s][info   ][gc,metaspace ] GC(0) Metaspace: 5691K->5691K(1056768K)
# 本次GC共收集了1MB的垃圾,耗时1.946ms
[0.174s][info   ][gc           ] GC(0) Pause Young (Allocation Failure) 1M->0M(5M) 1.946ms
# User:进程在用户态(User Mode)所花费的时间,只统计本进程所使用的时间,注意是指多核
# Sys:进程在核心态(Kernel Mode)花费的CPU时间量,指的是内核中的系统调用所花费的时间,只统计本进程所使用的时间
# Real:从开始到结束所花费的时间
[0.174s][info   ][gc,cpu       ] GC(0) User=0.01s Sys=0.00s Real=0.00s
# 第二次GC,Young GC
[0.191s][info   ][gc,start     ] GC(1) Pause Young (Allocation Failure)
[0.193s][info   ][gc,heap      ] GC(1) DefNew: 1762K->192K(1856K)
[0.193s][info   ][gc,heap      ] GC(1) Tenured: 679K->1985K(4096K)
[0.193s][info   ][gc,metaspace ] GC(1) Metaspace: 5759K->5759K(1056768K)
[0.193s][info   ][gc           ] GC(1) Pause Young (Allocation Failure) 2M->2M(5M) 1.616ms
[0.193s][info   ][gc,cpu       ] GC(1) User=0.00s Sys=0.00s Real=0.01s
# 第三次GC,Young GC
[0.202s][info   ][gc,start     ] GC(2) Pause Young (Allocation Failure)
maxMemory:20316160
freeMemory:3621424
totalMemory:6094848
*************分配了1MB的内存*************
maxMemory:20316160
freeMemory:2572832
totalMemory:6094848
*************分配了4MB的内存*************
maxMemory:20316160
freeMemory:4847976
totalMemory:12546048
# 第四次GC,发生Full GC
[0.203s][info   ][gc,start     ] GC(3) Pause Full (Allocation Failure)
# 第一阶段,标记存活的的对象
[0.203s][info   ][gc,phases,start] GC(3) Phase 1: Mark live objects
[0.204s][info   ][gc,phases      ] GC(3) Phase 1: Mark live objects 1.419ms
# 第二阶段,计算新对象地址
[0.204s][info   ][gc,phases,start] GC(3) Phase 2: Compute new object addresses
[0.204s][info   ][gc,phases      ] GC(3) Phase 2: Compute new object addresses 0.221ms
# 第三阶段,调整指针
[0.204s][info   ][gc,phases,start] GC(3) Phase 3: Adjust pointers
[0.205s][info   ][gc,phases      ] GC(3) Phase 3: Adjust pointers 0.737ms
# 第四阶段,移动对象
[0.205s][info   ][gc,phases,start] GC(3) Phase 4: Move objects
[0.205s][info   ][gc,phases      ] GC(3) Phase 4: Move objects 0.083ms
[0.205s][info   ][gc             ] GC(3) Pause Full (Allocation Failure) 3M->3M(5M) 2.565ms
[0.205s][info   ][gc,heap        ] GC(2) DefNew: 1453K->0K(1856K)
[0.205s][info   ][gc,heap        ] GC(2) Tenured: 1985K->3353K(4096K)
[0.205s][info   ][gc,metaspace   ] GC(2) Metaspace: 6129K->6129K(1056768K)
[0.205s][info   ][gc             ] GC(2) Pause Young (Allocation Failure) 3M->3M(7M) 3.603ms
[0.205s][info   ][gc,cpu         ] GC(2) User=0.01s Sys=0.00s Real=0.00s
# 所有GC完成,堆内存变化情况
[0.208s][info   ][gc,heap,exit   ] Heap
#  年轻代
[0.208s][info   ][gc,heap,exit   ]  def new generation   total 2560K, used 91K [0x00000007fec00000, 0x00000007feec0000, 0x00000007ff2a0000)
# 年轻代的eden区
[0.208s][info   ][gc,heap,exit   ]   eden space 2304K,   3% used [0x00000007fec00000, 0x00000007fec16c48, 0x00000007fee40000)
# 年轻代的from区
[0.208s][info   ][gc,heap,exit   ]   from space 256K,   0% used [0x00000007fee40000, 0x00000007fee40000, 0x00000007fee80000)
# 年轻代的to区
[0.208s][info   ][gc,heap,exit   ]   to   space 256K,   0% used [0x00000007fee80000, 0x00000007fee80000, 0x00000007feec0000)
# 老年代
[0.208s][info   ][gc,heap,exit   ]  tenured generation   total 9692K, used 7449K [0x00000007ff2a0000, 0x00000007ffc17000, 0x0000000800000000)
[0.208s][info   ][gc,heap,exit   ]    the space 9692K,  76% used [0x00000007ff2a0000, 0x00000007ff9e67f0, 0x00000007ff9e6800, 0x00000007ffc17000)
# 元数据区
[0.208s][info   ][gc,heap,exit   ]  Metaspace       used 6241K, capacity 6319K, committed 6528K, reserved 1056768K
# 压缩空间,即Compressed class space
[0.208s][info   ][gc,heap,exit   ]   class space    used 536K, capacity 570K, committed 640K, reserved 1048576K

2.内存溢出测试

  1. 编写测试代码
public class JvmOutOfMemoryDemo {
   // 一直持有,让GC释放不掉
    private static final List<byte[]> holderList = new ArrayList<>();

    public static void main(String[] args) {

        // TODO 第一次测试
        // 分配最小内存和最大内存一直,设置串行收集
        // -Xms20m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags

        // 导致多次GC,甚至Full GC,最终抛出
        // Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
        // Exception in thread "Monitor Ctrl-Break" java.lang.OutOfMemoryError: Java heap space

        while (true){
            byte[] bytes = new byte[1 * 1024];
            // 持续添加1MB的字节数组,导致内存溢出
            holderList.add(bytes);
        }

    }
}
  1. 运行结果
# 这里省略N次GC日志,直接看堆信息
[0.305s][info   ][gc,heap,exit   ] Heap
[0.305s][info   ][gc,heap,exit   ]  def new generation   total 6144K, used 6141K [0x00000007fec00000, 0x00000007ff2a0000, 0x00000007ff2a0000)
[0.305s][info   ][gc,heap,exit   ]   eden space 5504K,  99% used [0x00000007fec00000, 0x00000007ff15fff8, 0x00000007ff160000)
[0.305s][info   ][gc,heap,exit   ]   from space 640K,  99% used [0x00000007ff160000, 0x00000007ff1ff5a0, 0x00000007ff200000)
[0.305s][info   ][gc,heap,exit   ]   to   space 640K,   0% used [0x00000007ff200000, 0x00000007ff200000, 0x00000007ff2a0000)
[0.305s][info   ][gc,heap,exit   ]  tenured generation   total 13696K, used 13695K [0x00000007ff2a0000, 0x0000000800000000, 0x0000000800000000)
[0.305s][info   ][gc,heap,exit   ]    the space 13696K,  99% used [0x00000007ff2a0000, 0x00000007ffffff60, 0x0000000800000000, 0x0000000800000000)
[0.305s][info   ][gc,heap,exit   ]  Metaspace       used 6265K, capacity 6315K, committed 6528K, reserved 1056768K
[0.305s][info   ][gc,heap,exit   ]   class space    used 543K, capacity 570K, committed 640K, reserved 1048576K
# 最终抛出内存溢出异常
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
Exception in thread "Monitor Ctrl-Break" java.lang.OutOfMemoryError: Java heap space
  1. 增加如下配置,再次测试 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/yunnasheng/Desktop
  • +HeapDumpOnOutOfMemoryError:发生OOM时生成dump文件
  • HeapDumpPath=/Users/yunnasheng/Desktop:生成dump文件路径地址 运行程序,查看生成的文件会发现有个后缀为.hprof 的文件,这个文件就是dump文件
 yunnasheng@yunnashengdeMacBook-Pro  ~/Desktop  pwd
/Users/yunnasheng/Desktop
 yunnasheng@yunnashengdeMacBook-Pro  ~/Desktop  ls
 java_pid9167.hprof   

3.分析dump文件

利用EclipseMemory Analyze插件分析dump文件

  1. 打开dump文件 下载好插件以后,把 dump文件——java_pid9167.hprof拖拽到我们项目的resources目录下

2. 打开后的界面如下

从图中内容可看出,总共20MB的内存,a疑点就占用了17.1MB,内存泄露的疑点可能就在这里。

直方图

  1. 点击左上角的histogram按钮如图所示,可看出有byte[]1900万个对象

2. 合并GC Roots 因为只有强引用才会导致GC无法释放空间,最终导致OOM。所以我们只需要关注强引用的数据。

  • 过滤数据

  • 点开看详情 发现有一个com.lb.gc.JvmOutOfMemoryDemo类下的holderList java.util.ArrayList 有1万7千多个引用并且占用了1700多万个堆。 因为这个list一直没有被释放,所以会导致应用程序发生 OOM

树形图

  • 同时我们也可以使用树形图来查看占用率,可以看到这个class com.lb.gc.JvmOutOfMemoryDemo @ 0x7ff614e28占用率是非常高的,达到 88.75%

一般情况下我们不会等到内存溢出之后,才去分析内存溢出的原因和问题, 都会进行定时的巡检,这就需要使用jmap命令进行导出了

实时监控导出

jmap

jmap -heap pid来查看运行时的JVM配置

jdk1.8以上,需要用jhsdb jmap --heap --pid 36362代替jmap -heap pid命令 hsdb是HotSpot Debugger的简称

由于Mac系统下执行jmap有好多问题,这里就用Linux来演示了

  • 查看JVM配置 jmap -heap 2058
[lb@centos-linux ~]$ jmap -heap 2058
Attaching to process ID 2058, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.271-b09

using thread-local object allocation.
Parallel GC with 2 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 1048576000 (1000.0MB)
   NewSize                  = 10485760 (10.0MB)
   MaxNewSize               = 349175808 (333.0MB)
   OldSize                  = 20971520 (20.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 8388608 (8.0MB)
   used     = 3422128 (3.2635955810546875MB)
   free     = 4966480 (4.7364044189453125MB)
   40.794944763183594% used
From Space:
   capacity = 1048576 (1.0MB)
   used     = 1032320 (0.9844970703125MB)
   free     = 16256 (0.0155029296875MB)
   98.44970703125% used
To Space:
   capacity = 1048576 (1.0MB)
   used     = 0 (0.0MB)
   free     = 1048576 (1.0MB)
   0.0% used
PS Old Generation
   capacity = 20971520 (20.0MB)
   used     = 6143672 (5.859062194824219MB)
   free     = 14827848 (14.140937805175781MB)
   29.295310974121094% used

5304 interned Strings occupying 409096 bytes.
[lb@centos-linux ~]$
  • 导出dump文件 jmap -dump:format=b,file=app.hprof 2058
[lb@centos-linux ~]$ jmap -dump:format=b,file=app.hprof 2058
Dumping heap to /home/lb/app.hprof ...
Heap dump file created
[lb@centos-linux ~]$ ls
app.hprof  libs  servers  soft
[lb@centos-linux ~]$
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!