PDFBox转高画质JPG时Java堆内存溢出问题求助
解决PDFBox高分辨率转JPG时的内存溢出问题
结合你的代码、错误日志和使用场景,咱来拆解下问题根源——主要是未及时释放图像资源+PDFBox旧版本的Shading内存泄漏+内存配置不够精细这几个点,给你几个可落地的优化方案:
一、立即释放循环内的资源,杜绝内存泄漏
你的代码里,每次循环生成的BufferedImage(image和thumbnail)、ImageWriter用完后都没主动释放,这些对象会一直占着堆内存直到GC被动回收,循环次数多了就容易爆内存。
修改循环内的代码,在写完图片后立刻清理:
// 写完图片后添加以下代码 writer.dispose(); // 释放ImageWriter资源 image.flush(); // 清空BufferedImage的内存缓冲区 thumbnail.flush(); image = null; // 解除引用,帮助GC识别可回收对象 thumbnail = null; System.gc(); // 主动提示JVM进行垃圾回收(不强制但能加速)
二、优化PDFBox的内存使用策略
你已经用了setupTempFileOnly,但可以更精细地控制内存阈值,让PDFBox在内存占用到一定程度时优先用磁盘临时文件存储,而不是一直堆在内存里:
// 调整内存设置:允许100MB内存,超出部分用临时文件 MemoryUsageSetting memorySetting = MemoryUsageSetting.setupMixed(100 * 1024 * 1024); PDDocument document = PDDocument.load(file, memorySetting);
三、升级PDFBox版本(关键!)
你用的2.0.20版本存在已知的Shading渐变填充内存泄漏问题——错误日志里的TriangleBasedShadingContext.calcPixelTable就是核心证据。PDFBox在2.0.21及之后的版本修复了大量Shading相关的内存bug,建议直接升级到最新的2.0.x稳定版(比如2.0.32),这大概率能解决第三次循环崩溃的问题。
四、临时应急:调整JVM堆内存参数
如果暂时没法升级依赖,可以在启动Java程序时手动分配更大的堆内存,比如:
java -Xmx2G PdfToImage
(-Xmx2G表示给JVM分配最大2GB堆内存,根据你的空闲内存可以调整到1.5G甚至3G)
修改后的完整代码示例
把上面的优化点整合到你的代码里:
import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.plugins.jpeg.JPEGImageWriteParam; import javax.imageio.stream.FileImageOutputStream; import org.apache.pdfbox.io.MemoryUsageSetting; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.PDFRenderer; import net.coobird.thumbnailator.Thumbnails; public class PdfToImage { public static void main(String args[]) throws IOException { File file = new File("C:/myPath/printfile.pdf"); // 优化内存设置 MemoryUsageSetting memorySetting = MemoryUsageSetting.setupMixed(100 * 1024 * 1024); PDDocument document = PDDocument.load(file, memorySetting); PDFRenderer renderer = new PDFRenderer(document); try { int pageNumber = 0; for (PDPage page : document.getPages()) { float trimboxWidth = page.getTrimBox().getWidth(); float trimboxHeight = page.getTrimBox().getHeight(); float mediaboxWidth = page.getMediaBox().getWidth(); float mediaboxHeight = page.getMediaBox().getHeight(); float getY = (mediaboxHeight - trimboxHeight) / 2; float getX = (mediaboxWidth - trimboxWidth) / 2; PDRectangle rectangle = new PDRectangle(getX, getY, trimboxWidth, trimboxHeight); page.setCropBox(rectangle); // 渲染页面 BufferedImage image = renderer.renderImageWithDPI(pageNumber, 96, ImageType.RGB); // 调整尺寸 BufferedImage thumbnail = Thumbnails.of(image) .height(700) .asBufferedImage(); // 写入JPG JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null); jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); jpegParams.setCompressionQuality(1f); final ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next(); writer.setOutput(new FileImageOutputStream( new File("C:/myPath/myimage" + pageNumber + ".jpg"))); writer.write(null, new IIOImage(thumbnail, null, null), jpegParams); // 关键:释放当前循环的资源 writer.dispose(); image.flush(); thumbnail.flush(); image = null; thumbnail = null; System.gc(); System.out.println("Used Memory: " + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())); pageNumber++; } } finally { if (document != null) { document.close(); } } } }
总结
优先做升级PDFBox版本+循环内资源释放这两个操作,这是解决问题的核心;再配合内存设置优化和JVM参数调整,应该就能在保持96DPI高画质的前提下顺利完成4页PDF的转换。
内容的提问来源于stack exchange,提问作者Xtreme
相关产品推荐
相关产品推荐

