17370845950

在Java中什么是引用类型_Java对象引用关系解析
Java引用类型是class、interface、array、enum、record的统称,变量存储堆中对象地址而非实际数据;null为其默认值;含强、软、弱、虚四种引用强度;==比较地址,equals()默认也比地址,需重写才比内容;Java只有值传递,非引用传递。

Java 中的“引用类型”不是某种特殊的数据类型,而是对 classinterfacearrayenumrecord 这五类类型的统称——它们的变量存储的不是实际数据,而是堆内存中对象的地址。

引用类型变量到底存的是什么

声明一个 String s = "hello";,变量 s 本身在栈上,值是一个 32 位或 64 位的内存地址(取决于 JVM 是否开启压缩指针),指向堆中真正的 String 对象。这个地址本身不是对象,也不能直接算术运算,只用于定位。

常见误解是“引用就是指针”,但 Java 引用被刻意设计为不可见、不可操作的逻辑地址,JVM 可以在 GC 时移动对象并自动更新所有引用,开发者完全感知不到。

  • null 是引用类型的默认初始值,表示“不指向任何对象”
  • 基本类型(如 intboolean)变量直接存值,不存在 null
  • 数组是引用类型,哪怕元素是基本类型: int[] arrarr 是引用,arr[0] 才是值

四种引用强度:强、软、弱、虚

JDK 提供java.lang.ref 包下的四类包装类,用于控制对象被 GC 回收的时机,和普通变量声明无关,必须显式使用。

它们的区别不在语法,而在 JVM 的回收策略:

  • StrongReference:日常所有赋值都是强引用,只要可达就不会被回收
  • SoftReference:内存不足时才回收,适合做缓存(如 SoftReference
  • WeakReference:GC 时立刻回收,常用于避免内存泄漏(如 WeakHashMap 的 key)
  • PhantomReference:无法通过它访问对象,仅用于在对象被回收后收到通知,必须配合 ReferenceQueue

示例:

WeakReference wr = new WeakReference<>(new String("temp"));
System.gc(); // wr.get() 很可能返回 null

== 和 equals() 判等差异的本质原因

因为引用类型变量存的是地址,所以 == 比较的是两个变量是否指向堆中**同一个对象**;而 equals() 默认行为也是 ==(继承自 Object),只有重写后才可能比较内容。

  • String a = "abc"; String b = "abc";a == btrue(字符串常量池优化)
  • String a = new String("abc"); String b = new String("abc");a == bfalse,但 a.equals(b)true
  • 自定义类若没重写 equals(),两个不同实例即使字段全同,equals() 也返回 false

容易被忽略的引用陷阱

很多内存问题和逻辑 bug 都源于对引用传递机制的误判。

  • Java 中**没有引用传递,只有值传递**:方法参数接收的是引用的副本,修改该副本指向新对象不影响原引用;但通过该副本修改对象内部状态(如 list.add()),会影响原对象
  • 循环引用不会阻止 GC:JVM 使用可达性分析,不是引用计数,A.ref = B; B.ref = A; 且无外部强引用时,两者都会被回收
  • 局部变量引用的对象,在方法结束后不一定立即可回收——只要还有其他强引用(如静态集合、线程局部变量),就会一直存活

最常踩的坑是把大对象塞进 static 集合却忘了清理,导致内存泄漏,而堆 dump 里看到的“引用链”往往很长,源头却只是某次忘记 remove() 的操作。