PDFBox循环生成PDF:如何仅加载一次字体并重复使用?
问题解答
1. 调用PDType0Font.load(doc, ...)的行为
每次调用该方法时,PDFBox会执行以下操作:
- 读取传入的字体输入流,完整解析10M大小的TTF文件内容
- 创建对应的
PDType0Font实例,包含字体的字形、编码映射等所有解析后的数据 - 将该字体实例与当前的
PDDocument绑定,作为文档的资源对象存在
2. 是否会创建10000个大字体对象?
是的。循环执行10000次意味着会重复10000次字体解析流程,每次都会生成一个独立的PDType0Font实例,每个实例仅原始字体数据就占用10M内存,加上解析后的额外元数据,内存占用会更高。短时间内大量创建这类大对象会快速耗尽堆内存,频繁触发Full GC,这就是你遇到问题的核心原因。
3. 如何实现字体仅加载一次、循环复用?
可以通过预加载字体到临时文档,再在循环中克隆字体到目标文档的方式实现复用,彻底避免重复解析TTF文件,优化后的代码示例如下:
// 预加载字体数据到字节数组,避免重复从类加载器读取流 byte[] fontBytes; try (InputStream fontStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("font/simsun.ttf")) { // 自行实现流转字节数组,无需第三方依赖 ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = fontStream.read(buffer)) != -1) { baos.write(buffer, 0, len); } fontBytes = baos.toByteArray(); } // 创建临时文档预加载字体,仅解析一次TTF PDDocument tempDoc = new PDDocument(); PDType0Font baseFont = PDType0Font.load(tempDoc, new ByteArrayInputStream(fontBytes)); try { for(int i=0; i<10000; ++i){ PDDocument doc = PDDocument.load(inputstream); PDPage page = doc.getPage(0); // 将预加载的字体克隆到当前文档,复用已解析的字体核心数据 PDType0Font reusedFont = (PDType0Font) baseFont.cloneForDocument(doc); PDPageContentStream content = new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.APPEND, true, true); content.beginText(); content.setFont(reusedFont, 10); // ... 执行你的文本绘制逻辑 content.endText(); content.close(); doc.save(outstream); doc.close(); } } finally { // 关闭临时文档,释放资源 tempDoc.close(); }
核心优化点:
- 提前将TTF文件读入字节数组,避免循环中重复执行IO操作,提升效率
- 仅在临时文档中解析一次字体,后续循环通过
cloneForDocument方法复用已解析的字体核心数据,仅创建当前文档所需的绑定对象,内存占用大幅降低 - 严格管理文档资源,确保所有
PDDocument都被正确关闭,避免内存泄漏
这样调整后,堆内存中不会再出现大量重复的大字体对象,Full GC频率会显著降低。
内容的提问来源于stack exchange,提问作者sincosmos
相关产品推荐
相关产品推荐

