前言:
从事java开发的小伙伴,对JVM都不会陌生。这块的知识点笔者不再多述,网络上有很多教程。
基本了解了JVM的内存结构后,那么平时我们怎么排查jvm相关的问题呢?
那就要从jvm参数和GC日志来分析具体问题了,通过jvm参数了解其基本设置;通过GC日志了解GC具体情况。
下面我们来实战一把。
1.准备工作
笔者使用的JDK版本为1.8.0_131版本,通过命令java -version可以看出
PS D:\Program Files\Java\jdk1.8.0_131\bin> java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
再准备一个简单的程序,模拟full gc情况
1.1 JVM参数设置
笔者需要将具体的gc情况打印出来,一般需要使用到以下参数
-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的输出路径
笔者自己准备的测试代码jvm参数如下
-Xmx1G -Xms1G -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:E:/gclogs
最大堆内存设置为1G,打印gc日志到E:/gclogs文件里
1.2 代码准备
public class User implements Serializable {
private static final long serialVersionUID = -1754313838977971228L;
private String password;
private String username;
private byte[] bytes = null;
public User(String password, String username) {
this.password = password;
this.username = username;
bytes = new byte[1 * 1024 * 1024];
}
}
User对象,每次新建都会占用1MB的空间(bytes对象)
测试类如下:
// -Xmx1G -Xms1G -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:E:/gclogs
public class JvmTest {
@Test
public void test() {
List<User> stuList = new ArrayList<>();
for (int j = 0; j < 1000; j++) {
stuList.add(new User("jack" + j, "pass" +j));
if (stuList.size() == 500) {
stuList = null;
stuList = new ArrayList<>();
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
代码很简单,我们设置最大堆内存为1G,这里创建1000个User对象,50ms休息一次,500次将stuList设置为null
2.jvm日志
整个测试过程结束后,生成的gclog文件内容如下:
# 1.JDK及jvm参数信息
Java HotSpot(TM) 64-Bit Server VM (25.131-b11) for windows-amd64 JRE (1.8.0_131-b11), built on Mar 15 2017 01:23:53 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 8271080k(1594440k free), swap 14562536k(5074312k free)
CommandLine flags: -XX:InitialHeapSize=1073741824 -XX:MaxHeapSize=1073741824 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
# 2.GC情况
2022-03-26T12:22:15.599+0800: 14.553: [GC (Allocation Failure) [PSYoungGen: 262107K->43512K(305664K)] 262107K->225621K(1005056K), 0.0765077 secs] [Times: user=0.08 sys=0.09, real=0.06 secs]
2022-03-26T12:22:31.416+0800: 30.371: [GC (Allocation Failure) [PSYoungGen: 305123K->43496K(305664K)] 487232K->483665K(1005056K), 0.1101805 secs] [Times: user=0.08 sys=0.31, real=0.11 secs]
2022-03-26T12:22:31.525+0800: 30.481: [Full GC (Ergonomics) [PSYoungGen: 43496K->0K(305664K)] [ParOldGen: 440169K->481727K(699392K)] 483665K->481727K(1005056K), [Metaspace: 9390K->9390K(1058816K)], 0.0721537 secs] [Times: user=0.20 sys=0.02, real=0.06 secs]
2022-03-26T12:22:47.551+0800: 46.505: [Full GC (Ergonomics) [PSYoungGen: 262111K->0K(305664K)] [ParOldGen: 481727K->230802K(699392K)] 743839K->230802K(1005056K), [Metaspace: 9390K->9390K(1058816K)], 0.0846851 secs] [Times: user=0.26 sys=0.00, real=0.08 secs]
2022-03-26T12:23:03.537+0800: 62.490: [GC (Allocation Failure) [PSYoungGen: 262131K->43136K(305664K)] 492934K->492054K(1005056K), 0.0493609 secs] [Times: user=0.19 sys=0.00, real=0.05 secs]
2022-03-26T12:23:03.583+0800: 62.540: [Full GC (Ergonomics) [PSYoungGen: 43136K->0K(305664K)] [ParOldGen: 448918K->491961K(699392K)] 492054K->491961K(1005056K), [Metaspace: 9390K->9390K(1058816K)], 0.0924415 secs] [Times: user=0.17 sys=0.02, real=0.09 secs]
# 3.堆内存占用信息
Heap
PSYoungGen total 305664K, used 31079K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
eden space 262144K, 11% used [0x00000000eab00000,0x00000000ec959d90,0x00000000fab00000)
from space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
to space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
ParOldGen total 699392K, used 491961K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
object space 699392K, 70% used [0x00000000c0000000,0x00000000de06e740,0x00000000eab00000)
Metaspace used 9647K, capacity 10788K, committed 11008K, reserved 1058816K
class space used 1274K, capacity 1507K, committed 1536K, reserved 1048576K
3 当前进程内存分布情况
之前的博客笔者已经有过介绍,我们使用%JAVA_HOME%/bin下的jmap命令可以查看进程的具体内存分布情况
3.1 jps来获取当前测试进程ID
PS D:\Program Files\Java\jdk1.8.0_131\bin> jps
16128 Launcher
16724
10264 JUnitStarter
9960 RemoteMavenServer
19164 Jps
10264就是上述我们的JvmTest.test()方法进程id
3.2 jmap获取进程内存分布情况
PS D:\Program Files\Java\jdk1.8.0_131\bin> .\jmap.exe -heap 10264
Attaching to process ID 10264, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 1073741824 (1024.0MB)
NewSize = 357564416 (341.0MB)
MaxNewSize = 357564416 (341.0MB)
OldSize = 716177408 (683.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 = 268435456 (256.0MB)
used = 37198920 (35.47565460205078MB)
free = 231236536 (220.52434539794922MB)
13.857677578926086% used
From Space:
capacity = 44564480 (42.5MB)
used = 21331976 (20.34375762939453MB)
free = 23232504 (22.15624237060547MB)
47.867665010340076% used
To Space:
capacity = 44564480 (42.5MB)
used = 0 (0.0MB)
free = 44564480 (42.5MB)
0.0% used
PS Old Generation
capacity = 716177408 (683.0MB)
used = 16384 (0.015625MB)
free = 716161024 (682.984375MB)
0.002287701317715959% used
5259 interned Strings occupying 446160 bytes.
MaxHeapSize最大堆内存就是我们设置的1G
NewRatio=2,所以Old Generation(老年代)占用2/3,为683MB,NewSize(新生代)占用1/3,为341M,总共为683+341=1024M
SurvivorRatio=8,则新生代中 Eden区占用8/10,为256MB,From区和To区分别占用1/10,为42.5MB,总共为256+42.5+42.5=341MB
4.GC日志分析
4.1 young gc分析
我们先来分析下young gc的日志,就是如下一行
2022-03-26T12:22:15.599+0800: 14.553: [GC (Allocation Failure) [PSYoungGen: 262107K->43512K(305664K)] 262107K->225621K(1005056K), 0.0765077 secs] [Times: user=0.08 sys=0.09, real=0.06 secs]
具体每个字段的含义如下:
2022-03-26T12:22:15.599+0800: 14.553 # 当前执行时间
GC (Allocation Failure) # GC类型为young gc,发生在新生代
PSYoungGen: 262107K->43512K(305664K) # PSYoungGen(新生代) 262107K(young gc前新生代内存内存占用) 43512K(young gc后新生代内存内存占用) 305664K(新生代总内存大小)
262107K->225621K(1005056K) # 262107K(young gc前堆内存内存占用) 225621K(young gc后堆内存内存占用) 1005056K(JVM堆总内存大小)
0.0765077 secs # young gc耗时
Times: user=0.08 sys=0.09, real=0.06 secs # user(用户耗时) sys(系统耗时) real(young gc实际耗时)
4.2 full gc日志分析
full gc日志如下
2022-03-26T12:22:31.525+0800: 30.481: [Full GC (Ergonomics) [PSYoungGen: 43496K->0K(305664K)] [ParOldGen: 440169K->481727K(699392K)] 483665K->481727K(1005056K), [Metaspace: 9390K->9390K(1058816K)], 0.0721537 secs] [Times: user=0.20 sys=0.02, real=0.06 secs]
具体每个字段的含义如下
2022-03-26T12:22:31.525+0800: 30.481 # 当前执行时间
Full GC (Ergonomics) # GC类型为 full gc,发生在老年代
PSYoungGen: 43496K->0K(305664K) # PSYoungGen(新生代) 43496K(full gc前新生代内存内存占用) 0K(full gc后新生代内存内存占用) 305664K(新生代总内存大小)
ParOldGen: 440169K->481727K(699392K) # ParOldGen(老年代) 440169K(full gc前老年代内存内存占用) 481727K(full gc后老年代内存占用) 699392K(老年代总内存大小)
483665K->481727K(1005056K) # 483665K(full gc前堆内存内存占用) 481727K(full gc后堆内存内存占用) 1005056K(JVM堆总内存大小)
Metaspace: 9390K->9390K(1058816K) # Metaspace(方法区) 9390K(full gc前方法区内存内存占用) 9390K(full gc后方法区内存占用) 1058816K(方法区总内存大小)
0.0721537 secs # full gc耗时
[Times: user=0.20 sys=0.02, real=0.06 secs] # user(用户耗时) sys(系统耗时) real(full gc实际耗时)
4.3 heap信息分析
Heap
# 新生代总共305664K,已经使用31079K
# eden区空间,11%被使用
PSYoungGen total 305664K, used 31079K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
eden space 262144K, 11% used [0x00000000eab00000,0x00000000ec959d90,0x00000000fab00000)
from space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
to space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
# 老年代总共699392K,已经使用491961K,被占用70%
ParOldGen total 699392K, used 491961K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
object space 699392K, 70% used [0x00000000c0000000,0x00000000de06e740,0x00000000eab00000)
# 方法区总共11008K,被使用9647K
Metaspace used 9647K, capacity 10788K, committed 11008K, reserved 1058816K
class space used 1274K, capacity 1507K, committed 1536K, reserved 1048576K
有关于Metaspace的更多知识点可以参考:https://www.jianshu.com/p/a6f19189ec62
总结:
更多的明细信息可以参考Oracle官网信息,地址如下:Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning