DocumentBuilder.parse() 易触发 OOM,因其将整个XML加载为DOM树,节点对象开销大、无法流式释放、存在XXE和字符串重复等问题;应改用SAX或StAX流式解析,并禁用DTD、复用对象、合理分块处理。
DocumentBuilder.parse() 容易触发 OOMJava 中用 DocumentBuilder.parse(InputStream) 解析大 XML 文件时,DOM 会把整个文档树加载进内存。哪怕只有 100MB 的 XML,实际堆内存占用可能翻倍——因为每个 Element、Text、命名空间节点都带对象头、引用、字符数组副本。JVM 堆设得再大,也只是拖延 OOM 时间,不是解法。
DocumentBuilderFactory 未关闭外部实体(XXE),某些 DTD 加载行为会额外拉取远程资源并缓存String 实例,加剧堆压力SAXParser 或 XMLStreamReader 的关键动作
二者都是事件驱动、只保留当前上下文,内存占用稳定在 KB 级别。选型看需求:SAX 更轻量但不可回溯;StAX(XMLStreamReader)支持部分前向移动,调试友好。
System.setProperty("javax.xml.parsers.SAXParserFactory", "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);startElement() 里缓存全部文本内容——用 StringBuilder 拼接 characters() 回调,且每次处理完立即清空或复用Attributes 参数,提取需要的属性值后立刻转成基本类型或字符串 internXmlMapper(Jackson Dataformat XML)解析大文件的陷阱很多人以为 Jackson XML 是“流式”,其实 XmlMapper.readTree() 仍是 DOM 风格全量构建树,和原生 DOM 一样吃内存。真正安全的是 JsonParser + 手动遍历 token 流。
XmlFactory.createParser(InputStream) 获取 JsonParser,而非 readValue()
parser.nextToken() + parser.getCurrentName() 判断,不要调用 parser.readValueAsTree()
parser.getIntValue()、parser.getDecimalValue(),避免构造临时字符串XmlFactory 允许 DTD:需显式设置 factory.configure(FromXmlParser.Feature.USE_DTD, false)
即使用了流式解析,如果业务逻辑本身要聚合数据(比如按 分组统计),仍可能因缓存太多中间结果而 OOM。这时不能只依赖解析器,得配合业务切分。
CountingInputStream(Apache Commons IO)监控已读字节数,每 50MB 主动触发一次 flush + checkpoint 开始就 new 对象,结束就入库/发消息,不存 ListWeakHashMap 或软引用缓存计算结果,避免强引用锁死内存-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heap.hprof,OOM 后直接看哪个类实例最多最常被忽略的一点:XML 命名空间声明(xmlns)虽不占数据体积,但每个带 namespace 的元素都会触发 NamespaceContext 初始化和缓存——在百万级节点场景下,这部分对象能占到堆的 15% 以上。解析前先确认是否真需要 namespace 支持,不需要就关掉 factory.setNamespaceAware(false)。