如何降低限价订单簿内存占用?解决String.split引发的OOM
问题背景
开发完成的限价订单簿在测试环境(堆内存限制-Xmx128M)运行性能测试时触发OutOfMemoryError,经排查问题出在Main类的String.split操作,需要优化内存占用。当前订单簿基于TreeMap实现,支持更新、查询、市价单三类操作。
核心问题:
String.split的内存开销 String.split默认依赖正则表达式解析,会生成大量临时字符串数组和中间对象,在高频性能测试场景下,这些对象会快速填满堆内存,且如果没有及时被GC回收,就会触发OOM。另外,拆分后的字符串数组如果被长期持有,还可能导致内存泄漏。
优化方案
- 替换
String.split为轻量解析方式
用StringTokenizer代替split,它不依赖正则,直接按分隔符拆分字符串,生成的临时对象更少,内存开销更低。或者手动遍历字符串进行分割,进一步减少对象创建。 - 复用字符串与常量
对订单操作类型(如"update"、"query")这类重复出现的字符串,使用字符串常量池对象(直接用字面量,避免new String),或者用枚举类封装操作类型,减少重复创建相同内容的字符串实例。 - 精简TreeMap存储
清理订单簿中已完成(成交/取消)的订单,避免无效对象占用内存;用基本类型包装类的缓存(如Integer.valueOf替代new Integer),减少对象实例数量;去掉订单对象中不必要的冗余字段,压缩内存占用。 - 调整GC策略辅助内存回收
测试时可调整新生代内存比例(如-XX:NewRatio=1),让临时字符串对象在新生代更快被回收;用jmap、jconsole监控内存使用,确认临时对象的回收情况。
代码优化示例(针对Main类的解析逻辑)
原split实现:
String[] parts = line.split(" "); String operation = parts[0]; // 后续解析逻辑
优化为StringTokenizer实现:
public class Main { private static TreeMap<Long, Order> orderBook = new TreeMap<>(); public static void main(String[] args) { try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { String line; while ((line = reader.readLine()) != null) { StringTokenizer tokenizer = new StringTokenizer(line); if (!tokenizer.hasMoreTokens()) continue; String operation = tokenizer.nextToken(); switch (operation) { case "update": long orderId = Long.parseLong(tokenizer.nextToken()); double price = Double.parseDouble(tokenizer.nextToken()); int quantity = Integer.parseInt(tokenizer.nextToken()); orderBook.put(orderId, new Order(orderId, price, quantity)); break; case "query": long targetId = Long.parseLong(tokenizer.nextToken()); Order targetOrder = orderBook.get(targetId); // 处理查询输出 break; case "market_order": int dealAmount = Integer.parseInt(tokenizer.nextToken()); // 执行市价单成交逻辑 break; } } } catch (IOException e) { e.printStackTrace(); } } }
内容的提问来源于stack exchange,提问作者Данил Денк
相关产品推荐
相关产品推荐

