一、核心概念
1.1 什么是 Long
缓存机制?
Java 中的 Long
类(以及其他部分包装类如 Integer
、Short
、Byte
、Character
)为了提升性能和减少内存开销,内部维护了一个静态缓存池(称为 LongCache
),用于缓存一定范围内的常用 Long
对象。
- 目的:避免频繁创建相同值的包装对象,复用已有对象,减少 GC 压力。
- 缓存范围:
-128
到127
(包含两端),共 256 个Long
对象。 - 触发方式:通过
Long.valueOf(long)
方法访问,而非new Long(...)
。 - 实现原理:
Long
类内部有一个静态数组LongCache.cache
,在类加载时初始化,存储-128
到127
的Long
实例。
⚠️ 注意:
Long
缓存是 JVM 规范要求 的(Java SE 规范),所有兼容 JVM 必须实现此缓存。
二、操作步骤(非常详细)
步骤 1:理解缓存的创建时机
- JVM 启动,加载
java.lang.Long
类。 - 执行
static
块,初始化private static class LongCache
。 - 创建一个
Long
数组cache[]
,长度为256
。 - 循环填充数组:
cache[0]
存储new Long(-128)
cache[1]
存储new Long(-127)
- ...
cache[128]
存储new Long(0)
- ...
cache[255]
存储new Long(127)
- 缓存创建完成,后续可通过
valueOf()
复用。
步骤 2:使用 valueOf()
获取缓存对象(推荐方式)
// ✅ 正确使用缓存
Long a = Long.valueOf(100); // 值在 [-128,127] 范围内 → 返回缓存对象
Long b = Long.valueOf(100); // 再次获取 → 返回**同一个**缓存对象
System.out.println(a == b); // 输出: true(引用相等)
System.out.println(a.equals(b)); // true(值相等)
步骤 3:对比 new Long()
(绕过缓存,不推荐)
// ❌ 绕过缓存,每次创建新对象
Long c = new Long(100);
Long d = new Long(100);
System.out.println(c == d); // 输出: false(引用不同)
System.out.println(c.equals(d)); // true(值相等)
步骤 4:测试超出缓存范围的值
Long e = Long.valueOf(200); // 超出范围 → 创建新对象
Long f = Long.valueOf(200); // 再次调用 → 再次创建新对象
System.out.println(e == f); // 输出: false(非缓存对象,不重用)
步骤 5:自动装箱也使用缓存
// ✅ 自动装箱语法糖等价于 valueOf()
Long g = 100L; // 编译后 → Long.valueOf(100)
Long h = 100L; // 编译后 → Long.valueOf(100)
System.out.println(g == h); // true(使用缓存)
但:
Long i = 200L; // Long.valueOf(200)
Long j = 200L; // Long.valueOf(200)
System.out.println(i == j); // false(超出缓存,新对象)
三、常见错误
❌ 错误 1:使用 ==
比较 Long
对象
Long x = 300L;
Long y = 300L;
if (x == y) { ... } // ❌ 可能为 false!应使用 equals()
✅ 正确做法:
if (x.equals(y)) { ... } // ✅ 正确比较值
❌ 错误 2:认为所有小数值都缓存
Long a = Long.valueOf(127);
Long b = Long.valueOf(127);
System.out.println(a == b); // true
Long c = Long.valueOf(128); // 刚好超出
Long d = Long.valueOf(128);
System.out.println(c == d); // false!
❌ 错误 3:在性能敏感场景频繁创建大 Long
// ❌ 每次都创建新对象,GC 压力大
for (int i = 0; i < 1000000; i++) {
Long num = new Long(i + 1000); // 大量临时对象
process(num);
}
四、注意事项
- 缓存范围固定:
-128
到127
,不可配置(与Integer
不同,Integer
的缓存上限可通过-XX:AutoBoxCacheMax
调整,但Long
不支持)。 - 仅
valueOf()
有效:new Long()
永远不走缓存。 - 自动装箱走缓存:
Long x = 100L;
等价于Long.valueOf(100)
。 - 线程安全:缓存对象是 不可变的(
Long
是 final 类,值不可变),因此缓存是线程安全的。 - 内存占用:缓存 256 个
Long
对象,内存开销极小(约几 KB),但换来巨大性能收益。 - 只适用于常用值:若应用频繁使用
1000
,2000
等值,缓存无效,需考虑其他优化。
五、使用技巧
优先使用
valueOf()
或自动装箱Long num = 100L; // ✅ 推荐 // 而非 new Long(100)
比较
Long
使用equals()
if (a != null && a.equals(b)) { ... }
利用缓存优化高频小数值
- 如状态码、标志位、小 ID 等使用
0-127
范围内的值,可享受缓存优势。
- 如状态码、标志位、小 ID 等使用
避免在循环中创建大
Long
// ❌ 避免 for (long i = 1000; i < 1000000; i++) { Long wrapper = i; // 自动装箱 → 每次可能创建新对象 } // ✅ 优化:使用基本类型 for (long i = 1000; i < 1000000; i++) { processPrimitive(i); // 传 long,避免装箱 }
六、最佳实践
✅ 推荐做法:
统一使用自动装箱或
valueOf()
Long id = 42L; // ✅ 清晰、使用缓存 Long count = Long.valueOf(n); // ✅ 显式调用
值比较用
equals()
if (value != null && value.equals(100L)) { ... }
性能敏感场景减少装箱
- 在循环、高频调用中,优先使用
long
基本类型。 - 使用
Long.parseLong()
获取long
,而非Long.valueOf(str)
获取对象。
- 在循环、高频调用中,优先使用
理解缓存边界
- 明确
127
是缓存上限,128
不缓存。
- 明确
七、性能优化
场景 | 推荐方式 | 性能优势 |
---|---|---|
获取小数值 Long 对象 | Long.valueOf(100) 或 100L |
复用缓存,零创建开销 |
获取大数值 Long 对象 | Long.valueOf(1000) |
仍优于 new Long() (语义一致) |
高频数值计算 | 使用 long 基本类型 |
避免装箱/拆箱开销 |
字符串转数值 | Long.parseLong(str) |
返回 long ,无对象创建 |
🔍 性能对比(小数值):
new Long(100)
:每次分配对象 → 慢,GC 压力大Long.valueOf(100)
:返回缓存引用 → 极快(O(1) 查表)
八、总结
Long
缓存机制是 JVM 为优化性能而内置的重要特性,核心在于 复用 -128
到 127
范围内的 Long
对象。
✅ 核心要点速查:
项目 | 说明 |
---|---|
缓存范围 | -128 到 127 (含) |
触发方法 | Long.valueOf(long) 和自动装箱 |
不触发 | new Long(...) |
比较方式 | 引用用 == (仅限缓存内小值),值比较用 equals() |
性能优势 | 减少对象创建,降低 GC,提升速度 |
适用类型 | Byte , Short , Integer , Long , Character 都有类似缓存 |
🚀 实践口诀:
“小数装箱用缓存,
==
比较要小心;大数频繁要规避,基本类型性能高。”