tulip notes
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Star-Lord

希望一天成为大师的学徒
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java基础与面向对象

  • 高级进阶

    • Java中的集合
    • Java泛型
    • Java中的日志
    • Java8-行为参数化与Lambda
    • Java8-函数式数据处理
    • Java8-代码优化与设计模式
    • Java8-新的日期和时间API
    • Java中的引用
      • 引用的存在理由
        • 为什么不能直接操作对象?
        • 引用的好处
        • 引用与GC
        • 4 种引用类型
        • 设计4种的原因
        • 强引用的扩展
      • 代码示例
  • 并发合集

  • JVM合集

  • 实战与细节

  • 代码之丑与提升

  • 《Java》学习笔记
  • 高级进阶
EffectTang
2025-02-19
目录

Java中的引用

# Java中的引用

# 引用的存在理由

Java是面向对象的语言,对象通常存储在堆内存中。如果直接操作对象,可能意味着每次操作都需要在栈上创建对象的副本,这显然效率低下,尤其是对于大对象来说。所以引用应该是一种间接访问对象的方式,避免复制开销,同时方便内存管理。

引用存在的必要性。直接操作对象在Java中可能不可行,因为对象的大小不固定,而栈空间有限,所以通过引用来指向堆中的对象地址,这样可以更灵活地管理内存。引用还允许动态内存分配,对象生命周期管理,比如垃圾回收机制就是基于引用来判断对象是否可达。

引用就是一个地址,该地址是对象在堆(heap)中的具体位置。通过该引用(地址),可以找到一个对象,找到就意味着可以进行操作对象。

# 为什么不能直接操作对象?

Java中对象通常存储在堆内存中,而堆内存的分配和回收由JVM动态管理。如果直接操作对象(例如在方法栈中传递整个对象),会导致以下问题:

  1. 性能开销:对象可能占用较大内存,直接传递会导致频繁的内存复制(类似C++中的按值传递)。
  2. 内存管理困难:对象生命周期无法灵活控制,可能导致内存泄漏或过早释放。
  3. 动态性缺失:无法实现多态、对象共享等面向对象特性。

通过引用(即指向堆内存中对象的指针),可以避免直接操作对象的物理内存,实现轻量级的对象访问。

# 引用的好处

  • 间接访问对象 引用存储的是对象的地址(类似指针),而非对象本身,避免了直接操作对象的内存开销。

  • 动态内存管理 通过引用,JVM可以自动回收未被引用的对象(垃圾回收),无需手动释放内存。

  • 对象共享 多个引用可以指向同一个对象,实现数据共享(例如多个线程操作同一个对象)。

  • 灵活的生命周期控制 不同类型的引用允许开发者精细控制对象的回收策略(如缓存、弱关联等)。

# 引用与GC

在Java程序运行中,引用常常与GC打交道。因没有的对象会被清理,相应的,该对象对应的引用也要被清理。

在 Java 中,引用(Reference) 决定了对象与垃圾回收器(GC)的交互方式。其中ThreadLocalMap 的 Entry 设计涉及弱引用和强引用,理解它们的差异对避免内存泄漏至关重要。

# 4 种引用类型

Java中引用的类型可以分为4种。分别是:强引用、软引用、弱引用和虚引用。每种引用的不同在于垃圾回收时的处理方式。

强引用最常见,只要存在,对象就不会被回收。软引用在内存不足时会被回收,适合缓存。弱引用一旦没有强引用指向对象,就会被回收,同样用于缓存,比如WeakHashMap。虚引用主要用于跟踪对象被回收的状态,必须和引用队列一起使用。

什么需要这么多类型的引用,直接都用强引用不行吗?一个地址而已。

# 设计4种的原因

Java的四种引用类型(强引用、软引用、弱引用、虚引用)通过不同的回收策略,解决了对象生命周期管理的多样化需求:

引用类型 特点 垃圾回收时机 典型应用场景
强引用 (Strong Reference) 默认引用类型,Object obj = new Object(); 只要强引用存在,对象永远不会被回收。 普通对象操作。
软引用 (Soft Reference) 通过SoftReference类实现。 内存不足时会被回收。 实现缓存(如图片缓存)。
弱引用 (Weak Reference) 通过WeakReference类实现。 无论内存是否充足,只要没有强引用,立即回收。 缓存(如WeakHashMap)、避免内存泄漏。
虚引用 (Phantom Reference) 通过PhantomReference类实现。 对象被回收时,仅收到通知,无法通过虚引用访问对象。 管理堆外内存(如NIO的DirectBuffer)、资源清理。

# 强引用的扩展

在 Java 中,强引用(Strong Reference) 是默认的引用类型,也是我们日常编程中最常使用的引用。它的回收规则非常简单,但直接影响内存管理的关键逻辑:

  • 强引用的对象只要还存在至少一个强引用并且这些引用都是可达的,那么这个对象就不会被垃圾回收。
  • 要让一个强引用的对象能够被回收,必须确保没有任何强引用指向它,可以通过将引用设为 null 或者让引用超出其作用域来实现。
  • 即便如此,具体的垃圾回收行为仍然取决于JVM的具体实现以及当前的内存使用情况。调用 System.gc() 只是建议JVM进行垃圾回收,并不能保证立即执行。

1.显式断开引用 将引用赋值为 null,主动切断强引用链:

obj = null; // 对象此时可能被回收
1

2.引用超出作用域 当强引用变量离开其作用域(例如方法栈帧销毁),引用自动失效:

void example() {
    Object localObj = new Object(); // 强引用
} // 方法结束,localObj 超出作用域,对象可能被回收
1
2
3

3.对象不再被任何强引用关联 即使存在其他类型的引用(如软引用、弱引用),只要没有强引用指向对象,对象仍会被回收。

# 代码示例

  1. 强引用 普通对象的默认引用,确保对象只要被需要就不会被回收。

    Object obj = new Object(); // 强引用
    
    1
  2. 软引用 适合缓存场景,内存不足时自动释放,避免OOM。

    SoftReference<byte[]> cache = new SoftReference<>(new byte[1024]);
    byte[] data = cache.get(); // 可能返回null
    
    1
    2
  3. 弱引用 防止内存泄漏(如监听器、临时映射)。

    WeakReference<Listener> weakListener = new WeakReference<>(listener);
    
    1
  4. 虚引用

用于跟踪对象被回收的状态(需配合ReferenceQueue)。

ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);
1
2
上次更新: 2025/04/23, 16:23:16
Java8-新的日期和时间API
Java中创建线程的几种方式

← Java8-新的日期和时间API Java中创建线程的几种方式→

最近更新
01
面向切面跟自定义注解的结合
05-22
02
时间跟其他数据的序列化
05-19
03
数据加密与安全
05-17
更多文章>
Theme by Vdoing | Copyright © 2023-2025 EffectTang
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式