应使用 getCharacterStream() 或 getAsciiStream() 流式读取 CLOB,避免 getClob().getSubString() 或 toString() 全量加载;配合 try-with-resources 和合理缓冲区(如 8192),并在 ResultSet 当前行内完成读取,防止流失效。
Oracle CLOB太大导致Java内存溢出怎么办
直接用 getclob().getsubstring() 或 tostring() 读取大 clob(比如几十mb以上)时,jdbc 驱动会把整个内容加载进堆内存,极易触发 java.lang.outofmemoryerror: java heap space。这不是业务逻辑问题,是读取方式错了——必须绕过全量加载,走流式通道。
用 getAsciiStream() / getCharacterStream() 替代 getString()
Oracle JDBC 驱动对 CLOB 支持原生流接口,关键不是“能不能”,而是“选哪个流”:
-
getClob().getAsciiStream():返回InputStream,适合纯 ASCII 或已知单字节编码(如 ISO-8859-1),但对 UTF-8 中文可能乱码 -
getClob().getCharacterStream():返回Reader,底层按字符解码,推荐用于含中文、需正确处理 Unicode 的场景 - 别调
getClob().getSubString(1, (int) clob.length())—— 这一步就已把全部内容转成String,OOM 风险照旧
流式读取的典型安全写法(带 try-with-resources)
必须确保流关闭,且避免一次性读入大缓冲区。下面这段代码能稳定处理 GB 级 CLOB:
try (Reader reader = rs.getClob("content").getCharacterStream();
BufferedReader br = new BufferedReader(reader, 8192)) {
String line;
while ((line = br.readLine()) != null) {
// 每行处理,不累积全文
processLine(line);
}
}
注意点:
- 缓冲区大小设为
8192(8KB)足够,再大不提升性能,反而占堆 - 别用
br.lines().forEach(...)——lines()会尝试预加载全部行,破坏流式语义 - 如果要写入文件或转发 HTTP 响应,直接用
IOUtils.copy(reader, outputStream)(Apache Commons IO),它内部就是分块读写的
Connection 和 ResultSet 的 holdability 影响流有效性
Oracle CLOB 流依赖底层数据库连接和结果集保持打开状态。常见坑:
- 使用
ResultSet.TYPE_FORWARD_ONLY(默认)时,一旦调用rs.next()移动到下一行,前一行的 CLOB 流立即失效,读取会抛SQLException: Stream has already been closed - 解决办法:要么在当前行内完成全部流读取;要么创建
ResultSet时显式指定ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT(注意 Oracle 驱动版本需 ≥ 12.1) - Spring JDBC 用户注意:
JdbcTemplate.query()默认关闭ResultSet后才执行回调,此时 CLOB 流已不可用 —— 必须改用queryForRowSet()或手写ConnectionCallback
流式读取本身不难,难的是记住 CLOB 不是普通字符串,它的生命周期和数据库游标绑得太紧。漏掉任一 close 或提前移动 cursor,都会让流静默失效。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










