Java引用类型原理
其实object是栈中分配的一个引用,而new Object()是在堆中分配的一个对象。而'='的作用是用来将引用指向堆中的对象的。就像你叫张三但张三是个名字而已并不是一个实际的人,他只是指向的你。
-
对于软引用的介绍是:在内存不足的时候才会被回收,那内存不足是怎么定义的?什么才叫内存不足?
-
虚引用的介绍是:形同虚设,虚引用并不会决定对象的生命周期。主要用来跟踪对象被垃圾回收器回收的活动。真的是这样吗?
-
虚引用在Jdk中有哪些场景下用到了呢?
-
可以让程序员通过代码的方式来决定某个对象的生命周期;
-
有利用垃圾回收。
1 | Object obj = new Object(); |
这种就是强引用了,是不是在代码中随处可见,最亲切。只要某个对象有强引用与之关联,这个对象永远不会被回收,即使内存不足,JVM宁愿抛出OOM,也不会去回收。
1 | obj = null ; |
我们可以手动调用GC,看看如果强引用和对象之间的关联被中断了,资源会不会被回收,为了更方便、更清楚的观察到回收的情况,我们需要新写一个类,然后重写finalize方法,下面我们来进行这个实验:
1 2 3 4 5 6 7 8 9 10 11 | public class Student { @Override protected void finalize() throws Throwable { System.out.println( "Student 被回收了" ); } } public static void main(String[] args) { Student student = new Student(); student = null ; System.gc(); } |
运行结果:
可以很清楚的看到资源被回收了。当然,在实际开发中,千万不要重写finalize方法。在实际的开发中,看到有一些对象被手动赋值为null,很大可能就是为了“特意提醒”JVM这块资源可以进行垃圾回收了。
1 | SoftReference<Student> studentSoftReference = new SoftReference<Student>( new Student()); |
软引用就是把对象用SoftReference包裹一下,当我们需要从软引用对象获得包裹的对象,只要get一下就可以了:
1 2 3 | SoftReference<Student>studentSoftReference= new SoftReference<Student>( new Student()); Student student = studentSoftReference.get(); System.out.println(student); |
软引用特点:当内存不足,会触发JVM的GC,如果GC后,内存还是不足,就会把软引用的包裹的对象给干掉,也就是只有在内存不足,JVM才会回收该对象。
1 2 3 4 5 6 | SoftReference< byte []> softReference = new SoftReference< byte []>( new byte [ 1024 * 1024 * 10 ]); System.out.println(softReference.get()); System.gc(); System.out.println(softReference.get()); byte [] bytes = new byte [ 1024 * 1024 * 10 ]; System.out.println(softReference.get()); |
上述定义了一个软引用对象,里面包裹了byte[],byte[]占用了10M,然后又创建了10Mbyte[]。
1 2 3 4 | 运行结果: [B @11d7fff [B @11d7fff null |
可以很清楚的看到手动完成GC后,软引用对象包裹的byte[]还活的好好的,但是当我们创建了一个10M的byte[]后,最大堆内存不够了,所以把软引用对象包裹的byte[]给干掉了,如果不干掉,就会抛出OOM。
1 2 | WeakReference< byte []> weakReference = new WeakReference< byte []>( new byte [ 1024 \* 1024 \* 10 ]); System.out.println(weakReference.get()); |
弱引用的特点是不管内存是否足够,只要发生GC,都会被回收:
1 2 3 4 | WeakReference< byte []> weakReference = new WeakReference< byte []>( new byte [ 1 ]); System.out.println(weakReference.get()); System.gc(); System.out.println(weakReference.get()); |
运行结果:
- ThreadLocal.ThreadLocalMap threadLocals
1 2 3 4 5 6 7 8 | static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super (k); value = v; } } |
可以看见Entry是WeakReference的子类,而这个弱引用所关联的对象正是我们的ThreadLocal这个对象。那么问题:
1 2 3 | ReferenceQueue queue = new ReferenceQueue(); PhantomReference< byte []> reference = new PhantomReference< byte []>( new byte [ 1 ], queue); System.out.println(reference.get()); |
虚引用的使用和上面说的软引用、弱引用的区别还是挺大的,直接来运行:
1 2 3 | public T get() { return null ; } |
直接返回了null。
1 2 3 | ReferenceQueue queue = new ReferenceQueue(); PhantomReference< byte []> reference = new PhantomReference< byte []>( new byte [ 1 ], queue); System.out.println(reference.get()); |
创建虚引用对象,我们除了把包裹的对象传了进去,还传了一个ReferenceQueue,从名字就可以看出它是一个队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | ReferenceQueue queue = new ReferenceQueue(); List< byte []> bytes = new ArrayList<>(); PhantomReference<Student> reference = new PhantomReference<Student>( new Student(),queue); new Thread(() -> { for ( int i = 0 ; i < 100 ;i++ ) { bytes.add( new byte [ 1024 * 1024 ]); } }).start(); new Thread(() -> { while ( true ) { Reference poll = queue.poll(); if (poll != null ) { System.out.println( "虚引用被回收了:" + poll); } } }).start(); Scanner scanner = new Scanner(System.in); scanner.hasNext(); } 运行结果: Student 被回收了 虚引用被回收了:java.lang.ref.PhantomReference @1ade6f1 |
-
第一个线程往集合里面塞数据,随着数据越来越多,肯定会发生GC。
-
第二个线程死循环,从queue里面拿数据,如果拿出来的数据不是null,就打印出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public abstract class Reference<T> { //引用的对象 private T referent; //回收队列,由使用者在Reference的构造函数中指定 volatile ReferenceQueue<? super T> queue; //当该引用被加入到queue中的时候,该字段被设置为queue中的下一个元素,以形成链表结构 volatile Reference next; //在GC时,JVM底层会维护一个叫DiscoveredList的链表,存放的是Reference对象,discovered字段指向的就是链表中的下一个元素,由JVM设置 transient private Reference<T> discovered; //进行线程同步的锁对象 static private class Lock { } private static Lock lock = new Lock(); //等待加入queue的Reference对象,在GC时由JVM设置,会有一个java层的线程(ReferenceHandler)源源不断的从pending中提取元素加入到queue private static Reference<Object> pending = null ; } |
针对文章开头提出的几个问题,看完分析,我们已经能给出回答:
- 软引用会在内存不足时被回收,内存不足的定义和该引用对象get的时间以及当前堆可用内存大小都有关系,计算公式在上文中也已经给出。
- 严格的说,虚引用是会影响对象生命周期的,如果不做任何处理,只要虚引用不被回收,那其引用的对象永远不会被回收。所以一般来说,从ReferenceQueue中获得PhantomReference对象后,如果PhantomReference对象不会被回收的话(比如被其他GC ROOT可达的对象引用),需要调用clear方法解除PhantomReference和其引用对象的引用关系。
- DirectByteBuffer中是用虚引用的子类Cleaner.java来实现堆外内存回收的。
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术