java读取xml时inputstream未关闭会导致文件句柄耗尽和oom;必须用try-with-resources确保流在所有路径下自动关闭,documentbuilder.parse()、saxparser.parse()和xpath.evaluate()均不自动关闭流。

Java读取XML时InputStream没关,程序跑着跑着就OOM了
不是XML解析出错,是文件句柄或内存泄漏——InputStream没关,底层字节缓冲区和系统文件描述符一直占着。尤其在循环读多个XML、或Web服务高频调用时,java.io.IOException: Too many open files 几乎必现。
核心原则:流的生命周期必须和使用它的XML解析器对齐,不能靠GC“等它自己关”。DocumentBuilder.parse(InputStream) 不会帮你关流,它只读到EOF或异常就停,但流对象本身还活着。
- 别在
parse()外层用try包InputStream再单独close()——容易漏掉异常分支 - 别用
InputStream.close()放在finally里手动关——写法冗长且易错(比如close()自己抛异常) - 优先用
try-with-resources,确保哪怕解析中途抛SAXException或IOException,流也必然关闭
DocumentBuilder.parse()配合try-with-resources的正确写法
很多人以为只要把InputStream放进try()括号里就行,但要注意:DocumentBuilder.parse()内部可能多次读取流,如果流提前被其他逻辑消费(比如先用available()判断长度),会导致解析失败或空文档。
最稳妥的做法是让InputStream从创建到解析全程由try-with-resources托管,中间不穿插任何额外操作。
立即学习“Java免费学习笔记(深入)”;
File xmlFile = new File("config.xml");
try (InputStream is = new FileInputStream(xmlFile)) {
Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder()
.parse(is); // parse()内部读完即止,流由JVM保证关闭
// 后续处理doc...
} catch (Exception e) {
throw new RuntimeException("XML解析失败", e);
}
-
is必须是final或有效final变量,否则编译不通过 - 不要在
try块里重复调用is.read()或is.mark()——会干扰parse()的内部读取逻辑 - 如果XML来自网络(如
HttpURLConnection.getInputStream()),同样适用;但注意HTTP连接的InputStream关闭后,连接可能被复用或断开,需结合Connection: keep-alive策略看
用SAXParser或XPath时InputStream怎么关
SAXParser.parse(InputStream, DefaultHandler) 和 XPath.evaluate(String, InputSource) 都不自动关流。区别在于:SAXParser是边读边回调,XPath需要你先构造InputSource,而InputSource的setByteStream(InputStream)只是个引用,完全不接管生命周期。
错误示范:new InputSource(new FileInputStream(...)) → 流在InputSource里“裸奔”,没人关。
-
SAXParser.parse()必须用try-with-resources包住原始InputStream,和DocumentBuilder一样 -
XPath.evaluate()前,用InputSource包装流时,确保该流已被try-with-resources管理,而不是在evaluate()调用时才创建 - 如果XML内容已加载进
byte[]或String,直接用new InputSource(new ByteArrayInputStream(...)),这种内存流不用关,避免无谓的try
Spring Resource + SAXParser容易忽略的流陷阱
用ResourceLoader.getResource("classpath:xxx.xml")拿到Resource,再调resource.getInputStream()——这个InputStream是否需要关?答案是:**取决于Resource实现类**。
ClassPathResource返回的流,底层是ClassLoader.getResourceAsStream(),JDK规范不保证可重复读或可关闭;强行close()可能在某些容器(如OSGi)中引发问题。而FileSystemResource返回的FileInputStream必须关。
- 统一做法:一律用
try-with-resources包裹resource.getInputStream(),JDK 7+ 对ClassLoader流的close()是空操作,安全 - 别信“classpath资源不用关”的经验——不同Spring版本、不同打包方式(jar vs exploded)、不同类加载器行为不一致
- 如果用
@Value("classpath:xxx.xml") Resource res注入,记得在业务方法里立刻打开并关闭流,别把Resource存为字段长期持有
Document却忘了把InputStream关在方法内部;或者用Apache Commons IO的IOUtils.toString(is, "UTF-8")转成字符串再解析,结果is早被读空了,parse()拿不到数据。流关没关,得看close()调用点是否在所有执行路径上,而不是看代码里有没有这个词。










