创建JSON对象列表调用toJSONString()时Java堆内存溢出求助
list.toJSONString()触发Java堆内存溢出的解决方法
问题代码
String query1 = "select * from booking where id='"+ids+"'"; rs = stmt.executeQuery(query1); JSONObject obj = new JSONObject(); while(rs.next()) { String passengerName = rs.getString("passengerName"); String passengerPhone = rs.getString("passengerPhone"); String emergencyPhone = rs.getString("emergencyPhone"); String trip = rs.getString("trip"); String destination = rs.getString("destination"); String seatNumber = rs.getString("seatNumber"); String cad = rs.getString("idNumber"); obj.put("passengerName", passengerName); obj.put("passengerPhone", passengerPhone); obj.put("emergencyPhone", emergencyPhone); obj.put("trip", trip); obj.put("destination", destination); obj.put("seatNumber", seatNumber); obj.put("cad", cad); list.add(obj); } out.print(list.toJSONString()); out.flush();
报错信息
2022年7月23日 11:44:00.326 严重 [http-nio-81-exec-10] org.apache.catalina.core.StandardWrapperValve.invoke 上下文路径为[/ctracker]的Servlet[jsp]的service()方法抛出异常[处理[calData.jsp]时在第282行发生异常]
第282行代码为:out.print(list.toJSONString());根因:java.lang.OutOfMemoryError: Java堆内存空间不足
问题分析与修复方案
1. 代码逻辑错误:重复复用同一个JSONObject对象
原代码将JSONObject obj = new JSONObject();放在循环外部,每次循环仅修改该对象的属性值再添加到列表中,导致列表中所有元素均指向同一个对象引用——这不仅会导致最终列表中所有元素都是最后一条记录的值,还会在序列化时产生额外内存消耗。
修复:将JSONObject实例化代码移至循环内部,每次迭代创建新对象:
while(rs.next()) { JSONObject obj = new JSONObject(); // 移至循环内,每次创建新对象 String passengerName = rs.getString("passengerName"); String passengerPhone = rs.getString("passengerPhone"); String emergencyPhone = rs.getString("emergencyPhone"); String trip = rs.getString("trip"); String destination = rs.getString("destination"); String seatNumber = rs.getString("seatNumber"); String cad = rs.getString("idNumber"); obj.put("passengerName", passengerName); obj.put("passengerPhone", passengerPhone); obj.put("emergencyPhone", emergencyPhone); obj.put("trip", trip); obj.put("destination", destination); obj.put("seatNumber", seatNumber); obj.put("cad", cad); list.add(obj); }
2. 大数据量一次性加载导致堆内存耗尽
若查询返回的记录数过多,将所有数据加载到列表后再一次性序列化,会占用大量堆内存触发溢出。
解决方法:
- 分页查询:修改SQL添加分页条件(不同数据库语法不同,如MySQL用
LIMIT offset, size),避免一次性加载全量数据。 - 流式序列化输出:不将所有数据存入列表,边读取ResultSet边输出JSON,减少内存占用:
out.print("["); boolean isFirst = true; while(rs.next()) { if(!isFirst){ out.print(","); } isFirst = false; JSONObject obj = new JSONObject(); // 赋值属性 obj.put("passengerName", rs.getString("passengerName")); obj.put("passengerPhone", rs.getString("passengerPhone")); obj.put("emergencyPhone", rs.getString("emergencyPhone")); obj.put("trip", rs.getString("trip")); obj.put("destination", rs.getString("destination")); obj.put("seatNumber", rs.getString("seatNumber")); obj.put("cad", rs.getString("idNumber")); out.print(obj.toJSONString()); out.flush(); // 实时输出,释放内存 } out.print("]"); out.flush(); - 调整JVM堆内存:临时缓解方案,修改Tomcat启动参数增大堆内存,例如在
catalina.sh(Linux)或catalina.bat(Windows)中添加:
其中-Xms512m -Xmx1024m-Xms为初始堆内存,-Xmx为最大堆内存,可根据服务器配置调整。
3. 修复SQL注入漏洞
原代码直接拼接字符串生成SQL,存在严重SQL注入风险,且若参数异常可能返回大量数据加重内存负担。
修复:使用PreparedStatement预编译SQL:
String query1 = "select * from booking where id=?"; PreparedStatement pstmt = conn.prepareStatement(query1); pstmt.setString(1, ids); rs = pstmt.executeQuery();
内容的提问来源于stack exchange,提问作者maxwell Owusu
相关产品推荐
相关产品推荐

