You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
yudao/DIRECT_MEMORY_ISSUE.md

258 lines
5.9 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# ONNX 直接内存不足问题解决指南
## 问题现象
```
java.lang.OutOfMemoryError: Unable to allocate 12582912 bytes
at java.nio.DirectByteBuffer.allocateDirect(ByteBuffer.java:343)
at ai.onnxruntime.OrtUtil.prepareBuffer(OrtUtil.java:507)
at ai.onnxruntime.OnnxTensor.createTensor(OnnxTensor.java:690)
```
## 问题分析
### 什么是直接内存(Direct Memory)?
直接内存是 Java 在堆外分配的内存,用于:
- **NIO 操作**:与操作系统进行高效数据交换
- **图像处理**:OpenCV、ONNX 等需要直接访问内存
- **GPU 交互**:与底层驱动通信
### 为什么会不足?
1. **Java 默认限制**
- 默认:接近系统内存上限
- 但实际可用受限于:
* 已分配的堆内存(2GB)
* 其他库占用的内存(OpenCV、相机驱动)
* 操作系统保留内存
2. **ONNX 的内存需求**
- 每次推理需要创建张量
- 高分辨率图像需要更多内存
- 并发请求会累积内存占用
3. **内存泄漏风险**
- DirectByteBuffer 需要手动释放
- ONNX 对象未正确关闭
- 请求过多,GC 无法及时回收
## 解决方案
### 方案 1:增加直接内存限制(推荐)
```bash
-XX:MaxDirectMemorySize=1g
```
**说明:**
- 将直接内存限制设置为 1GB
- 足够处理多个并发 ONNX 请求
- 在 7GB 系统上合理分配
**已更新文件:**
- `LxCameraApi-start.bat`
- `optimized-jvm-start-template.bat`
- `yudao-server/install-service.bat`
- `yudao-server/start.bat`
### 方案 2:优化 ONNX 代码
**在代码中确保资源释放:**
```java
// 错误示例
OnnxTensor tensor = OnnxTensor.createTensor(ortEnvironment, buffer, shape);
// 忘记释放!
// 正确示例
try (OnnxTensor tensor = OnnxTensor.createTensor(ortEnvironment, buffer, shape)) {
// 使用 tensor
} // 自动释放
// 或者手动释放
OnnxTensor tensor = OnnxTensor.createTensor(ortEnvironment, buffer, shape);
try {
// 使用 tensor
} finally {
tensor.close(); // 确保释放
}
```
**检查代码位置:**
- `com.example.lxcameraapi.service.IndustrialCamera.algorithm.ONNXServiceNew.detect:568`
- 确保所有 OnnxTensor 和 OrtSession 正确关闭
### 方案 3:减少并发请求数
```java
// 使用线程池限制并发
ExecutorService executor = Executors.newFixedThreadPool(2); // 限制为 2 个并发
// 或者使用信号量
Semaphore semaphore = new Semaphore(3); // 最多 3 个并发
```
### 方案 4:减小图像尺寸
```java
// 在推理前缩小图像
BufferedImage resizedImage = resize(originalImage, 640, 480);
// 使用 resizedImage 进行推理
```
## 完整的 JVM 参数配置
```bash
-server
# 堆内存
-Xms1g
-Xmx2g
# 元空间
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m
# 代码缓存
-XX:ReservedCodeCacheSize=512m
# 直接内存(关键!)
-XX:MaxDirectMemorySize=1g
# 堆地址(解决本地内存冲突)
-XX:HeapBaseMinAddress=0x100000000
# 垃圾回收器
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
# 错误处理
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=logs/heap_dump.hprof
-XX:ErrorFile=logs/hs_err_pid%%p.log
# 系统属性
-Dfile.encoding=UTF-8
-Duser.timezone=Asia/Shanghai
```
## 监控直接内存使用
### 1. 使用 JConsole
```bash
jconsole <pid>
```
查看:`内存` → `Buffer Pool` → `direct`
### 2. 使用 JMX
```java
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("java.nio:type=BufferPool,name=direct");
Long count = (Long) mbs.getAttribute(name, "Count");
Long totalCapacity = (Long) mbs.getAttribute(name, "TotalCapacity");
Long memoryUsed = (Long) mbs.getAttribute(name, "MemoryUsed");
System.out.println("Direct Buffers: " + count);
System.out.println("Total Capacity: " + (totalCapacity / 1024 / 1024) + " MB");
System.out.println("Memory Used: " + (memoryUsed / 1024 / 1024) + " MB");
```
### 3. 使用 JVM 参数监控
```bash
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
```
## 内存分配示例
### 单次 ONNX 推理的内存使用
| 组件 | 内存占用 | 说明 |
|------|---------|------|
| 输入图像(640x480x3) | 0.9 MB | RGB 图像数据 |
| ONNX 输入张量 | 1.8 MB | Float32 格式 |
| 中间激活值 | 5-20 MB | 模型大小决定 |
| 输出张量 | 0.1-1 MB | 取决于输出维度 |
| **总计** | **8-25 MB** | 单次推理 |
### 并发 3 个请求
- 直接内存使用:24-75 MB
- 加上缓冲和临时对象:约 100 MB
- **1GB 限制足够处理 10+ 个并发请求**
## 验证修复
启动应用后,检查日志:
```bash
# 查看堆地址(应该在 4GB 以上)
Heap address: 0x0000000100000000
# 如果没有 "MaxDirectMemorySize" 警告,说明参数生效
```
## 如果问题仍然存在
### 1. 进一步增加直接内存限制
```bash
-XX:MaxDirectMemorySize=2g
```
### 2. 禁用某些功能
如果不需要高并发,可以限制:
```bash
# 在 application.yml 中设置
server:
tomcat:
threads:
max: 10 # 减少最大线程数
```
### 3. 分析内存泄漏
使用以下工具:
```bash
# 使用 MAT 分析堆转储
mat heap_dump.hprof
# 查看 DirectByteBuffer 的实例数量
# 检查是否有未释放的 ONNX 对象
```
## 性能影响
### MaxDirectMemorySize 对性能的影响
| 大小 | 并发能力 | 性能影响 |
|------|---------|---------|
| 512MB | 5-10 个请求 | 可能受限 |
| 1GB | 10-20 个请求 | 平衡点 ✓ |
| 2GB | 20+ 个请求 | 轻微增加 GC 压力 |
**建议:从 1GB 开始,根据实际使用情况调整。**
## 相关文档
- `JVM_OPTIMIZATION_GUIDE.md` - 通用 JVM 优化指南
- `hs_err_pid*.log` - 崩溃日志分析
## 总结
**立即行动:**
1. 重新启动 `LxCameraApi-start.bat`(已包含 `-XX:MaxDirectMemorySize=1g`)
2. 测试 ONNX 推理功能
3. 监控直接内存使用情况
**如果还有问题:**
1. 检查代码是否有内存泄漏
2. 增加到 2GB
3. 减少并发请求数
4. 考虑增加物理内存到 16GB