Saxon EE流式转换13GB大文件出现Java堆内存溢出问题求助
问题根因
- 你当前使用的
ByteArrayOutputStream会将所有转换输出结果全部缓冲在JVM堆内存中,13GB级别的转换结果远超出常规JVM堆内存上限,直接触发堆内存溢出错误,完全浪费了SAXON EE流式转换不需要全量加载文件的设计优势。 - 额外隐藏风险:你当前代码中对SAXON License校验逻辑存在问题,仅调用
config.isLicensedFeature但未判断返回值,若License未生效,SAXON会自动退化为非流式全量加载模式,也会触发OOM。 - 需确认你的
feed.xsl是否符合SAXON流式转换规范,XSLT中需显式声明`<xsl:mode streamable="yes"/>,且XPath语法符合可流式规则,否则也会触发全量加载。
解决方案
方案1:直接对接S3流式上传(最优)
直接使用AWS S3 SDK提供的可流式上传的OutputStream作为转换输出目标,转换过程中边生成结果边上传到S3,无需在本地内存缓存全量数据,完全规避堆内存溢出问题。
修改后代码示例:
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.saxonica.config.StreamingTransformerFactory; import net.sf.saxon.Configuration; import net.sf.saxon.TransformerFactoryImpl; import javax.xml.transform.*; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import java.io.File; import java.io.OutputStream; public class TransformWorker { public static void main(String args[]) throws Exception { // S3基础配置 String bucketName = "你的S3存储桶名称"; String targetS3Key = "转换后文件在S3的存储路径"; AmazonS3 s3Client = AmazonS3ClientBuilder.standard() .withCredentials(new DefaultAWSCredentialsProviderChain.getInstance()) .build(); ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentType("application/xml"); File sourceFile = new File("files/feed.xml"); Source streamSource = new StreamSource(sourceFile); TransformerFactory factory = new StreamingTransformerFactory(); Configuration config = ((TransformerFactoryImpl)factory).getConfiguration(); factory.setAttribute("http://saxon.sf.net/feature/licenseFileLocation","saxon-license.lic"); // 校验License是否生效 boolean isEELicensed = config.isLicensedFeature(Configuration.LicenseFeature.ENTERPRISE_XSLT); if (!isEELicensed) { throw new RuntimeException("SAXON EE License未生效,无法启用流式转换功能"); } File xslSheet = new File("files/feed.xsl"); Templates templates = factory.newTemplates(new StreamSource(xslSheet)); Transformer trans = templates.newTransformer(); trans.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); trans.setOutputProperty(OutputKeys.METHOD, "xml"); // 直接将S3上传输出流作为转换输出目标,边转换边上传 try (OutputStream s3OutputStream = s3Client.putObject(new PutObjectRequest(bucketName, targetS3Key, null, metadata).getOutputStream()) { trans.transform(streamSource, new StreamResult(s3OutputStream)); } } }
S3默认会将上传分片大小为128MB,整个转换过程仅会占用少量内存缓存分片数据,不会出现堆内存溢出。
方案2:本地临时文件中转
如果暂时不需要对接S3流式上传,可以用本地临时文件作为转换输出中转,磁盘空间足够的前提下也不会出现堆内存溢出:
// 替换原ByteArrayOutputStream相关代码段 File tempOutputFile = File.createTempFile("transformed_", ".xml"); try (FileOutputStream outputStream = new FileOutputStream(tempOutputFile)) { trans.transform(streamSource, new StreamResult(outputStream)); } // 后续将tempOutputFile上传到S3后删除本地临时文件即可
额外优化建议
- 确认XSLT满足可流式规则,避免使用需要全量上下文的XPath表达式,比如全局变量等会导致SAXON退化为非流式处理。
- 可适当调整JVM启动参数,添加
-Xmx4G为JVM分配4GB堆内存,预留足够的流式处理缓冲空间。
内容的提问来源于stack exchange,提问作者Gopinath
相关产品推荐
相关产品推荐

