Skip to content

一、分析问题

分析一: 应用程序占用堆内存(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 (默认) => 新生代为占比 1512 _ 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]

分析三: 排查【内存泄漏】

  1. 启动时,使用 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]
  1. 使用 Jvisualvm 工具,执行垃圾回收。

使用 Jvisualvm工具 执行垃圾回收, 如下图所示:

分析四: 排查【内存溢出】

  1. 使用 Jmap 内存分析, 运行参数加上 -XX:+PrintGCDetails 输出 GC 回收日志
shell
# 查看JAVA进程
$ jps

# 使用 Jmap 工具,查看堆内存占用情况
$ jmap -heap 进程ID
  1. 使用 Jvisualvm 通过图形化界面查看内存信息, 运行参数加上 `-XX:+PrintGCDetails`` 输出 GC 回收日志

二、优化操作

优化一: 分配应用程序【最大内存和最小内存】

  • -Xms512m -Xmx512m -Xms用于表示堆区的起始内存, -Xmx用于表示堆区的最大内存。

  • 通常会将 -Xms-Xmx 两个参数 配置相同的值

    • 频繁的扩容和释放造成不必要的压力,避免在 GC 之后调整堆内存给服务器带来压力。如果两个设置一样的就少了频繁扩容和缩容的步骤。
  • 默认情况下:

    • 初始内存大小:物理电脑内存大小/64
    • 最大内存大小:物理电脑内存大小/4
    java
    import 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:SurvivorRatio 设置 新生代中 edenS0S1空间的比例
  • 默认: -XX:SurvivorRatio=8 => Eden:S0:S1=8:1:1

例如: 启动参数设置为 -XX:SurvivorRatio=4 => Eden:S0:S1=4:1:1

优化三: 解决【内存泄漏】

  • 注意循环赋值新增对象,需要执行后释放循环数量不适宜过大

优化四:解决【内存溢出】

  • 排查溢出问题,将内存扩大,如果扩大无法解决内存异常优化代码减少代码创建对象内存使用情况,使用 GC 回收垃圾对象