# 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 ``` 查看:`内存` → `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