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

# 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