跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

如何排查基本类型 long 执行高频乘法未升级导致的静默溢出

long乘法溢出静默绕回致负数,常见于ID生成、计费等场景;线索包括数据库bigint突变负值、时间戳排序错乱、ArithmeticException告警骤降、指标跳变;高危写法如字面量缺L后缀。 long 类型乘法溢出不会报错,结果直接“绕回”,比如
long.max_value * 2
得到一个巨大负数。这种静默溢出在高频累加、id生成、时间差计算、计费统计等场景中极易埋雷,且上线后难以复现和定位。 看日志与监控中的异常信号 不依赖代码审查,先从运行态找线索: 数据库 bigint 字段写入值突变为负数(如订单金额、累计PV),尤其集中在某批任务或某类用户行为后 排序逻辑错乱:比如按时间戳升序查出的结果里,后面一条记录的时间戳反而比前面小几十亿 告警中出现
ArithmeticException
的频率骤降——反而是危险信号,说明本该触发的
Math.multiplyExact
被注释或绕过了 监控图表中某指标(如单日总耗时纳秒、分片步长)突然归零或跳变,但无服务重启记录 查代码中三类高危乘法模式 重点扫描以下写法,它们几乎都隐含 int→long 的中间截断或符号误判: 字面量未带 L 后缀 :如
1024 * 1024 * 1024 * 2
,全程按 int 算,溢出后再转 long;应写成
1024L * 1024 * 1024 * 2
或
1L
DTO/接口参数类型不一致 :RPC 接口定义为
long
,但消费者传的是
int
,序列化框架(如 Hessian、Kryo)配置不当,自动截断高位,日志里看不出异常 混合符号乘法未预检 :如
a * b
,其中
a = -1000000000000L
,
b = -1000000000000L
,结果正溢出,但
a * b > 0
判断失效,需用
Math.multiplyExact
或手动分情况检查 用工具链主动暴露问题 别等线上崩了再查,把检测左移到开发和测试阶段: 编译期:启用
-Xlint:all
,JDK 会警告类似
integer number too large
的字面量风险 单元测试:对所有乘法逻辑补边界用例,例如输入
Long.MAX_VALUE, 2, -1, 0, Long.MIN_VALUE
,验证是否抛出预期异常或返回合理降级值 静态扫描:用 SonarQube 或 Alibaba Java Coding Guidelines 插件,规则
java:S2184
(危险的整数运算)可识别未防护的
long * long
运行时防护:在核心路径(如计费引擎)统一封装
SafeLongMath.multiply(a, b)
,内部调用
Math.multiplyExact
,捕获
ArithmeticException
并打标 + 告警 + 切 BigInteger 降级 数据库与序列化层必须对齐精度 很多溢出其实在落地前就已发生,只是被掩盖: MyBatis 映射中,
→ 即使数据库是
BIGINT
,Java 端也早被截成
int
,后续任何乘法都基于错误值 JSON 序列化(如 Jackson)默认把 long 当数字处理,若前端 JS 用 Number 接收(最大安全整数
2^53-1
),超限后精度丢失,后端再乘一次就彻底失真 解决方案:所有涉及数值传输的环节,显式声明类型——DTO 字段用
Long
包装类而非
long
;数据库字段映射强制指定
javaType="java.lang.Long"
;JSON 接口对大数字段加
@JsonFormat(shape = JsonFormat.Shape.STRING)
转字符串传输

相关文章