java中有四种类型的引用,关于引用的类在java.lang.ref包下,其类图如下:
各种引用类型介绍
⑴强引用(StrongReference)
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
⑵软引用(SoftReference)
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
⑶弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
⑷虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
示列代码如下:
public void test() { //MyDate finalRef = new MyDate(); MyDate softRef = new MyDate(); MyDate weakRef = new MyDate(); MyDate phantomRef = new MyDate(); ReferenceQueue<MyDate> softQueue = new ReferenceQueue<MyDate>(); ReferenceQueue<MyDate> weakQueue = new ReferenceQueue<MyDate>(); ReferenceQueue<MyDate> phantomQueue = new ReferenceQueue<MyDate>(); SoftReference<MyDate> soft = new SoftReference<MyDate>(softRef, softQueue); WeakReference<MyDate> weak = new WeakReference<MyDate>(weakRef, weakQueue); PhantomReference<MyDate> phantom = new PhantomReference<MyDate>(phantomRef, phantomQueue); softRef = null; weakRef = null; phantomRef = null; print(soft); print(weak); print(phantom); System.out.println("phantom.isEnqueued:"+phantom.isEnqueued()); System.gc(); System.out.println("============================================================="); print(soft); print(weak); print(phantom); //phantom.enqueue(); System.out.println("phantom.isEnqueued:"+phantom.isEnqueued()); System.gc(); System.gc(); System.gc(); System.gc(); System.gc(); System.out.println("============================================================="); print(phantom); System.out.println("phantom.isEnqueued:"+phantom.isEnqueued()); } public void print(Reference<MyDate> ref) { MyDate obj = ref.get(); System.out.println("ref = "+ref+"\tobj="+obj); }
打印结果如下:
ref = java.lang.ref.SoftReference@61de33 obj=Date: 1399972627546 ref = java.lang.ref.WeakReference@14318bb obj=Date: 1399972627546 ref = java.lang.ref.PhantomReference@ca0b6 obj=null phantom.isEnqueued:false ============================================================= ref = java.lang.ref.SoftReference@61de33 obj=Date: 1399972627546 ref = java.lang.ref.WeakReference@14318bb obj=null ref = java.lang.ref.PhantomReference@ca0b6 obj=null obj [Date: 1399972627546] is gc phantom.isEnqueued:false obj [Date: 1399972627546] is gc ============================================================= ref = java.lang.ref.PhantomReference@ca0b6 obj=null phantom.isEnqueued:true
多次GC之后,虚引用被加入到引用队列中
虚引用会引起OOM
public void test() { Reference<MyRef>[] referent = new PhantomReference[100000]; ReferenceQueue<MyRef> queue = new ReferenceQueue<MyRef>(); for (int i = 0; i < referent.length; i++) { referent[i] = new PhantomReference<MyRef>(new MyRef(), queue);// throw } System.out.println(referent[referent.length-1].get()); }
设置最大堆内存为2M,最后打印:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at test.ref.TestPhantom.test(TestPhantom.java:21) at test.ref.TestPhantom.main(TestPhantom.java:11)
引用类型总结
引用类型 | 获取引用对象方式 | 引用对象回收条件 | 是否会造成OOM |
强引用 | 直接获取 | 不回收 | 是 |
软引用 | 通过引用对象的get() | 内存满时 | 否 |
弱引用 | 通过引用对象的get() | 垃圾回收时 | 否 |
虚引用 | 无法获得 | 不回收 | 是 |
JDK中的后台线程:
java.lang.ref.Reference$ReferenceHandler
当对象被回收时,虚拟机触发这个引用线程,这个线程用来处理各种类型的引用,并将引用加入到引用
队列中
java.lang.ref.Finalizer$FinalizerThread
当检查到有强引用被加入到队列后,就从队列中取出引用并,之后调用finalize()方法。并将强引用队列的前后引用关系清空
引用队列和两个后台线程执行图如下:
在这个图中, 各种引用被放入到一个队列中,这是一个双向队列,由后台线ReferenceHandler负责处理这个队列,将不同的引用加入到相应的队列中,并将强引用加入到强引用队列中。
此时FinalizerThread检查到有引用加入到队列中了,就将其从最上方的引用队列中删除,然后调用Object#finalize()方法。
ReferenceHandler核心逻辑如下:
public void run() { for (;;) { Reference r; synchronized (lock) { if (pending != null) { r = pending; Reference rn = r.next; pending = (rn == r) ? null : rn; r.next = r; } else { try { lock.wait(); } catch (InterruptedException x) { } continue; } } // Fast path for cleaners if (r instanceof Cleaner) { ((Cleaner)r).clean(); continue; } ReferenceQueue q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); } }
FinalizerThread核心逻辑和引用队列的逻辑:
public void run() { for (;;) { try { Finalizer f = (Finalizer)queue.remove(); f.runFinalizer(); } catch (InterruptedException x) { continue; } } } //引用队列会阻塞获取: public Reference<? extends T> remove(long timeout) throws IllegalArgumentException, InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("Negative timeout value"); } synchronized (lock) { Reference<? extends T> r = reallyPoll(); if (r != null) return r; for (;;) { lock.wait(timeout); r = reallyPoll(); if (r != null) return r; if (timeout != 0) return null; } } }
通过jstack打印出的线程堆栈:
最后还有一个WeakHashMap,它的键是弱引用类型,值为强引用,当键的引用被回收后,这个KV对就会被删除,WeakHashMap中的Entry,就将Key包装成WeakReference,将加入到弱引用队列中,
每次调用get都会对弱引用队列做检查,如果有数据则将其删除,其实现函数是expungeStaleEntries()
public void weak() { Map<MyRef, Object> weakmap = new WeakHashMap<MyRef, Object>(); MyRef a = new MyRef(); MyRef b = new MyRef(); weakmap.put(a, "aaa"); weakmap.put(b, "bbb"); weakmap.remove(a); a = null; b = null; System.gc(); Iterator<Entry<MyRef, Object>> i = weakmap.entrySet().iterator(); while (i.hasNext()) { Map.Entry<MyRef, Object> en = i.next(); System.out.println("map:" + en.getKey() + ":" + en.getValue()); } }
参考:
相关推荐
### Java中的正则表达式:分组引用介绍 #### 概述 正则表达式是计算机科学中一种非常强大的文本处理工具,在Java等编程语言中广泛应用于字符串匹配、搜索替换等场景。当需要对正则表达式的部分结果进行进一步处理...
开发者在Java项目中引用这个JAR文件,就可以通过JDBC API与MySQL数据库进行通信。 2. **README.txt**:这是一个常见的文本文件,通常包含关于软件的安装指南、使用说明、版本信息或版权信息等。对于MySQL Connector/...
- Java库文件:如protobuf-java.jar,供Java项目引用。 - 编译器工具:protoc,用于将.proto文件转换为Java代码。 - 示例和测试代码:展示如何使用protobuf API。 - 文档:介绍如何安装、使用和进一步了解...
`taobao-sdk-java-20110512.jar` 是淘宝SDK的核心库文件,包含了所有必要的类和方法,供开发者在Java项目中引用。这个版本号(20110512)表示该SDK的发布日期,开发者需要根据实际需求选择合适版本的SDK以确保兼容性...
JDBC(Java Database Connectivity)是Java编程语言与各种数据库之间通信的一种标准接口。最新版的JDBC驱动程序,即mysql-connector-java-8.0.16,是MySQL官方提供的用于连接Java应用程序到MySQL数据库的工具。这个...
1. **JavaCV介绍** JavaCV是基于Java的计算机视觉开发包,由Tangram Vision公司维护。它的目标是简化Java和Android上的计算机视觉应用开发,通过提供对多个跨平台库的简单接口,如OpenCV、FFmpeg、ImageIO、HighGUI...
- **数据类型与变量**:了解Java的八种基本数据类型以及引用类型,掌握变量的声明、初始化和作用域。 - **流程控制**:包括条件语句(if, switch)和循环(for, while, do-while)的使用。 - **面向对象**:深入...
谷歌API Java客户端库是一个强大的工具,它为开发者提供了与Google各种服务进行交互的能力,包括但不限于Google Drive、Google Calendar、Google Maps等。在这个1.6.0-Beta版本中,我们可以看到一系列的组件和资源,...
存放方法栈、成员基本数据类型变量的引用和值、成员引用数据类型变量的引用
本文将详细讲解如何利用JMeter连接MySQL数据库,并重点介绍使用"mysql-connector-java-5.1.38-bin.jar"驱动进行数据库连接的相关知识点。 首先,JMeter是一款强大的负载和性能测试框架,它可以对各种服务器、协议和...
"protobuf-3.17.0"可能包含了protobuf的库文件和其他资源,如jar包,供Java项目引用。 使用protobuf时,你需要注意以下几点: 1. **版本兼容**:确保你的protobuf编译器版本与protobuf库版本相匹配,以避免编译或...
综上所述,“JAVA -SL275”文档是一份详细介绍Java编程语言的学习指南,不仅涵盖了Java的基础知识,还包括了高级特性和实际应用场景的指导。对于希望深入了解Java编程的初学者来说,这是一份非常有价值的资源。
此外,Java正则表达式支持各种复杂的匹配规则,比如贪婪与非贪婪匹配、分组、反向引用等高级特性。这些特性的掌握对于编写更加灵活和精确的正则表达式非常有帮助。 #### 五、总结 本文详细介绍了如何使用Java中的`...
2. **Eclipse IDE介绍**:介绍Eclipse的基本界面,包括工作区、透视图、视图、编辑器等,并演示如何创建一个新的Java项目。 3. **Java编程基础**:涵盖基本的Java语法,如数据类型、变量、控制流语句、类和对象,...
"Java--全家桶课件"可能指的是一个全面的Java学习资源集合,涵盖了从基础到高级的各种主题。这个压缩包包含了两个部分,分别是“第1部分:Java基础编程.zip”和“第2部分:Java高级编程.zip”,下面我们将分别探讨这...
- **变量和数据类型**:解释了Java中的各种数据类型,包括基本类型(如int、double)和引用类型(如String、数组)。 - **控制结构**:涵盖了条件语句(if-else)、循环语句(for、while)以及其他流程控制指令。 - ...
接着,你会接触到Java的基础语法,如数据类型(包括基本类型和引用类型)、变量、运算符、流程控制语句(如if条件语句、for循环、while循环和switch-case语句)以及方法的定义和调用。 其次,深入理解面向对象编程...
- 自我介绍:如何准备和进行有效的自我介绍。 - 项目经验:阐述项目经验时应关注的关键点。 - 技能展示:如何展示自己的技能和解决问题的能力。 - 行业动态:关注Java领域的新技术和发展趋势。 这份面试指南...
为了解决这个问题,并帮助初学者更好地理解Java的`package`和`import`机制,本文将详细介绍这两个核心概念。 #### 二、Package与Import的基本概念 **1. Package的作用** - **组织代码:** `package`是Java中用来...
8. **内存管理**:探讨Java的垃圾回收机制,包括对象的生命周期、引用类型(强引用、软引用、弱引用、虚引用)以及如何避免内存泄漏。 9. **反射**:介绍Java反射机制,允许程序在运行时动态获取类的信息并操作类的...