大量输出文档,内容中有不同的SVG,SVG中有@font-face属性,内存一直升高直到溢出。
Aspose.Words 26.5版本依然存在WORDSJAVA-3167问题。
请尽快修复,已经严重影响用户正常使用!
html svg code.zip (36.1 KB)
请查看附件,html中带svg的内容,code是代码。像这种svg内容,大量写入文档时,或者循环多次,写入不同的文档时,内存溢出。测试输出1000个及以上文档时内存升高明显。
@dhzhao2016 我尝试在循环中生成相同的文档,但没有发现内存问题。您是否多次将相同的 HTML 代码插入到同一个文档中?如果是这样,文档大小预计会不断增加,因此文档 DOM 所需的内存也会相应增加。能否请您提供一些代码,以便我们能够重现内存不足的问题?
没有多次将相同的HTML代码插入到同一个文档。代码也没有什么特殊的操作,你就循环输出文档测试就能验证出来。
上面附件中的svg,都是各个试题的内容。我们的场景是学生考试后会将所有学生的试题输出到word或者pdf,比如有2000个学生,那么就是这么多的试题,分别输出到2000个文档中,在生产环境,大概输出600–800份时,内存就快达到10G了。
经AI分析,可能的原因有:
原因1:Aspose.Words 全局字体缓存(最可能,占80%以上)
现象 :带公式的答卷容易OOM,纯文本没问题 → 公式SVG中嵌入了字体数据!
原理 :
- 公式SVG中通常嵌入了 @font-face + base64编码的字体数据(stix、math1等20多种字体)
- 每次调用 builder.insertHtml(svg) 时,Aspose.Words会把这些字体加载到**全局静态字体缓存中
- Document.clearCaches() 只能清理Document级别的缓存,**清理不了全局静态的字体缓存
- 500份答卷 × 每份上百个公式 × 每个公式20多种字体 → 字体缓存无限增长
原因2:SVG转换后的Shape对象缓存未完全释放
- SVG被Aspose渲染为Shape对象后,内部可能有静态缓存
- Document.clearCaches() 可能不彻底
@dhzhao2016 感谢您提供的更多信息。我们在两台不同的机器上测试了该场景,仍然无法重现内存异常的输出。我们观察到,文档生成后内存已正确释放,内存使用情况稳定,没有增长。我们也检查了字体资源的处理方式,没有发现任何内存问题。
LeakRealityTest.zip (4.9 KB)
我没有办法提供你们生产环境的完整代码,只能尽可能的模拟内存溢出的情况,在本地环境也确实不好复现内存问题,请多试几次,或者多运行一段时间看看。
内存溢出的情况确实是存在的,请重视!特别是公式(SVG)内容较多的情况下比较明显。
还有一点,在生成学生答卷文件过程中,使用过文档的deepClone(),完整深拷贝复制所有内部结构,全局 FontStore ,包含主题字体,完整 SVG 渲染 ,含 @font-face 解析。
deepClone()的文档,最后也进行了cleanup、clearCaches清理,不知道对堆内存影响如何。
@dhzhao2016 感谢您提供的信息。
我们测试了多种场景。驻留堆内存在第 100 份文档时达到 84 MB,随后一直到第 600 份都精确保持在 84 MB,其余 500 份文档没有任何增长。我们还在完全去掉 cleanup() 和 clearCaches() 的情况下运行了测试,结果完全相同 —— 这说明克隆出的文档并没有驻留任何"cleanup 未能释放"的内容。
遗憾的是,我们无法在隔离的 Aspose 代码路径中复现这种增长,这表明内存驻留发生在流程的其他环节。为了定位它,从您的环境中获取一份堆转储(Heap Dump)是最直接的方式。
请在约 600 份文档、OOM 发生之前抓取:
jcmd <pid> GC.heap_dump /path/to/heap_600.hprof
您也可以选择使用 jmap -dump:live,format=b,file=/path/to/heap_600.hprof <pid>,它生成的文件更小 —— 因为会先执行一次 Full GC,仅转储可达对象,而这正是我们需要的内容。
请在同一时刻同时执行以下两条命令,并将输出以纯文本形式发给我们。它们仅有几 KB,且不包含任何文档内容或学生数据:
jcmd <pid> GC.heap_info # Java 堆的实际占用
jcmd <pid> VM.flags # 实际生效的 JVM 参数(-Xmx、垃圾回收器等)
如果是 Linux 环境,请补充该时刻的进程 RSS:
ps -o pid,rss,vsz -p <pid>
如果您更倾向于在发生 OOM 的瞬间自动抓取,可以添加以下参数,但需要重启 JVM:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumps/
我们理解该转储文件会比较大,并且其中确实以字符串形式包含文档文本和学生数据。因此如果无法共享,您可以在本地使用 Eclipse MAT 打开,然后将 dominator tree(支配树,按 Retained Heap 排序的前 20 行)以及对占用最大对象执行 Path to GC Roots → exclude weak/soft references 的结果发给我们。截图即可。
另外,能否提供你们并发生成文档的相关信息 —— 工作线程数量或线程池大小,以及文档是并行生成还是串行生成?
files.zip (399.0 KB)
根据上面的过程,保留了hprof文件和截图,hprof文件提示上传失败,没有超过200M。先将其他文件提供出来,供参考。因多次测试,pid可能不一样,运行结果都是同样的情况。Aspose.word 26.5版本。
相同的程序、相同的参数,20.2版本运行就没有内存溢出问题。期间也更新了几个版本,比如24.5、25.3、26.3,均有内存问题。目前生产环境一直是退回到20.2版本运行,但是20.2版本又有一些缺陷是无法解决的。
工作线程只有一个,文件一个一个生成,附件压缩包里有示例文件,看看是否可排查出什么问题?
@dhzhao2016 感谢您提供的堆转储文件。您的堆中包含 101,409 个 sun.font.TrueTypeFont 对象,但它们只指向 8 个不同的字体文件:
36,641 timesi.ttf 13,803 round_brackets1854.ttf
25,215 times.ttf 1,594 horizontal.ttf
22,031 math1.ttf 797 brack_sm.ttf
796 /tmp/simsun.ttcSimSun.tmp
532 timesbi.ttf
同样的几个字体文件被反复打开了数万次,且从未释放。每个对象都持有一块本地内存映射(DirectByteBuffer 共 101,416 个,与字体一一对应),这正是您的 RSS 达到 11.2 GB、而 Java 堆仅为 2 GB 的原因。内存消耗在堆外的本地内存中 —— 这也解释了为什么 cleanup()、clearCaches() 和 deepClone() 都没有效果,以及为什么我们之前基于堆内存的测试什么都没有发现。
这些是 JDK 的字体类(sun.font.*),并非 Aspose.Words 的文档对象。我们使用您提供的示例试卷,搭建了 Linux + Java 8 的容器环境:
| JVM | 结果 |
|---|---|
| OpenJDK 8u181 | 每份文档增加 4.9 个字体对象,线性增长,从不释放 |
| Temurin 8u492 | 稳定在 44,保持不变 |
相同的 Aspose.Words 版本、相同的文档、相同的代码 —— 唯一的差异是 JVM。这强烈表明根本原因是 JDK 8 字体管理器的缺陷,而非 Aspose.Words。在我们的测试中,Aspose.Words 20.1 同样出现了泄漏(每份文档 +3.9)—— 与 26.5 相差无几。因此,我们目前还无法解释为什么 20.2 在您那里是正常的。
在我们测试的所有版本中,保存为 PDF 从未加载过任何一个 JDK 字体对象。而您的转储中却大量存在这些对象,这说明您的流程中一定还有渲染图片的环节,而不仅仅是生成 PDF。
在更换 Aspose.Words 版本之前,请先尝试将 JDK 升级到最新的 Java 8 版本(或 Java 11/17),同时保持当前的 Aspose.Words 版本不变。在我们的测试中,仅此一项就消除了增长。相比继续停留在 20.2,这个方案的影响要小得多。
补充问题:
- 您的确切 JVM 版本 ——
java -version的完整输出。另外,使用的是 Oracle JDK 还是 OpenJDK?您的转储中包含sun.font.T2KFontScaler,该类仅存在于 Oracle JDK 8 中。 - 当 20.2 正常运行时,JDK 是否也是同一个版本?如果升级过程中 JVM、基础镜像或操作系统也发生了变化,那么真正的变量可能并不是 Aspose.Words 的版本。
- 您的流程中是否有渲染图片的环节 —— 页面预览、缩略图、
save(PNG/JPEG),或者对已渲染的页面调用getPageCount()/updatePageLayout()?仅保存 PDF 不会产生这些字体对象,因此中间还缺少一个环节。 math1.ttf、round_brackets1854.ttf、brack_sm.ttf和horizontal.ttf来自哪里?它们看起来像是 WIRIS/MathType 的公式字体,并被安装到了系统级目录。是手动添加到/usr/share/fonts/的吗?- 能否在非生产环境中进行一次 JDK 升级测试,并反馈在约 600 份文档时 RSS 是否仍会达到 10 GB?
十分感谢您的回复,通过您提供的思路,我们经过大量测试,原有使用的是Oracle JDK 171(或221),均出现了泄漏问题,此问题是在JDK 301版本之后修复的,所以至少要更新到Oracle JDK 301 解决字体泄漏问题。
同时,我们经过评估,将生产环境升级至Java 17, Temurin jdk-17.0.20+8,全面解决字体泄漏问题,包括WIRIS/MathType 的公式字体,并同时更新Aspose.word版本至26.7最新版本。经大量测试,没有再出现内存泄漏问题。
再次感谢您的技术分析和指导!