
- 一、JVM 类加载机制
- 二、JVM 内存模型
- 三、JVM 内存分配机制
- 四、JVM 垃圾收集算法 和 垃圾收集器
- 五、JVM 调优工具
一、
JDK 体系结构
- Alibaba Arthas
- Jstack
- SPI
java
javac
javadoc
javap -c Math.class > math.txt
# 反编译字节码文件
javap -c VolatileMain2.class
jvisualvm
二、
Jmap 内存图
[root@dam ~]# jps
2008 dam.jar
28667 Jps
[root@dam ~]# jmap -histo 2008 > ./log.txt
- 此命令可以用来查看内存信息。
num:序号。#instances:实例数量。#bytes:占用空间大小。class name:类名称。
[B:is a byte[][S:is a short[][C:is a char[][I:is a int[][[I:is a int[][]
1. 查看堆信息
[root@dam ~]# jps
1827 Jps
2008 dam.jar
[root@dam ~]# jmap -heap 2008
Attaching to process ID 2008, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.312-b07
using thread-local object allocation.
Parallel GC with 2 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2051014656 (1956.0MB)
NewSize = 42991616 (41.0MB)
MaxNewSize = 683671552 (652.0MB)
OldSize = 87031808 (83.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 = 651165696 (621.0MB)
used = 180286232 (171.9343490600586MB)
free = 470879464 (449.0656509399414MB)
27.686690669896713% used
From Space:
capacity = 15728640 (15.0MB)
used = 1015808 (0.96875MB)
free = 14712832 (14.03125MB)
6.458333333333333% used
To Space:
capacity = 16252928 (15.5MB)
used = 0 (0.0MB)
free = 16252928 (15.5MB)
0.0% used
PS Old Generation
capacity = 191889408 (183.0MB)
used = 56952368 (54.31401062011719MB)
free = 134937040 (128.6859893798828MB)
29.679787224107752% used
43450 interned Strings occupying 4151912 bytes.
2. 设置堆参数
# 运行时
jmap -dump:live,format=b,life=<'filepath'> <'pid'>
-Xmx128m:设置堆内存。-XX:+HeapDumpOnOutOfMemoryError:启动时开启堆错误快照。-XX:HeapDumpPath=./:堆错误快照指定路径。
[root@dam ~]# jps
1827 Jps
2008 dam.jar
# 设置内存溢出,自动导出`dump`文件(内存很大的时候,可能会导不出来)。
[root@dam ~]# jmap -dump:format=b,file=dam.hprof 2008
- 分析堆快照。
MAT。VisualVM。JProfiler。
3.
jvisualvm 工具,分析 dump 文件
C:\Users\qshome>jvisualvm
- 可以用
jvisualvm命令工具,导入dump文件进行分析。
三、
Jstack 查找死锁
# 死锁-定位进程号
jps -l
# 死锁-查看进程信息
jstack pid
/**
* @author wy
* describe 死锁
*/
public class LockTest2 {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
String threadName = Thread.currentThread().getName();
synchronized (lock1) {
System.out.printf("线程:%s 运行", threadName).println();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.printf("线程:%s 结束", threadName).println();
}
}
}).start();
new Thread(() -> {
String threadName = Thread.currentThread().getName();
synchronized (lock2) {
System.out.printf("线程:%s 运行", threadName).println();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.printf("线程:%s 结束", threadName).println();
}
}
}).start();
System.out.printf("线程:%s 结束", Thread.currentThread().getName()).println();
}
}
1.
jvisualvm 工具,检测死锁
- 可以用
jvisualvm自动检测死锁。
2.
jvisualvm 工具,远程连接
jvisualvm工具,远程连接服务。
需要在远程服务器上配置Host(连接IP主机名),并且要关闭防火墙。
jar程序,配置JMX端口。
# `jar`程序,配置`JMX`端口。
java -jar foo.jar \
-Dcom.sun.management.jmxremote.port=8899 \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false
Tomcat配置JMX端口。
# `Tomcat`配置`JMX`端口。
JAVA_OPTS=-Dcom.sun.management.jmxremote.port=8899 \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false
3.
Jstack 找出占用 CPU 最高的堆栈信息
[root@dam ~]# jps
7153 Jps
2008 dam.jar
# 1. 显示`Java`进程的内存情况。
[root@dam ~]# top -p 2008
top -p:显示Java进程的内存情况(进程号)。
# 2. 获取每个线程的内存情况。
[root@qs ~]# H
- 找到内存和
CPU占用最高的线程tid(比如:2008,转为十六进制:0x7d8)。
- 查看对应的堆栈信息,找出可能存在问题的代码。
# 3. 得到线程堆栈信息中,线程`7d8`所在行的后`10`行。
jstack 2008 | grep -A 10 7d8
四、
Jinfo 扩展参数
- 查看正在运行的
Java应用程序的扩展参数。
1. 查看
JVM 的参数
[root@dam ~]# jps
2008 dam.jar
1400 Jps
[root@dam ~]# jinfo -flags 2008
2. 查看
Java 系统参数
[root@dam ~]# jps
30613 Jps
2008 dam.jar
[root@dam ~]# jinfo -sysprops 2008
五、
Jstat 查看堆内存使用量
jstat命令,查看堆内存各部分的使用量,以及加载类的数量。- 注意:使用的
JDK-8版本。
# jstat [-命令选项] [vmid] [间隔时间(毫秒)] [查询次数]
1. 垃圾回收统计
[root@dam ~]# jps
14032 Jps
2008 dam.jar
[root@dam ~]# jstat -gc 2008
S0C:第一个幸存区的大小。S1C:第二个幸存区的大小。S0U:第一个幸存区的使用大小。S1U:第二个幸存区的使用大小。EC:伊甸园区的大小。EU:伊甸园区的使用大小。OC:老年代大小。OU:老年代使用大小。MC:方法区大小(元空间)。MU:方法区使用大小。CCSC:压缩类空间大小。CCSU:压缩类空间使用大小。YGC:年轻代垃圾回收次数。YGCT:年轻代垃圾回收消耗时间(单位s)。FGC:老年代垃圾回收次数。FGCT:老年代垃圾回收消耗时间(单位s)。GCT:垃圾回收消耗总时间(单位s)。
2. 堆内存统计
[root@dam ~]# jps
25729 Jps
2008 dam.jar
[root@dam ~]# jstat -gccapacity 2008
NGCMN:新生代最小容量。NGCMX:新生代最大容量。NGC:当前新生代容量。S0C:第一个幸存区大小。S1C:第二个幸存区的大小。EC:伊甸园区的大小。OGCMN:老年代最小容量。OGCMX:老年代最大容量。OGC:当前老年代大小。OC:当前老年代大小。MCMN:最小元数据容量。MCMX:最大元数据容量。MC:当前元数据空间大小。CCSMN:最小压缩类空间大小。CCSMX:最大压缩类空间大小。CCSC:当前压缩类空间大小。YGC:年轻代GC次数。FGC:老年代GC次数。
3. 新生代垃圾回收统计
[root@dam ~]# jps
2008 dam.jar
3839 Jps
[root@dam ~]# jstat -gcnew 2008
S0C:第一个幸存区的大小。S1C:第二个幸存区的大小。S0U:第一个幸存区的使用大小。S1U:第二个幸存区的使用大小。TT:对象在新生代存活的次数。MTT:对象在新生代存活的最大次数。DSS:期望的幸存区大小。EC:伊甸园区的大小。EU:伊甸园区的使用大小。YGC:年轻代垃圾回收次数。YGCT:年轻代垃圾回收消耗时间。
4. 新生代内存统计
[root@dam ~]# jps
2008 dam.jar
15389 Jps
[root@dam ~]# jstat -gcnewcapacity 2008
NGCMN:新生代最小容量。NGCMX:新生代最大容量。NGC:当前新生代容量。S0CMX:最大幸存1区大小。S0C:当前幸存1区大小。S1CMX:最大幸存2区大小。S1C:当前幸存2区大小。ECMX:最大伊甸园区大小。EC:当前伊甸园区大小。YGC:年轻代垃圾回收次数。FGC:老年代回收次数。
5. 老年代垃圾回收统计
[root@dam ~]# jps
2008 dam.jar
22607 Jps
[root@dam ~]# jstat -gcold 2008
MC:方法区大小。MU:方法区使用大小。CCSC:压缩类空间大小。CCSU:压缩类空间使用大小。OC:老年代大小。OU:老年代使用大小。YGC:年轻代垃圾回收次数。FGC:老年代垃圾回收次数。FGCT:老年代垃圾回收消耗时间。GCT:垃圾回收消耗总时间。
6. 老年代内存统计
[root@dam ~]# jps
2008 dam.jar
6558 Jps
[root@dam ~]# jstat -gcoldcapacity 2008
OGCMN:老年代最小容量。OGCMX:老年代最大容量。OGC:当前老年代大小。OC:老年代大小。YGC:年轻代垃圾回收次数。FGC:老年代垃圾回收次数。FGCT:老年代垃圾回收消耗时间。GCT:垃圾回收消耗总时间。
7. 元数据空间统计
[root@dam ~]# jps
2008 dam.jar
14921 Jps
[root@dam ~]# jstat -gcmetacapacity 2008
MCMN:最小元数据容量。MCMX:最大元数据容量。MC:当前元数据空间大小。CCSMN:最小压缩类空间大小。CCSMX:最大压缩类空间大小。CCSC:当前压缩类空间大小。YGC:年轻代垃圾回收次数。FGC:老年代垃圾回收次数。FGCT:老年代垃圾回收消耗时间。GCT:垃圾回收消耗总时间。
[root@dam ~]# jps
20384 Jps
2008 dam.jar
[root@dam ~]# jstat -gcutil 2008
S0:幸存1区当前使用比例。S1:幸存2区当前使用比例。E:伊甸园区使用比例。O:老年代使用比例。M:元数据区使用比例。CCS:压缩使用比例。YGC:年轻代垃圾回收次数。FGC:老年代垃圾回收次数。FGCT:老年代垃圾回收消耗时间。GCT:垃圾回收消耗总时间。
六、
JVM 运行情况预估
JVM优化参数。
- 堆内存大小。
- 年轻代大小。
Eden和Survivor区的比例。- 老年代的大小。
- 大对象的阈值。
- 大龄对象进入老年代的阈值等。
JVM优化思路。
- 尽量让每次
YoungGC后的存活对象,小于Survivor区域的50%。- 尽量让对象,都留存在年轻代里。
- 尽量别让对象,进入老年代。
- 尽量减少
FullGC的频率,避免频繁FullGC对JVM性能的影响。
1. 年轻代对象增长的速率
[root@dam ~]# jps
27777 Jps
2008 dam.jar
# 每隔`1`秒执行`1`次命令,共执行`10`次。
[root@dam ~]# jstat -gc 2008 1000 10
- 每隔
1秒执行1次命令,共执行10次。
- 观察
Eden区的使用,来估算每秒Eden区大概新增多少对象。- 如果系统负载不高,可以把频率
1秒换成1分钟,甚至10分钟来观察整体情况。- 注意,一般系统可能有高峰期和日常期,所以需要在不同的时间,分别估算不同情况下对象增长速率。
2.
YoungGC 的触发频率和每次耗时
- 知道年轻代对象增长速率,就能根据
Eden区的大小,推算出YoungGC大概多久触发一次。YoungGC的平均耗时,可以通过YGCT/YGC公式算出。- 根据结果大概就能知道,系统大概多久会因为
YoungGC的执行而卡顿多久。
3. 每次
YoungGC 后有多少对象存活和进入老年代
[root@dam ~]# jps
27777 Jps
2008 dam.jar
# 每隔`5`分钟执行`1`次命令,共执行`10`次。
[root@dam ~]# jstat -gc 2008 300000 10
- 知道
YoungGC的频率,假设是每5分钟一次,观察每次Eden、Survivor区 和 老年代 使用的变化情况。- 在每次
GC后Eden区使用一般会大幅减少,Survivor和 老年代 都有可能增长,这些增长的对象就是每次YoungGC后存活的对象。- 同时还可以看出每次
YoungGC后进入老年代大概多少对象,从而可以推算出老年代对象增长速率。
4.
FullGC 的触发频率和每次耗时
- 知道老年代对象的增长速率,就可以推算出
FullGC的触发频率了。FullGC的每次耗时,可以用公式FGCT/FGC计算得出。
七、
JVM 调优概念
- 尽可能让对象,都在 新生代 里分配和回收。
- 尽量别让太多对象,频繁进入 老年代,避免频繁对 老年代 进行垃圾回收。
- 同时给系统充足的内存大小,避免 新生代 频繁的进行垃圾回收。
1.
JVM 相关参数
-Dcatalina.home=E:\tools\tomcat\apache-tomcat-7.0.67
-Dcatalina.base=E:\tools\tomcat\apache-tomcat-7.0.67
-Djava.endorsed.dirs=E:\tools\tomcat\apache-tomcat-7.0.67\endorsed
-Djava.io.tmpdir=E:\tools\tomcat\apache-tomcat-7.0.67\temp
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
# 堆溢出异常
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=E:\tools\tomcat\apache-tomcat-7.0.67\bin\java_heapdump.hprof
-Djava.util.logging.config.file=E:\tools\tomcat\apache-tomcat-7.0.67\conf\logging.properties
-javaagent:E:\tools\tanzhen\tingyun\tingyun-agent-java.jar
# `FullGC`异常
-XX:+HeapDumpBeforeFullGC
-XX:+HeapDumpAfterFullGC
-XX:NewSize=6144M
# 元空间
-XX:MetaspaceSize=1024M
-XX:MaxMetaspaceSize=2048M
# 堆总大小
-Xms1536M
# 堆最大大小
-Xmx1536M
# 堆新生代大小
-Xmn512M
-Xmn1024M
# 栈大小
-Xss256K
# 新生代比率(6 : 1 : 1)
-XX:SurvivorRatio=6
# 方法区
-XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize=256M
# 新生代`GC`
-XX:+UseParNewGC
# CMS(老年代`75%`触发`FULLGC`)
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:CMSInitiatingOccupancyFraction=92
-XX:+UseCMSInitiatingOccupancyOnly
2. 参数案例
java -jar test.jar
# 堆`3G`
# 年轻代:`Eden=800M`、`S0=100M`、`S1:100M`
# 老年代:`Old=2G`
-Xms3G \
-Xmx3G \
-Xss1M \
-XX:MetaspaceSize=512M \
-XX:MaxMetaspaseSize=512M
java -jar test.jar
# 堆`3G`
# 年轻代:`Eden=1.6G`、`S0=200M`、`S1=200M`
# 老年代:`Old=1G`
-Xms3G \
-Xmx3G \
-Xmn2G \
-Xss1M \
-XX:MetaspaceSize=512M \
-XX:MaxMetaspaseSize=512M
八、
JVM 调优案例
1. 系统频繁
FullGC,导致系统卡顿
- 机器配置:双核4G。
JVM内存大小:2G。- 系统运行时间:7天。
- 期间发生的
FullGC次数和耗时:500多次,200多秒。- 期间发生的
YoungGC次数和耗时:1万多次,500多秒。
- 大致估算每天会发生
70多次FullGC,平均每小时3次,每次FullGC在400毫秒左右。- 每天会发生
1000多次YoungGC,每分钟会发生1次,每次YoungGC在50毫秒左右。
JVM调优参数。
‐Xms1536M \
‐Xmx1536M \
‐Xmn512M \
‐Xss256K \
‐XX:SurvivorRatio=6 \
‐XX:MetaspaceSize=256M \
‐XX:MaxMetaspaceSize=256M \
‐XX:+UseParNewGC \
‐XX:+UseConcMarkSweepGC \
‐XX:CMSInitiatingOccupancyFraction=75 \
‐XX:+UseCMSInitiatingOccupancyOnly
- 结合对象挪动到老年代的规则,推理程序可能存在的一些问题。
- 打印
jstat的结果。
YoungGC和FullGC都太频繁了。- 有大量的对象,频繁的被挪动到老年代。
- 打印
jmap的结果。
/**
* @author wy
* describe
*/
@RestController
public class IndexController {
@GetMapping("/getPersons")
public String getPersons() {
List<Person> persons = queryPersons();
for (Person person : persons) {
System.out.println(person);
}
return "Success";
}
/**
* 模拟批量查询用户
*/
private List<Person> queryPersons() {
List<Person> persons = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
persons.add(new Person(String.valueOf(i++), 18));
}
return persons;
}
}
JVM调优参数。
‐Xms1536M \
‐Xmx1536M \
‐Xmn1024M \
‐Xss256K \
‐XX:SurvivorRatio=6 \
‐XX:MetaspaceSize=256M \
‐XX:MaxMetaspaceSize=256M \
‐XX:+UseParNewGC \
‐XX:+UseConcMarkSweepGC \
‐XX:CMSInitiatingOccupancyFraction=92 \
‐XX:+UseCMSInitiatingOccupancyOnly
Java的代码也是需要优化的,一次查询出500M的对象出来,明显不合适。- 要根据各种原则尽量优化到合适的值,尽量消除这种朝生夕死的对象导致的
FullGC。
2.
OOM 调优
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: Java metaspace
java.lang.OutOfMemoryError: Java perm gen
java.lang.OutOfMemoryError: GC overhead limit exceeded
# 垃圾回收统计
jstat -gc 30046
# 新生代
jstat -gcnew 30046
# 老年代
jstat -gcold 30046
# 元空间
jstat -gcmetacapacity 30046
[root@DB ~]# jps
20697 qs-1.0.jar
21759 Jps
[root@DB ~]# jstat -gc 20697
---
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
17920.0 20480.0 16880.1 0.0 279552.0 36980.2 84992.0 40565.8 71424.0 68320.8 8960.0 8407.2 18 0.275 3 0.249 0.523
106496.0 106496.0 0.0 0.0 107520.0 107520.0 642048.0 630045.5 81920.0 77122.4 10240.0 9415.6 209 6.728 134 97.947 104.675
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
131072.0 131072.0 0.0 44847.0 786432.0 761630.0 524288.0 0.0 56448.0 53894.1 7296.0 6756.6 3 0.229 0 0.000 0.229
131072.0 131072.0 0.0 121469.0 786432.0 786432.0 524288.0 475417.2 81920.0 77610.7 10240.0 9475.0 25 3.058 5 1.409 4.468
131072.0 131072.0 0.0 0.0 786432.0 733991.6 524288.0 478621.7 81920.0 77286.1 10240.0 9428.9 36 3.578 48 29.893 33.470
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
131072.0 131072.0 0.0 44688.1 786432.0 749633.6 524288.0 0.0 56448.0 53866.6 7296.0 6759.5 3 0.256 0 0.000 0.256
131072.0 131072.0 0.0 0.0 786432.0 221083.2 524288.0 524288.0 81664.0 77483.7 10240.0 9466.5 23 3.125 13 7.530 10.656
131072.0 131072.0 131072.0 0.0 786432.0 786432.0 524288.0 517821.8 82176.0 77772.3 10240.0 9500.5 27 3.289 98 80.753 84.042
# 修改后
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
131072.0 131072.0 0.0 44712.8 786432.0 766408.2 524288.0 0.0 56192.0 53799.1 7296.0 6753.7 3 0.236 0 0.000 0.236
131072.0 131072.0 87523.0 0.0 786432.0 741640.9 1048576.0 767878.5 81664.0 77347.5 10240.0 9472.9 45 5.594 10 3.956 9.550
# 4G 对比 8G
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
131072.0 131072.0 0.0 0.0 786432.0 235652.1 524288.0 57379.7 87168.0 82038.8 10624.0 9826.2 67 5.968 16040 3325.389 3331.357
166912.0 174080.0 0.0 75378.4 253952.0 52146.1 1316352.0 1030067.7 81792.0 77861.7 10112.0 9472.5 254 8.709 31 27.906 36.615
nohup java \
-XX:+HeapDumpOnOutOfMemoryError \
# 堆总大小
-Xms1536M \
# 堆最大大小
-Xmx2048M \
# 堆新生代大小
-Xmn1024M \
# 栈大小
-Xss256K \
# 新生代比率(6 : 1 : 1)
-XX:SurvivorRatio=6 \
# 方法区
-XX:MetaspaceSize=256M \
-XX:MaxMetaspaceSize=256M \
# 新生代`GC`
-XX:+UseParNewGC \
# CMS(老年代`92%`触发`FULLGC`)
-XX:+UseConcMarkSweepGC \
-XX:CMSInitiatingOccupancyFraction=92 \
-XX:+UseCMSInitiatingOccupancyOnly \
-jar ./qs-0.0.1.jar \
--server.port=9000 \
>running.log &
九、
JVM 优化扩展
1. 内存泄露
- 一般电商架构可能会使用多级缓存架构,就是
Redis加上JVM级缓存。
- 大多可能为了图方便,对于
JVM级缓存就简单使用一个HashMap,于是不断往里面放缓存数据。- 但是很少考虑这个
Map的容量问题,结果这个缓存Map越来越大,一直占用着老年代的很多空间。- 时间长了就会导致
FullGC非常频繁,这就是一种内存泄漏。- 对于一些老旧数据没有及时清理,导致一直占用着宝贵的内存资源。
- 时间长了除了导致
FullGC,还有可能导致OOM。
- 这种情况完全可以考虑采用一些成熟的
JVM级缓存框架来解决。
比如:Ehcache等自带一些LRU数据淘汰算法的框架,来作为JVM级的缓存。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)