Skip to main content

JAVA内存泄漏问题分析优化

根据最新提供的系统监控数据和进程信息,以下是深入分析和具体建议:

图片.png

## 关键进程内存占用分析

### 内存消耗Top 3进程
1. **Tomcat Java应用 (PID 31037)**:
   - 占用内存:5.26GB (32.5%)
   - JVM配置:`-Xms2048m -Xmx4096m` (堆内存2-4GB)
   - 实际RSS远超堆设置,可能存在:
     * 直接内存泄漏(通过`-XX:MaxDirectMemorySize=1024m`配置)
     * 大量JNI调用或本地内存分配

2. **MySQL (PID 1129)**:
   - 占用内存:2.53GB (15.6%)
   - 长期运行(88460小时),需检查:
     ```bash
     mysqladmin variables | grep buffer_pool
     mysqladmin status | grep -E 'Threads|Memory'
     ```

3. **shun-sync服务 (PID 5522)**:
   - 占用内存:1.35GB (8.3%)
   - JVM配置:`-Xms1024m -Xmx1024m` (堆设置合理)

## 内存问题诊断

### 异常现象确认
1. **物理内存**:
   - 总内存:16GB
   - 已用:15.97GB (99.8%)
   - 空闲:186MB (1.2%)
   - 缓存:2.3GB (偏低)

2. **交换空间**:
   - 总量:4GB
   - 使用量:1.3MB (0.03%)
   - 缓存比例:10.5% (正常)

3. **内存分布**:
   - Active内存:12.4GB (其中匿名页11.1GB)
   - Inactive内存:2.8GB

## 根本原因分析

1. **Tomcat内存泄漏**:
   - RSS(5.26GB) > Xmx(4GB) + MaxDirectMemory(1GB)
   - 可能原因:
     * JNI库内存泄漏
     * 未受JVM管控的本地内存分配
     * 线程栈过多(`-Xss`设置过大)

2. **swappiness配置不当**:
   - 当前配置可能为0,导致系统宁愿触发OOM也不使用swap
   - 验证命令:
     ```bash
     cat /proc/sys/vm/swappiness
     ```

3. **MySQL内存配置**:
   - 未优化的InnoDB缓冲池可能导致内存碎片

## 紧急处理方案

### 立即释放内存
```bash
# 清理页面缓存(临时方案)
sync; echo 1 > /proc/sys/vm/drop_caches

# 重启最耗内存的Tomcat(需安排维护窗口)
systemctl restart tomcat
```

### JVM内存诊断
```bash
# 安装JDK工具
apt-get install -y openjdk-8-jdk

# 检查Tomcat内存详情
jcmd 31037 VM.native_memory summary

图片.png


jmap -histo:live 31037 | head -20

图片.png

```

## 长期优化建议

1. **Tomcat优化**:
   ```diff
   - -XX:MaxDirectMemorySize=1024m
   + -XX:MaxDirectMemorySize=512m
   + -XX:NativeMemoryTracking=summary
   + -XX:+AlwaysPreTouch
   ```

2. **MySQL优化**:
   ```ini
   # /etc/mysql/my.cnf
   [mysqld]
   innodb_buffer_pool_size = 1G
   innodb_buffer_pool_instances = 4
   ```

3. **系统参数调整**:
   ```bash
   # 防止OOM killer误杀
   echo 'vm.panic_on_oom=2' >> /etc/sysctl.conf
   echo 'vm.overcommit_memory=2' >> /etc/sysctl.conf
   sysctl -p
   ```

4. **监控增强**:
   ```bash
   # 内存泄漏检测
   apt-get install -y sysstat valgrind
   sar -r 60 -o /var/log/sa/memlog &
   ```

## 验证指标

优化后应达到:
- 内存使用率 <90%
- Swap使用率 1-5%
- 缓存占比 >20%
- 关键进程RSS ≈ Xmx + 30%

根据 `jcmd` 和 `jmap` 的输出分析,Tomcat 进程 (PID 31037) 存在严重的内存管理问题。以下是详细诊断和解决方案:

---

## **1. 内存占用分析**
### **1.1 Native Memory 分布**
- **总 Reserved**: 6,165,979 KB (~6.1GB)
- **总 Committed**: 4,780,715 KB (~4.78GB)
  
| 内存区域          | Reserved (KB) | Committed (KB) | 问题点 |
|------------------|--------------|---------------|--------|
| Java Heap        | 4,194,304    | 3,899,392     | 接近 Xmx 上限 (4GB) |
| Class Metadata   | 1,280,500    | 260,596       | **MetaSpace 占用高** |
| Thread Stacks    | 186,976      | 186,976       | **179 个线程,栈占用过大** |
| Code Cache       | 273,364      | 203,492       | **JIT 编译代码缓存较大** |
| GC 相关          | 159,054      | 158,478       | G1GC 占用偏高 |
| Symbol Storage   | 44,112       | 44,112        | 字符串常量池较大 |

### **1.2 Heap 内存分析 (`jmap -histo`)**
- **Top 内存占用对象**:
  - `char[]` (`[C`): **379MB**
  - `byte[]` (`[B`): **171MB**
  - `String`: **13MB**
  - `LinkedHashMap$Entry`: **9.4MB**
  - `Object[]` (`[Ljava.lang.Object;`): **8.5MB**

**问题点**:
- **大量 `char[]` 和 `byte[]` 占用**,可能是:
  - **未优化的字符串处理**(如日志、JSON 解析)
  - **缓存未限制大小**(如 HTTP Session 缓存)
  - **内存泄漏**(如静态集合未清理)

---

## **2. 问题诊断**
### **2.1 MetaSpace 占用高**
- **Class Metadata 占用 260MB**,加载了 **37,630 个类**。
- **可能原因**:
  - **动态类加载**(如 JSP 编译、反射生成类)
  - **未限制 MetaSpace**(默认无上限,可能无限增长)

### **2.2 线程栈占用过大**
- **179 个线程**,每个线程栈占用约 **1MB**(默认 `-Xss1m`)。
- **总栈内存**:179 × 1MB ≈ **179MB**。
- **可能原因**:
  - **线程池配置过大**(如 Tomcat `maxThreads=200`)
  - **线程泄漏**(如未正确关闭线程池)

### **2.3 Direct Memory 泄漏**
- `-XX:MaxDirectMemorySize=1024m` 设置了 **1GB 堆外内存**。
- **NIO Buffer 或 Netty 等框架可能泄漏 Direct Memory**。

---

## **3. 优化方案**
### **3.1 JVM 参数优化**
```diff
# 修改 Tomcat 启动参数(catalina.sh 或 setenv.sh)
- -Xms2048m -Xmx4096m -XX:MaxDirectMemorySize=1024m
+ -Xms2048m -Xmx4096m 
+ -XX:MaxDirectMemorySize=512m          # 限制 Direct Memory
+ -XX:MaxMetaspaceSize=256m            # 避免 MetaSpace 无限增长
+ -Xss256k                             # 减少线程栈大小
+ -XX:+UseG1GC                         # 改用 G1 垃圾回收器
+ -XX:NativeMemoryTracking=detail      # 持续监控 Native Memory
```

### **3.2 检查线程泄漏**
```bash
# 查看 Tomcat 线程数
ps -L -p 31037 | wc -l

# 查看线程栈
jstack 31037 > /tmp/jstack.log
grep "pool-" /tmp/jstack.log | wc -l
```
**优化建议**:
- 调整 `server.xml` 中的 `maxThreads`(如从 200 → 100)。
- 检查是否有自定义线程池未正确关闭。

### **3.3 检查 MetaSpace 泄漏**
```bash
# 查看加载的类数量
jcmd 31037 GC.class_stats | head -20

# 检查是否有动态生成的类
jcmd 31037 VM.class_hierarchy | grep Generated
```
**优化建议**:
- 限制 `-XX:MaxMetaspaceSize=256m`,防止无限增长。
- 检查是否有频繁的 JSP 热部署。

### **3.4 检查 Direct Memory 泄漏**
```bash
# 查看 NIO Buffer 分配
jcmd 31037 VM.native_memory detail | grep "Java Heap\|Direct"
```
**优化建议**:
- 如果使用 Netty,检查 `ByteBuf` 是否正确释放。
- 限制 `-XX:MaxDirectMemorySize=512m`。

---

## **4. 监控与验证**
### **4.1 监控内存变化**
```bash
# 每 5 秒打印内存使用
watch -n 5 'ps -p 31037 -o %mem,rss,comm && jcmd 31037 VM.native_memory summary'
```

### **4.2 触发 Full GC 观察内存回收**
```bash
# 手动触发 Full GC
jcmd 31037 GC.run
```

### **4.3 检查 OOM 风险**
```bash
# 查看 GC 日志(需开启 -Xloggc)
tail -f /usr/local/apache-tomcat-8.0.39/logs/gc.log
```

---

## **5. 结论**
- **主要问题**:
  - **MetaSpace 占用高**(260MB),需限制 `MaxMetaspaceSize`。
  - **线程栈占用大**(179MB),可减少 `-Xss` 或 `maxThreads`。
  - **Direct Memory 可能泄漏**,需限制并检查 NIO Buffer。
  - **Heap 内 `char[]` 和 `byte[]` 占用高**,需检查缓存和字符串处理逻辑。

- **优化后预期**:
  - **RSS 内存下降 1-2GB**(从 5.26GB → 3.5GB 左右)。
  - **减少 OOM 风险**,提高系统稳定性。

建议在 **低峰期重启 Tomcat** 并应用新 JVM 参数,持续监控内存变化。

root@shuncom-01:/usr/local/apache-tomcat-8.0.39/bin# jcmd 31037 VM.native_memory detail | grep "Java Heap\|Direct"
-                 Java Heap (reserved=4194304KB, committed=3646976KB)
[0x00000006c0000000 - 0x00000007c0000000] reserved 4194304KB for Java Heap from
root@shuncom-01:/usr/local/apache-tomcat-8.0.39/bin# jcmd 31037 VM.class_hierarchy | grep Generated               
java.io.IOException: Command failed in target VM
        at sun.tools.attach.LinuxVirtualMachine.execute(LinuxVirtualMachine.java:224)
        at sun.tools.attach.HotSpotVirtualMachine.executeCommand(HotSpotVirtualMachine.java:217)
        at sun.tools.attach.HotSpotVirtualMachine.executeJCmd(HotSpotVirtualMachine.java:199)
        at sun.tools.jcmd.JCmd.executeCommandForPid(JCmd.java:150)
        at sun.tools.jcmd.JCmd.main(JCmd.java:129)
root@shuncom-01:/usr/local/apache-tomcat-8.0.39/bin# jcmd 31037 GC.class_stats | head -20                         
31037:
GC.class_stats command requires -XX:+UnlockDiagnosticVMOptions

根据最新的诊断结果,以下是针对 Tomcat 进程 (PID 31037) 的 最终优化方案 和 关键结论

---

## **1. 关键发现**
### **1.1 Java Heap 使用情况**
- **配置**:`-Xmx4096m` (4GB)
- **实际 Commit**:3.64GB(接近上限,存在 OOM 风险)
- **问题**:Heap 使用率过高,可能由于:
  - 缓存未限制(如 HTTP Session 缓存)
  - 大对象未及时释放(如 `char[]` 占 379MB)

### **1.2 Direct Memory**
- **未检测到 Direct Buffer 泄漏**(`grep "Direct"` 无输出)
- 但 `-XX:MaxDirectMemorySize=1024m` 仍可能被 NIO 或第三方库(如 Netty)使用,需保持限制。

### **1.3 MetaSpace**
- **无法直接获取类统计**(需 `-XX:+UnlockDiagnosticVMOptions`)
- **间接证据**:`Class Metadata` 占用 260MB,加载 37,630 个类,可能存在:
  - 动态类加载(如 JSP 编译)
  - 反射生成类(如 Spring AOP)

---

## **2. 最终优化方案**
### **2.1 修改 Tomcat JVM 参数**
编辑 `catalina.sh` 或 `setenv.sh`,替换原有参数:
```bash
JAVA_OPTS="
  -Xms2048m -Xmx4096m
  -XX:MaxMetaspaceSize=256m           # 限制 MetaSpace 增长
  -XX:MaxDirectMemorySize=512m        # 限制堆外内存
  -Xss256k                            # 减少线程栈大小
  -XX:+UseG1GC                        # 改用 G1 垃圾回收器
  -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/usr/local/apache-tomcat-8.0.39/logs/gc.log
  -XX:NativeMemoryTracking=detail     # 持续监控 Native Memory
"
```

### **2.2 检查线程泄漏**
```bash
# 查看线程数
ps -L -p 31037 | wc -l

# 如果线程数 > 150,调整 Tomcat 的 maxThreads
sed -i 's/maxThreads="200"/maxThreads="100"/g' /usr/local/apache-tomcat-8.0.39/conf/server.xml
```

### **2.3 检查缓存问题**
```bash
# 检查 Tomcat 的 Session 超时设置(单位:分钟)
grep -A 1 "<session-config>" /usr/local/apache-tomcat-8.0.39/conf/web.xml
```
**建议**:确保 `<session-timeout>30</session-timeout>`(默认 30 分钟)。

### **2.4 重启 Tomcat 并监控**
```bash
# 重启 Tomcat
/usr/local/apache-tomcat-8.0.39/bin/shutdown.sh
/usr/local/apache-tomcat-8.0.39/bin/startup.sh

# 监控内存变化
watch -n 5 'ps -p $(pgrep -f "Bootstrap start") -o %mem,rss,comm'
```

---

## **3. 预期优化效果**
| 指标                | 优化前               | 优化后目标         |
|---------------------|----------------------|--------------------|
| **RSS 内存**        | 5.26GB               | 3.5-4GB            |
| **线程栈内存**      | 179MB (179 threads)  | 25MB (100 threads) |
| **MetaSpace**       | 260MB (无限制)       | ≤256MB             |
| **Direct Memory**   | 可能泄漏 (1GB)       | ≤512MB             |

---

## **4. 后续维护建议**
1. **定期检查 GC 日志**:
   ```bash
   tail -f /usr/local/apache-tomcat-8.0.39/logs/gc.log | grep -E "Full GC|OOM"
   ```

2. **设置内存报警**(如 Prometheus + Grafana):
   - 报警规则:`RSS > 3.5GB` 持续 10 分钟。

3. **升级 JDK**:
   - 当前使用 **JDK 1.8.0_111**(旧版,存在已知内存问题),建议升级到 **1.8.0_381+**。

---

## **5. 紧急情况处理**
如果优化后仍出现 **OOM**:
1. **快速 Dump 内存**:
   ```bash
   jmap -dump:live,format=b,file=/tmp/tomcat_heap.hprof 31037
   ```
2. **分析内存泄漏**:
   - 使用 [Eclipse MAT](https://www.eclipse.org/mat/) 分析 `tomcat_heap.hprof`。

---

**结论**:通过限制 MetaSpace、减少线程栈大小、优化 JVM 参数,**预计可降低 1-2GB 内存占用**,显著减少 OOM 风险。建议在低峰期实施并持续监控。