大文件PDF转换成图片上传引发了线上的OOM,看看怎么回事?

问题报错

报错的图片:
在这里插入图片描述
看到明显的错误占用了很大的内存
代码块如下

  PDFRenderer pdfRenderer = new PDFRenderer(pd);
            for (int i = 0; i < pd.getNumberOfPages(); i++) {
                BufferedImage image = pdfRenderer.renderImageWithDPI(i, 300, ImageType.RGB);
                String name = file.getOriginalFilename().substring(0, file.getOriginalFilename().lastIndexOf("."));
                //获取name下划线最后一个前面的值
                name = name.substring(0, name.lastIndexOf("_"));
                Path tempFile = Files.createTempFile(name, ".jpg");
                ImageIO.write(image, "jpg", tempFile.toFile());
                // 上传 OSS
                String imageUrl = uploadToOSS(tempFile, name, i + 1);
                imageUrls.add(imageUrl);
                // 清理临时文件
                Files.deleteIfExists(tempFile);
            }
            return imageUrls;

分析问题

一、可能存在的问题

  • 内存占用方面
    大文件处理:当处理大型 PDF 文件(包含大量页面或高分辨率图像)时,BufferedImage对象会占用大量内存。在循环中,每一页都被渲染成BufferedImage,如果这些图像没有及时释放内存,可能会导致内存占用过高。
    临时文件处理:虽然代码中有清理临时文件的操作Files.deleteIfExists(tempFile);,但是如果在uploadToOSS(tempFile, name, i + 1);步骤中出现异常,临时文件可能无法被正确删除,导致磁盘空间占用和潜在的资源泄漏。
  • 异常处理方面
    在PDDocument.load(file.getInputStream())和ImageIO.write(image, “jpg”, tempFile.toFile())等操作中,如果出现异常,可能会导致PDDocument和其他相关资源没有被正确关闭和释放,进一步引发资源泄漏和潜在的内存问题。
  • 性能方面
    反复创建和删除临时文件可能会对磁盘 I/O 性能产生影响,尤其是在处理大量 PDF 文件时。

二、引发 OOM(Out - Of - Memory)的原因

  • 图像数据积累
    在循环中,pdfRenderer.renderImageWithDPI(i, 300, ImageType.RGB);会将 PDF 页面渲染成BufferedImage。如果 PDF 文件有很多页,这些BufferedImage对象会一直存在于内存中(直到方法结束或者垃圾回收),因为它们被存储在imageUrls列表中(通过后续的uploadToOSS操作,但在上传操作完成前内存中仍持有这些图像数据),可能导致内存耗尽。
  • 垃圾回收不及时
    如果 Java 虚拟机的垃圾回收机制不能及时回收这些大的BufferedImage对象,随着处理的页面越来越多,可用内存会逐渐减少,最终导致 OOM。
    在这里插入图片描述

避免 OOM 的方法

  • 优化内存管理
    及时释放图像资源:在将图像上传到 OSS 后,显式地释放BufferedImage所占用的内存。可以通过将BufferedImage相关数据置为null来提示垃圾回收器回收内存,例如image = null;在uploadToOSS(tempFile, name, i + 1);之后。
  • 调整 JVM 内存参数:
    根据处理的 PDF 文件的大致规模,适当增加 Java 虚拟机的堆内存大小(通过 - Xmx参数),但这只是一种缓解措施,不能从根本上解决内存泄漏问题。
  • 改进文件处理
    使用内存映射文件(如果适用):对于大型 PDF 文件,可以考虑使用内存映射文件技术来减少内存占用。例如,在加载 PDF 文件时,查看是否有支持内存映射的 PDF 库或方法,这样可以让操作系统更高效地管理文件数据在内存中的加载和卸载。
  • 优化临时文件管理:
    确保在任何情况下临时文件都能被正确删除。可以使用try - with - resources语句来确保PDDocument等资源被正确关闭,并且在uploadToOSS操作周围添加try - catch - finally块来保证临时文件的删除。
try (PDDocument pd = PDDocument.load(file.getInputStream())){
    PDFRenderer pdfRenderer = new PDFRenderer(pd);
    for (int i = 0; i < pd.getNumberOfPages(); i++) {
        try {
            BufferedImage image = pdfRenderer.renderImageWithDPI(i, 300, ImageType.RGB);
            String name = file.getOriginalFilename().substring(0, file.getOriginalFilename().lastIndexOf("."));
            name = name.substring(0, name.lastIndexOf("_"));
            Path tempFile = Files.createTempFile(name, ".jpg");
            try {
                ImageIO.write(image, "jpg", tempFile.toFile());
                String imageUrl = uploadToOSS(tempFile, name, i + 1);
                imageUrls.add(imageUrl);
            } finally {
                Files.deleteIfExists(tempFile);
            }
        } catch (Exception e) {
            // 处理图像渲染和临时文件写入异常
        }
    }
    return imageUrls;
} catch (Exception e) {
    // 处理PDDocument加载异常
    throw new RuntimeException(e);
}
  1. 分页处理和流式处理(如果可行)
    分页处理:如果可能,将 PDF 文件分页处理,而不是一次性处理所有页面。例如,可以提供一个机制让用户选择一次处理多少页,处理完一批后释放内存,再处理下一批。
    流式处理:查看是否有支持流式处理 PDF 文件的库或方法,这样可以边读取边处理,避免将整个文件数据加载到内存中。

思考:如何释放 PDDocument 占用的内存?

有很多方法吧
比如:

  1. 使用临时文件:当处理大型PDF文件时,可以配置PDFBox使用临时文件来减少内存使用。这可以通过MemoryUsageSetting.setupTempFileOnly()方法实现,这样PDF文件将临时存放在硬盘上,而不是完全加载到内存中。
PDDocument document = PDDocument.load(new File("path/to/document.pdf"), MemoryUsageSetting.setupTempFileOnly());
  1. 关闭PDDocument对象:确保在不再需要PDDocument对象时关闭它,以释放与之关联的资源。
document.close();
  1. 减少渲染图像的分辨率:在渲染图像时,可以通过降低DPI值或缩小图像尺寸来减少内存使用。
PDFRenderer renderer = new PDFRenderer(document);
renderer.setSubsamplingAllowed(true); // 允许下采样
BufferedImage image = renderer.renderImage(pageIndex, 100); // 使用较低的DPI值
  1. 优化内存设置:调整JVM的内存设置,例如增加最大堆内存-Xmx值,可以为处理大型文件提供更多内存空间。

  2. 自定义资源缓存:通过自定义DefaultResourceCache,可以减少缓存中的资源占用,从而降低内存使用。

  3. 处理完立即释放图像资源:在渲染图像后,及时释放不再需要的图像资源,避免长时间持有这些资源。

  4. 增量加载PDF:如果可能,使用增量加载方法loadNonSeq,这样可以减少初始内存占用,因为只有PDF的部分内容被加载到内存中。

  5. 清理缓存:在Linux系统中,可以通过写入/proc/sys/vm/drop_caches来清除缓存,释放内存。这可以通过定时任务来实现。

  6. 更新PDFBox版本:确保使用的是最新版本的PDFBox,因为新版本可能包含内存优化和bug修复。

  7. 优化PDF文件:在处理前,尝试优化PDF文件,例如通过减小图像大小或移除不必要的内容,以减少处理时的内存需求

朋友们,你有遇到OOM异常吗?如何解决的?

### 关于线OOM (Out Of Memory) 案例分析及解决方案 #### 线上环境中的OOM问题概述 在线上环境中,当应用程序遭遇 Out of Memory (OOM) 错误时,通常会调用 `Thread::ThrowOutOfMemoryError` 函数并传递描述错误详情的消息参数 msg[^1]。这类异常不仅影响用户体验还可能导致服务中断。 #### 实际案例解析 假设某 Android 应用程序频繁出现崩溃现象,在日志中发现大量由系统抛出的 OOM 异常记录。进一步调查表明该应用存在不合理加载图片资源的情况——即一次性尝试加载过多高分辨率图像至内存中而未做适当优化处理。这使得虚拟机无法分配足够的连续空间来满足请求从而触发了 OOM 错误。 针对上述情况采取如下措施: - **减少单次加载量**:限制每次仅读取一定数量的小尺寸缩略图而非原始大小; - **启用缓存机制**:对于已加载过的图片实施 LRU 缓存策略以便重复访问时不需重新获取; - **异步操作**:采用后台线程完成耗时较长的任务如网络请求或磁盘IO动作防止阻塞主线程造成响应延迟甚至卡死状况的发生。 经过以上改进之后有效地缓解了因图片加载不当所引发的一系列性能瓶颈问题显著降低了 OOM 发生概率提升了整体稳定性表现。 #### 工具辅助诊断流程 面对较大规模的应用程序,手动排查可能存在效率低下且难以全面覆盖所有潜在风险点的问题。此时可以借助专业的调试工具来进行更深入细致地剖析工作。例如 JVisualVM 是一款功能强大的 Java 应用性能监控平台能够帮助开发者快速定位到具体哪一部分代码消耗了大量的堆内存量进而指导后续修复方向的选择不过需要注意的是如果待检测的数据集非常庞大则建议预先调整好 JVM 的启动参数以确保有足够的可用 RAM 来支持整个分析过程顺利开展[^2]。 另外还可以考虑使用其他专门用于 heap dump 文件解析的专业软件比如 Eclipse MAT 或 Visual VM 自身集成的功能模块等它们各自具备独特的优势可以根据实际需求灵活选用最合适的选项。 #### 内存泄漏预防指南 为了避免未来再次遇到类似的挑战可以从以下几个方面着手加强防护力度: - 定期审查现有架构设计是否存在不必要的对象持有关系特别是静态成员变量以及监听器注册注销逻辑是否严谨无遗漏之处; - 对第三方库保持警惕谨慎引入未经充分测试验证的新依赖项以免埋下隐患; - 培养良好的编程习惯遵循最佳实践编写易于维护扩展性强的高质量源码。 ```java // 示例代码展示如何安全释放Bitmap资源 public void recycleBitmap(Bitmap bitmap){ if(bitmap != null && !bitmap.isRecycled()){ bitmap.recycle(); System.gc(); // 提示垃圾回收器尽快清理不再使用的对象 } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小冷coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值