Tomcat 8发送POST请求时触发Java堆内存溢出崩溃,求根因分析
让我们一步步拆解你的问题,从核心崩溃原因到具体疑问逐一解答:
核心崩溃原因:堆内存耗尽(Java Heap Space)
日志里的java.lang.OutOfMemoryError: Java Heap Space说明JVM的堆内存已经被完全占满,无法分配新的内存空间,最终导致Tomcat进程崩溃。结合你的场景和代码,大概率是资源泄漏+内存渐进式消耗共同作用的结果,而非单次请求直接撑爆内存。
针对你的疑问逐一解答
1. 多数资源如outputstream和inputstream未关闭?
你的代码里看似手动关闭了流,但存在一个致命漏洞:如果代码执行过程中抛出异常,后续的close()调用可能不会被执行到。比如:
- 调用
wr.writeBytes(urlParameters)时抛出IO异常,wr.flush()和wr.close()就会跳过 - 调用
con.getResponseCode()返回非200状态码时,getInputStream()会抛出异常,后续的in.close()也不会执行
未关闭的流会关联着缓冲区、连接资源等堆内存对象,这些对象无法被GC回收,久而久之就会造成内存泄漏。
2. HttpsURLConnection对象con未关闭/断开连接?
HttpsURLConnection本身没有close()方法,它的资源释放依赖于关联流的关闭,以及底层连接池的管理。但如果流没有正确关闭:
- 连接可能会被滞留在连接池中,占用内存和TCP资源
- 未读取完的响应体也会导致连接无法被复用,资源无法释放
这也是内存泄漏的潜在来源之一。
3. 为何并非每次都会发生?
内存泄漏是渐进式积累的过程:每次异常场景下泄漏少量内存,随着请求次数增加,堆内存被慢慢蚕食,直到某次请求触发内存分配时,才会触发OOM。两个多月才出现一次,说明泄漏速率较慢,且只有特定请求场景(比如触发异常的请求)才会导致泄漏,不是每次请求都有问题。
4. OME由ByteArray相关组件抛出?
这完全符合内存泄漏的特征:未被回收的流缓冲区、连接池缓存数据等都是字节数组对象,当堆内存被这些对象占满时,JVM尝试分配新的字节数组(比如请求参数、响应体缓存)就会抛出这个异常。
5. 日志中伴随Exception in thread "AsyncFileHandlerWriter-#######"?
这是OOM导致的连锁反应,而非根源问题:堆内存耗尽后,Tomcat的异步日志写入线程无法分配内存来处理日志,因此抛出异常。它只是OOM的表现之一,不用单独针对这个线程排查。
解决建议
1. 用try-with-resources语法确保资源自动关闭
这是Java 7+推荐的资源管理方式,无论是否发生异常,JVM都会自动关闭流资源:
URL obj = new URL(url); // 用try-with-resources管理HttpsURLConnection(部分实现支持自动释放) try (HttpsURLConnection con = (HttpsURLConnection) obj.openConnection()) { con.setRequestMethod("POST"); con.setRequestProperty("User-Agent", USER_AGENT); con.setRequestProperty("Accept-Language", "en-US,en;q=0.5"); con.setDoOutput(true); // 自动管理DataOutputStream try (DataOutputStream wr = new DataOutputStream(con.getOutputStream())) { wr.writeBytes(urlParameters); wr.flush(); } int responseCode = con.getResponseCode(); // 处理非200响应的错误流,避免资源泄漏 InputStream inputStream = responseCode >= 200 && responseCode < 300 ? con.getInputStream() : con.getErrorStream(); // 自动管理BufferedReader和InputStream try (BufferedReader in = new BufferedReader(new InputStreamReader(inputStream))) { String inputLine; while ((inputLine = in.readLine()) != null) { response.append(inputLine); } } } catch (IOException e) { // 统一处理异常,避免吞掉错误信息 e.printStackTrace(); }
2. 启用内存诊断定位泄漏根源
添加JVM启动参数,下次OOM时自动生成堆转储文件:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/your/dump/path/heap_dump.hprof
用MAT(Memory Analyzer Tool)打开堆转储文件,就能直观看到哪些对象占用了大量内存,以及它们的引用链,精准定位泄漏点。
3. 优化连接池与内存配置
- 调整HttpsURLConnection的连接池大小,通过系统属性控制:
-Dhttp.maxConnections=200(根据你的并发量调整) - 检查Tomcat的JVM堆内存配置,确保初始堆
-Xms和最大堆-Xmx设置合理,避免堆内存过小
4. 检查response对象的生命周期
如果response是全局变量或者长时间存活的对象,不断append内容会导致内存持续占用。建议改为局部变量,每次请求后重置或销毁。
内容的提问来源于stack exchange,提问作者theimpatientcoder




