Tomcat因iText7.1.8未释放.pfm文件崩溃,求解决方案
解决iText7.1.8生成PDF导致Tomcat文件句柄泄漏问题
先直接给结论:是的,使用单例模式复用DefaultFontProvider是解决这个文件句柄泄漏问题的核心方案,下面详细拆解原因和修改方法:
问题根源分析
你的场景里,Tomcat崩溃的直接原因是打开文件句柄数达到系统上限,而这些未释放的文件都是.pfm字体文件——这是因为每次生成PDF时,你都新建了一个DefaultFontProvider(true, true, true)实例。
这个类在初始化时会扫描并加载系统中的字体资源,而iText 7.1.8版本中,频繁创建该实例会导致旧实例持有的字体文件句柄无法被及时回收(JVM垃圾回收不会立刻清理这类底层资源,高并发场景下会快速累积),最终触发Too many open files错误拖垮Tomcat。
为什么单例模式有效
DefaultFontProvider是iText官方设计为线程安全的组件,它的核心职责就是缓存字体资源、避免重复加载。将它做成单例后,整个应用生命周期内只会加载一次字体文件,所有PDF生成请求复用同一个字体提供者,自然不会持续产生新的未释放文件句柄。
修改后的代码示例
你可以把DefaultFontProvider改成静态单例,调整后的代码如下:
public class PdfGenerator { // 饿汉式单例:应用启动时初始化一次,全局复用 private static final DefaultFontProvider GLOBAL_FONT_PROVIDER = new DefaultFontProvider(true, true, true); public static File convertToPDF(File pdfFile, URL webURL) { InputStream htmlStream = null; FileOutputStream pdfStream = null; try { htmlStream = webURL.openStream(); pdfStream = new FileOutputStream(pdfFile); ConverterProperties properties = new ConverterProperties(); // 复用全局单例的字体提供者,而非每次新建 properties.setFontProvider(GLOBAL_FONT_PROVIDER); HtmlConverter.convertToPdf(htmlStream, pdfStream, properties); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (htmlStream != null) { htmlStream.close(); } if (pdfStream != null) { pdfStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return pdfFile; } }
额外优化建议
- 如果担心应用启动时初始化字体影响启动速度,可以改成懒加载单例(比如双重检查锁定方式),不过大部分场景下饿汉式单例的性能表现已经足够。
- 可以临时调整Ubuntu的文件句柄上限(修改
/etc/security/limits.conf)缓解紧急问题,但这只是治标,复用字体提供者才是彻底解决问题的办法。 - 可以检查iText 7.1.x后续小版本是否修复了字体资源泄漏的已知bug,升级版本也是一个长期优化方向。
内容的提问来源于stack exchange,提问作者Billel Mehdi Bendjaballah
相关产品推荐
相关产品推荐

