一、分析问题
分析一: 应用程序占用堆内存(Heap)信息
- 查看 Java 后台进程
shell
$ jps
## 执行结果
14656 Jps
129060 SystemCloudApplication
14472 GatewayApplication
23064 DataExporterApplication
18316 RemoteMavenServer36
129996 Launcher
- 查看
SystemCloudApplication
后台进程, 通过jmap -heap 进程号
shell
$ jmap -heap 129060
## 执行结果如下:
Attaching to process ID 129060, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.271-b09
using thread-local object allocation.
# 并行线程数有 6 个
Parallel GC with 6 thread(s)
# 一、堆内存配置
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
## 最大堆内存空间: 512 MB
MaxHeapSize = 536870912 (512.0MB)
## 新生代内存空间: 85MB
NewSize = 89128960 (85.0MB)
## 新生代最大内存空间: 170.5MB
MaxNewSize = 178782208 (170.5MB)
## 老年代内存空间: 171MB
OldSize = 179306496 (171.0MB)
## 新生代占比: 2
NewRatio = 2
## 幸存者占比: 8
SurvivorRatio = 8
## 元空间内存空间为: 20MB
MetaspaceSize = 21807104 (20.796875MB)
## 压缩类内存空间为: 1024MB
CompressedClassSpaceSize = 1073741824 (1024.0MB)
## 最大元空间内存空间为: 17592186044415MB
MaxMetaspaceSize = 17592186044415 MB
## G1 堆地区空间大小为: 0MB
G1HeapRegionSize = 0 (0.0MB)
# 二、堆内存使用
Heap Usage:
## 新生代
PS Young Generation
### Eden 区
Eden Space:
#### 容量: 约120MB
capacity = 125304832 (119.5MB)
#### 使用: 约30MB
used = 31100840 (29.660072326660156MB)
#### 未使用: 约90MB
free = 94203992 (89.83992767333984MB)
#### 使用率: 24%
24.820144206410173% used
### From 区
From Space:
#### 容量: 约17MB
capacity = 17825792 (17.0MB)
#### 使用: 约17MB
used = 17574920 (16.76074981689453MB)
#### 未使用: 约0.2MB
free = 250872 (0.23925018310546875MB)
#### 使用率: 98.5%
98.59264598173253% used
### To 区
To Space:
#### 容量: 约26MB
capacity = 27262976 (26.0MB)
#### 使用: 约0MB
used = 0 (0.0MB)
#### 未使用: 约26MB
free = 27262976 (26.0MB)
#### 使用率: 0.0%
0.0% used
## 老年代
PS Old Generation
#### 容量: 约341.5MB
capacity = 358088704 (341.5MB)
#### 使用: 约247MB
used = 259611320 (247.58464813232422MB)
#### 未使用: 约93.9MB
free = 98477384 (93.91535186767578MB)
#### 使用率: 72.4%
72.4991648996557% used
72840 interned Strings occupying 7540376 bytes.
- 当前设置启动程序最大内存配置为: -Xmx512m
log
# 新生代占比: 2 (默认) => 新生代为占比 1(512 _ 1/3) 老年代占比为 2(512 _ 2/3 )
NewRatio = 2
# 新生代最大内存空间: 170MB => 新生代占用内存空间约: 120MB(eden 区) + 17MB(from 区) + 26MB(to 区) = 163MB
MaxNewSize = 178782208 (170.5MB)
# 老年代占用内存空间约: 341.5MB
OldSize = 179306496 (171.0MB)
新生代、老年代比例参数
- 在 HotSpot 中,
Eden区
和另外两个survivor区
默认所占的比例是8:1:1
, - 当然开发人员可以通过选项
-XX:SurvivorRatio调整
这个空间比例, 几乎所有的 Java 对象都是在 Eden 区被 new 出来的。比如: -XX:SurvivorRatio=8 - 绝大部分的 Java 对象的销毁都在新生代进行了(有些大的对象在 Eden 区无法存储时候,将直接进入老年代),IBM 公司的专门研究表明,新生代中 80%的对象都是“朝生夕死”的。
- 可以使用选项
"-Xmn"
设置新生代最大内存大小,但这个参数一般使用默认值就可以了.
分析二: 查看应用程序【发生GC操作】
-XX:+PrintGCDetails
输出 JVM GC 回收的日志信息
shell
$ java -Xms512m -Xmx512m -XX:+PrintGCDetails system-application.jar
日志输出
log
# 当前执行 Minor GC
[GC (Allocation Failure) [PSYoungGen: 131584K->5877K(153088K)] 131584K->5885K(502784K), 0.0083668 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 137461K->5949K(153088K)] 137469K->5965K(502784K), 0.0053099 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 137533K->6401K(153088K)] 137549K->6489K(502784K), 0.0061064 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 137985K->8217K(153088K)] 138073K->8313K(502784K), 0.0083541 secs] [Times: user=0.01 sys=0.01, real=0.00 secs]
[GC (Metadata GC Threshold) [PSYoungGen: 47002K->7533K(153088K)] 47098K->7637K(502784K), 0.0105789 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
# 当前执行了 FULL GC 垃圾回收
[Full GC (Metadata GC Threshold) [PSYoungGen: 7533K->0K(153088K)] [ParOldGen: 104K->7426K(349696K)] 7637K->7426K(502784K), [Metaspace: 20548K->20548K(1067008K)], 0.0307101 secs] [Times: user=0.05 sys=0.00, real=0.03 secs]
[GC (Allocation Failure) [PSYoungGen: 131584K->3460K(164864K)] 139024K->10909K(514560K), 0.0089160 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 159620K->8565K(164864K)] 167069K->16022K(514560K), 0.0043587 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
分析三: 排查【内存泄漏】
- 启动时,使用
java -XX:+PrintGCDetails 启动程序类
shell
$ java -XX:+PrintGCDetails HeapMemoryLeakExample
# 输出打印GC回收日志
[GC (System.gc())
[PSYoungGen: 14172K->10704K(76288K)] 14172K->10712K(251392K), 0.0020514 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 10704K->0K(76288K)]
[ParOldGen: 8K->10581K(175104K)] 10712K->10581K(251392K),
[Metaspace: 3132K->3132K(1056768K)], 0.0028869 secs]
[Times: user=0.00 sys=0.01, real=0.00 secs]
- 使用
Jvisualvm
工具,执行垃圾回收。
使用 Jvisualvm
工具 执行垃圾回收, 如下图所示:
分析四: 排查【内存溢出】
- 使用
Jmap
内存分析, 运行参数加上-XX:+PrintGCDetails
输出 GC 回收日志
shell
# 查看JAVA进程
$ jps
# 使用 Jmap 工具,查看堆内存占用情况
$ jmap -heap 进程ID
- 使用
Jvisualvm
通过图形化界面查看内存信息, 运行参数加上 `-XX:+PrintGCDetails`` 输出 GC 回收日志
二、优化操作
优化一: 分配应用程序【最大内存和最小内存】
-Xms512m -Xmx512m
-Xms
用于表示堆区的起始内存,-Xmx
用于表示堆区的最大内存。通常会将
-Xms
和-Xmx
两个参数 配置相同的值。- 频繁的扩容和释放造成不必要的压力,避免在 GC 之后调整堆内存给服务器带来压力。如果两个设置一样的就少了频繁扩容和缩容的步骤。
默认情况下:
- 初始内存大小:物理电脑内存大小/64
- 最大内存大小:物理电脑内存大小/4
javaimport lombok.extern.slf4j.Slf4j; /** * @author Calvin * @date 2023/8/7 * @since v1.0.0 */ @Slf4j public class SystemMemoryUtil { /** * 获取启动程序默认-Xms * * @return {@link String} */ public static String getDefaultXmsMemory() { // 返回Java虚拟机中的堆内存总量 long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024; log.info("-Xms : " + initialMemory + "M"); String xmsGb = initialMemory * 64.0 / 1024 + "G"; log.info("系统内存大小为:" + xmsGb); return xmsGb; } /** * 获取启动程序默认-Xmx * * @return {@link String} */ public static String getDefaultXmxMemory() { // 返回Java虚拟机试图使用的最大堆内存量 long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024; log.info("-Xmx : " + maxMemory + "M"); String xmxGb = maxMemory * 4.0 / 1024 + "G"; log.info("系统内存大小为:" + xmxGb); return xmxGb; } } /* * 输出如下: * -Xms : 245M * 系统内存大小为:15.3125G * -Xmx : 3641M * 系统内存大小为:14.22265625G */
shell
$ java -Xms512m -Xmx512m system-application.jar
优化二:【新生代和老年代的分配内存比例】
-XX:NewRatio
设置新生代比例参数- 配置年轻代与老年代在堆结构的占比
- 默认:-XX:NewRatio=2 新生代占 1、老年代占 2、年轻代占整个堆的 1/3、老年代占整个堆的 2/3
例如: -XX:NewRatio=4 新生代占 1,
- (老年代占 4) 512*4/5=409.6m
- (新生代占 1) 512*1/5=102.4m
- 默认:-XX:NewRatio=2 新生代占 1、老年代占 2、年轻代占整个堆的 1/3、老年代占整个堆的 2/3
-XX:SurvivorRatio
设置 新生代中eden
、S0
、S1
空间的比例- 默认:
-XX:SurvivorRatio=8
=> Eden:S0:S1=8:1:1
例如: 启动参数设置为 -XX:SurvivorRatio=4 => Eden:S0:S1=4:1:1
优化三: 解决【内存泄漏】
- 注意
循环赋值新增对象
,需要执行后释放
,循环数量不适宜过大
。
优化四:解决【内存溢出】
- 排查溢出问题,将
内存扩大
,如果扩大无法解决内存异常
,优化代码减少代码创建对象
内存使用情况,使用GC 回收垃圾对象
。