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内存分配测试
- 编写测试类
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());
}
}
- 配置运行时JVM参数
- 在方法上鼠标右键,选择修改配置
- 添加JVM参数
- 应用保存
- 运行结果
# 打印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.内存溢出测试
- 编写测试代码
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);
}
}
}
- 运行结果
# 这里省略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
- 增加如下配置,再次测试
-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文件
利用Eclipse
的Memory Analyze
插件分析dump文件
- 打开dump文件 下载好插件以后,把 dump文件——
java_pid9167.hprof
拖拽到我们项目的resources
目录下
2. 打开后的界面如下
从图中内容可看出,总共20MB的内存,a疑点就占用了17.1MB,内存泄露的疑点可能就在这里。
直方图
- 点击左上角的
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 ~]$
来源:oschina
链接:https://my.oschina.net/codingcloud/blog/4939374