Java中PriorityQueue扩容时int溢出会导致OutOfMemoryError,实为非法负容量请求而非真实内存不足;其1.5倍扩容在容量达约14.3亿后溢出,JDK21+已增加溢出检查。

Java 中 PriorityQueue 扩容时若发生 int 溢出,会直接导致容量计算错误,进而触发 OutOfMemoryError: Java heap space,但**这不是真正的内存不足,而是整数溢出引发的非法容量请求**——JVM 尝试分配一个负数或极小正数大小的数组(如 new Object[-1]),最终抛出异常。
扩容逻辑与 int 溢出点
PriorityQueue(JDK 8+)默认初始容量为 11,扩容公式为:newCapacity = oldCapacity + (oldCapacity >> 1)(即 1.5 倍增长)。
当 oldCapacity 达到约 Integer.MAX_VALUE / 1.5 ≈ 1,431,655,765 时,下一次扩容计算会溢出为负数。例如:
- 当前容量 =
1,431,655,765 - 扩容后 =
1,431,655,765 + (1,431,655,765 >> 1) = 2,147,483,647(刚好是Integer.MAX_VALUE) - 再扩容一次 →
2,147,483,647 + (2,147,483,647 >> 1) = -1,073,741,824(溢出为负)
此时 Arrays.copyOf 内部调用 new Object[n],n 为负数,JVM 直接抛出 OutOfMemoryError(规范要求:数组长度必须为非负 int)。
如何识别这是 int 溢出而非真实内存不足
关键看堆栈和错误信息特征:
- 异常堆栈中包含
PriorityQueue.grow、Arrays.copyOf、Object[]::new - 错误消息是
java.lang.OutOfMemoryError: Java heap space,但堆 dump 显示实际使用内存远低于-Xmx - 没有大量对象堆积,GC 日志显示回收正常,但某次
offer()突然失败
规避方案
根本思路是**不让队列增长到接近 Integer.MAX_VALUE**:
立即学习“Java免费学习笔记(深入)”;
- 明确设置合理初始容量:
new PriorityQueue(initialCapacity),避免频繁扩容 - 业务层控制入队总量,例如对流式数据加计数限流:
if (queue.size() > 1_000_000) throw new IllegalStateException("Too many items"); - 改用支持 long 容量的替代结构(如自定义无界队列 + 分段存储),但需权衡复杂度
- 升级 JDK:JDK 21+ 的
PriorityQueue在grow()中增加了溢出检查,会提前抛出IllegalStateException而非 OOM
临时应急处理
如果已在线上出现,且无法立即改代码:
- 增大堆内存(仅缓解,不治本):
-Xmx8g等,延缓溢出时机 - 启用堆转储:
-XX:+HeapDumpOnOutOfMemoryError,确认是否真有巨型对象堆积 - 监控
PriorityQueue.size(),在达到1_500_000_000前主动告警并降级


















