这是典型的“拒绝服务型内存耗尽攻击”,源于外部不可信数据未经校验直接参与内存分配,需从输入限制、反序列化防护、集合缓存限流及jvm兜底四层面综合防御。

这种情况属于典型的“拒绝服务型内存耗尽攻击”,不是单纯配置或GC问题,而是代码未对输入做边界防护所致。核心在于:外部不可信数据未经校验就直接参与内存分配,攻击者可构造超长字符串、超大JSON、海量嵌套对象等,触发 OutOfMemoryError: Java heap space。
输入长度与结构必须硬性限制
任何接收外部数据的入口(HTTP Body、MQ消息、RPC参数、文件上传)都应设置明确上限:
- HTTP接口用
@Size(max = 10240)或 Servlet Filter 拦截超长请求体,直接返回 413 Payload Too Large - JSON 解析前检查原始字节数:
if (bytes.length > 2 * 1024 * 1024) throw new IllegalArgumentException("Payload too large") - XML/Protobuf 等格式启用解析器的深度和大小限制(如 Jackson 的
JsonFactory.Feature.USE_BIG_DECIMAL_FOR_FLOATS不相关,但DeserializationFeature.FAIL_ON_TRAILING_TOKENS+ 自定义LimitingInputStream才关键)
避免反射式无约束反序列化
使用 ObjectMapper.readValue() 或 XStream 等工具时,默认允许任意类加载,极易被诱导构造恶意链并占用堆内存:
- 禁用默认类型处理:
mapper.disable(DeserializationFeature.USE_DEFAULT_TYPE) - 显式白名单反序列化类:
SimpleModule module = new SimpleModule(); module.addDeserializer(VictimClass.class, new SafeDeserializer()); - 绝对不用
XStream解析不可信 XML;改用 StAX 或 DOM + 元素深度/节点数计数器
集合类与缓存需主动限流
攻击者常利用泛型容器自动扩容机制(如 ArrayList 扩容为 1.5 倍)制造内存雪崩:
- 解析 JSON 数组时,先读取顶层 size 字段,若 > 1000 则拒绝;不直接
readValueAsTree()后遍历 - 缓存层强制设置最大条目数和单条大小上限,例如 Caffeine:
Caffeine.newBuilder().maximumSize(1000).weigher((k,v) -> ((String)v).length()).build() - 数据库查询结果不调用
listAll(),改用分页 + 流式处理(Stream.iterate或 MyBatis 的Cursor)
运行时防御:JVM 层面兜底
即使代码有疏漏,也应通过 JVM 参数降低攻击成功率:
- 启用 GC 日志并监控 Eden 区回收频率,突增即告警
- 设置
-XX:+ExitOnOutOfMemoryError防止 OOM 后进程僵死 - 对关键服务加
-XX:MaxRAMPercentage=75.0(容器环境),避免吃光宿主机内存 - 元空间和直接内存也设限:
-XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=128m
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











