1. 对象类型 vs 基本类型 & 不可变性原理
- 基本类型(如
int):变量中直接存储的是值本身。赋值 a = 20 时,是直接覆盖内存中原来的值,不会创建新对象。它们天生为极致性能而生,不存在于堆内存中。
- 对象类型(如
String, Integer):变量中存储的是指向堆内存中真实对象的内存地址(引用)。
- 不可变性原理:像
String 和 Integer 这样的对象,一旦创建,其内部状态就永远不能改变。当你执行 s = "World" 或 a = 20 时,JVM 不会去修改原对象,而是在堆内存中新建一个对象,然后让变量指向新地址(即“换人”而不是“换心”)。这保证了线程安全和作为 HashMap Key 时的稳定性。
2. 缓存池与常量池的运作机制
- String 常量池:JVM 在内存中开辟的“公共仓库”。相同的字符串字面量在池中只保留一份,所有变量指向同一个对象。
- Integer 缓存池(IntegerCache):JVM 在启动时,提前在内存中创建好特定范围的 Integer 对象(本质是一个静态数组)。通过
Integer.valueOf() 或自动装箱时,直接复用池中的对象,避免频繁 new 对象带来的内存和 GC 开销。
- 全局唯一性:无论是 String 常量池还是 Integer 缓存池,在整个 JVM 进程中都只会存在一份。 它们属于类级别的静态资源,伴随 JVM 的整个生命周期,全 JVM 共享,绝对不会被垃圾回收(GC)。
- 核心区别:基本类型没有缓存池;对象类型通过缓存池实现高频对象的复用。
3. -128 到 127 的意义、配置及越界处理
- 意义:这是 JVM 在“内存占用”和“命中率”之间找到的黄金平衡点。现实业务中(如循环索引、状态码等)这个范围内的数字出现频率极高,缓存它们能拦截绝大部分的内存开销。
- 配置最大值:可以通过 JVM 启动参数
-XX:AutoBoxCacheMax=2000 手动扩大缓存池范围,以适应特定业务场景。
- 超出范围的处理:如果数值不在缓存池范围内(如 129),JVM 绝对不会扩容缓存池,而是直接在堆内存中
new 一个全新的对象。程序正常运行,只是享受不到缓存池的性能红利。
4. 变量的存储位置与缓存的关系
- 局部变量:存储在栈内存(Stack)中,方法执行完毕后,变量(门牌号)立刻销毁。
- 全局/成员变量:存储在堆内存(Heap)中,随着对象一起存在。
- 与缓存池的关系:无论是局部还是全局变量,当赋值为 -128~127 之间的数时,它们拿到的都是同一个缓存池对象的地址。局部变量销毁时,仅仅是断开了指向缓存池的线,缓存池里的对象永远不会被垃圾回收(GC)。
5. equals 与 == 比较的原理
== 比较:比较的是内存地址。对于基本类型,比较的是值;对于对象类型,比较的是指针是否指向堆中的同一个对象。
.equals() 比较:比较的是对象的内部内容。
- 铁律:因为 HashMap 依赖这两个方法,所以重写
equals() 必须同时重写 hashCode()。否则会导致内容相同的对象被散列到不同的桶中,引发逻辑 Bug。
6. HashCode 的生成机制
- 延迟生成(懒加载):对象在
new 的时候,不会立刻生成 HashCode。只有当第一次调用 .hashCode() 方法时,JVM 才会进行计算,并将其固化在对象的“对象头(Mark Word)”中。
- 重写的特例:像 String 这样重写了 hashCode 的类,每次调用都会根据内容实时计算,不会存入对象头。
7. 哈希冲突(Hash Collision)与 HashMap 底层
- 必然重复:因为 int 的范围有限(约 42 亿),而现实数据无限,根据“抽屉原理”,不同的对象算出相同的 HashCode 是必然的。
- 解决机制:HashCode 只是“敲门砖”,负责把数据定位到 HashMap 的同一个“桶(Bucket)”中。真正决定数据是否覆盖的,是桶内通过
.equals() 进行的“验明正身”。
- 链表与红黑树:如果发生严重冲突,数据会在同一个桶内形成链表。当链表长度超过 8 时,JVM 会将其转化为“红黑树”,将查找时间复杂度从 O(n)O(n) 优化到 O(logn) O(logn) ,防止系统崩溃。
posted @
2026-08-30 22:50
倾听-静轩水月
阅读(
4)
评论()
收藏
举报