Java中四种引用详解
java 中的 4 种引用方式,适用于不同的场景,重点需要理解虚引用,结合文字和代码
强引用
被强引用的对象,不会被垃圾回收器回收,JVM 宁愿抛出 OOM 也不会去回收被强引用的对象;
M m = new M();
软引用
当堆空间够用时,GC 不会对软引用的对象进行回收,当堆空间不足以分配新的空间时,触发 GC 就会对这部分对象进行回收,通常用在缓存等领域。将缓存对象使用软引用,空间不足的时候释放这部分空间,需要再次使用的时候,重新从 DB 中加载即可。
另外软引用可以配合队列(ReferenceQueue
) 来使用,如果软引用引用的对象被垃圾回收,JVM 会把软引用加入到与之关联的引用队列中。
/** * 软引用:一般用在缓存,只要空间不足,GC 跑起来就会回收它 * 运行参数 -Xmx200m -XX:+PrintGC * Created by etfox on 2021/03/01 17:06 **/ public class TestSoftReference { public static void main(String[] ags) throws InterruptedException { //100M的缓存数据 byte[] cacheData = new byte[100 * 1024 * 1024]; //将缓存数据用软引用持有 SoftReference<byte[]> cacheRef = new SoftReference<>(cacheData); //将缓存数据的强引用去除 cacheData = null; System.out.println("第一次GC前" + cacheData); System.out.println("第一次GC前" + cacheRef.get()); //进行一次GC后查看对象的回收情况 System.gc(); //等待GC Thread.sleep(500); System.out.println("第一次GC后" + cacheData); System.out.println("第一次GC后" + cacheRef.get()); //在分配一个120M的对象,看看缓存对象的回收情况 byte[] newData = new byte[120 * 1024 * 1024]; System.out.println("分配后" + cacheData); System.out.println("分配后" + cacheRef.get()); } } console==> [GC (Allocation Failure) 4120K->1055K(15872K), 0.0016237 secs] [Full GC (Allocation Failure) 1055K->1054K(15872K), 0.0015426 secs] 第一次GC前null 第一次GC前[B@1973e9b [Full GC (System.gc()) 103583K->103455K(118340K), 0.0015559 secs] 第一次GC后null 第一次GC后[B@1973e9b [GC (Allocation Failure) 105575K->103455K(198016K), 0.0001733 secs] [Full GC (Allocation Failure) 103455K->103455K(198016K), 0.0011860 secs] [Full GC (Allocation Failure) 103455K->819K(198016K), 0.0012080 secs] 分配后null 分配后null
弱引用
弱引用的引用对象在每次 GC 时,不管当前堆内存大小,都会将这个对象清除。如果此对象偶尔使用,并且希望需要用到的时候可以获取到,但是又不希望影响这个对象的回收,就可以使用弱引用来描述对象。
当然弱引用也可以结合事件队列使用。
/** * 弱引用:如果对象时偶尔使用,并且希望使用的时候就能获取到(get),但是又不想影响此对象的垃圾收集 * 可以引入队列 * Created by etfox on 2021/03/01 17:59 **/ public class TestWeakReference { public static void main(String[] args) throws InterruptedException { //100M的缓存数据 byte[] cacheData = new byte[100 * 1024 * 1024]; //将缓存数据用软引用持有 WeakReference<byte[]> cacheRef = new WeakReference<>(cacheData); System.out.println("第一次GC前" + cacheData); System.out.println("第一次GC前" + cacheRef.get()); //进行一次GC后查看对象的回收情况 System.gc(); //等待GC Thread.sleep(500); System.out.println("第一次GC后" + cacheData); System.out.println("第一次GC后" + cacheRef.get()); //将缓存数据的强引用去除 cacheData = null; System.gc(); //等待GC Thread.sleep(500); System.out.println("第二次GC后" + cacheData); System.out.println("第二次GC后" + cacheRef.get()); } } console==> [GC (Allocation Failure) 3912K->1025K(15872K), 0.0016372 secs] [Full GC (Allocation Failure) 1025K->1024K(15872K), 0.0014157 secs] 第一次GC前[B@1973e9b 第一次GC前[B@1973e9b [Full GC (System.gc()) 103723K->103456K(118340K), 0.0016463 secs] 第一次GC后[B@1973e9b 第一次GC后[B@1973e9b [Full GC (System.gc()) 105601K->1056K(198016K), 0.0012771 secs] 第二次GC后null 第二次GC后null
虚引用
虚引用,顾名思义是虚幻的,虚引用的对象并不能在 get 的时候获取到它。它也在我们日常开发中没有适用的场景,它的主要作用是用来跟踪一个对象的生命周期 (通常来说是直接内存 [JDK1.5 Java 中除了由 JVM 管理的空间,还可以在内存中直接分配对象]中的对象),一般使用在 JVM 的开发中,主要用来管理直接内存,因为直接内存通常 GC 无法管理这一块内存(C++ delete 完事),需要特殊处理。
例如 NIO 的 ByteBuffer.allocateDirect(1024); 分配内存到直接内存空间中,通常来说从网卡中读取的数据,由操作系统读取到直接内存中,在需要使用的时候,需要拷贝到 JVM 堆空间中,如果不使用 allocateDirect 就需要一个拷贝的过程,这是非常消耗时间的,
// |-- ---| | --------| |------------|
// | 网卡 | ==> | 直接内存 | == copy ==> | JVM 堆空间 |
// |--- --| | ------- | |------------|
使用直接存内存省略了拷贝的过程,俗称 nio 的 zero copy,但是直接内存中的对象在不需要使用的时候无法通过正常 GC 过程去管理这一块空间,所以用到了虚引用,
解释:
虚引用需要配合一个事件队列一起使用,JVM GC 的时候并不是说把虚引用的引用清理掉完事,而是说会把虚引用的引用放到事件队列当中,垃圾回收线程会时不时的去检查这个事件队列,看一下引用的回收过程需不需要做一些后续善后处理(例如清理直接内存中的对象,这玩意儿由实现人去弄)这就是虚引用的作用和含义了。
/** * 虚引用:可以通过队列跟踪一个对象的生命周期,一般在写 JVM 相关的时候才会用到虚引用,主要用来管理直接内存(C++ delete 一下子完事) * -Xmx20m -XX:+PrintGC * Created by etfox on 2021/03/03 12:14 **/ public class TestPhantomReference { private static final List<Object> LIST = new LinkedList<>(); private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>(); public static void main(String[] args) throws InterruptedException { final PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE); // 永远都会返回 null System.out.println(phantomReference.get()); // ByteBuffer.allocateDirect(1024); 分配内存到操作系统的空间,直接内存的空间, JDK 1.5 // 通常从网卡读取的数据,通常由系统读取到直接内存里面,如果想用,需要拷贝到 JVM 堆空间里 // 如果不使用 allocateDirect(直接内存) 就需要一个拷贝的过程,是非常消耗时间的 // |-- ---| | --------| |------------| // | 网卡 | ==> | 直接内存 | == copy ==> | JVM 堆空间 | // |--- --| | ------- | |------------| // 使用直接内存省略了拷贝的过程,俗称 nio zero copy // 但是直接内存中的对象在不再需要的时候无法由 JVM 去 GC 清理内存,所以用到了虚引用 // 虚引用需要和一个队列一起使用,JVM GC 时并不是说会把虚引用的引用清理,而是说会把虚引用的引用放到事件队列中 // 垃圾回收线程可以时不时的检查这个事件队列,看一下这个引用的回收过程需不需要做一些善后处理(例如清理直接内存的那个对象) // 这就是虚引用的作用和含义 ByteBuffer b = ByteBuffer.allocateDirect(1024); new Thread(new Runnable() { @Override public void run() { while (true) { LIST.add(new byte[1024 * 1024]); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(phantomReference.get()); } } }).start(); // 模拟垃圾回收线程 new Thread(new Runnable() { @Override public void run() { while (true) { Reference<? extends M> poll = QUEUE.poll(); if (poll != null) { System.out.println("虚引用对象被 JVM 回收了 " + poll); } } } }).start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } public static class M { @Override protected void finalize() throws Throwable { System.out.println("当对象将被回收时, GC 会调用当前方法"); } } } console==> null [GC (Allocation Failure) 3493K->1061K(15872K), 0.0017311 secs] 当对象将被回收时, GC 会调用当前方法 null null null [GC (Allocation Failure) 4483K->4132K(15872K), 0.0020184 secs] null null null null [GC (Allocation Failure) 8299K->8228K(15872K), 0.0017350 secs] null null null null [GC (Allocation Failure) 12401K->12324K(17928K), 0.0016853 secs] [Full GC (Allocation Failure) 12324K->12324K(17928K), 0.0011354 secs] 虚引用对象被 JVM 回收了 java.lang.ref.PhantomReference@d5fbc1 null null null null null [Full GC (Allocation Failure) 17599K->17445K(19840K), 0.0019903 secs] null [Full GC (Allocation Failure) 18524K->18469K(19840K), 0.0011629 secs] [Full GC (Allocation Failure) 18469K->18232K(19840K), 0.0022320 secs] Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space at com.pangu.TestPhantomReference$1.run(TestPhantomReference.java:41) at java.lang.Thread.run(Thread.java:748)
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注猪先飞的更多内容!
原文出处:https://blog.csdn.net/qq_29689487/article/details/114310375
相关文章
- 这篇文章主要介绍了如何利用java语言实现经典《复杂迷宫》游戏,文中采用了swing技术进行了界面化处理,感兴趣的小伙伴可以动手试一试...2022-02-01
java 运行报错has been compiled by a more recent version of the Java Runtime
java 运行报错has been compiled by a more recent version of the Java Runtime (class file version 54.0)...2021-04-01- 这篇文章主要介绍了在java中获取List集合中最大的日期时间操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
- 这篇文章主要介绍了教你怎么用Java获取国家法定节假日,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下...2021-04-23
- 这篇文章主要介绍了Java如何发起http请求的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-31
- 说起C#和Java这两门语言(语法,数据类型 等),个人以为,大概有90%以上的相似,甚至可以认为几乎一样。但是在工作中,我也发现了一些细微的差别...2020-06-25
- 这篇文章主要介绍了解决Java处理HTTP请求超时的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-29
- 这篇文章主要介绍了java 判断两个时间段是否重叠的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
java 画pdf用itext调整表格宽度、自定义各个列宽的方法
这篇文章主要介绍了java 画pdf用itext调整表格宽度、自定义各个列宽的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-31- 这篇文章主要介绍了超简洁java实现双色球若干注随机号码生成(实例代码),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-02
- 这篇文章主要介绍了Java生成随机姓名、性别和年龄的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-01
- 这篇文章主要介绍了java正则表达式判断前端参数修改表中另一个字段的值,需要的朋友可以参考下...2021-05-07
Java使用ScriptEngine动态执行代码(附Java几种动态执行代码比较)
这篇文章主要介绍了Java使用ScriptEngine动态执行代码,并且分享Java几种动态执行代码比较,需要的朋友可以参考下...2021-04-15- 这篇文章主要介绍了Java开发实现人机猜拳游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-08-03
Java 8 Stream 的终极技巧——Collectors 功能与操作方法详解
这篇文章主要介绍了Java 8 Stream Collectors 功能与操作方法,结合实例形式详细分析了Java 8 Stream Collectors 功能、操作方法及相关注意事项,需要的朋友可以参考下...2020-05-20- 这篇文章主要介绍了Java List集合返回值去掉中括号('[ ]')的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-29
Java中lombok的@Builder注解的解析与简单使用详解
这篇文章主要介绍了Java中lombok的@Builder注解的解析与简单使用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-01-06- 下面小编就为大家带来一篇java中String类型变量的赋值问题介绍。小编觉得挺不错的。现在分享给大家,给大家一个参考。...2016-03-28
- 这篇文章主要介绍了Java线程池中的各个参数如何合理设置操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-06-19
- 虽然C++11引入了智能指针的,但是开发人员在与内存的斗争问题上并没有解放,如果我门实用不当仍然有内存泄漏问题,其中智能指针的循环引用缺陷是最大的问题。下面通过实例代码给大家介绍c++中的循环引用,一起看看吧...2020-04-25