背景

工程中用到guava的本地缓存。它底层实现和API接口上使用了强引用、软引用、弱引用。所以温故知新下,也夯实下基础。

预备知识

先来看下GC日志每个字段的含义

Young GC示例解释

[GC (Allocation Failure) [PSYoungGen: 273405K->20968K(278016K)] 480289K->473619K(737792K), 0.1090103 secs] [Times: user=0.19 sys=0.27, real=0.11 secs]

解释

[GC(产生GC的原因,例子中是由于分配内存失败)  [PSYoungGen: 年轻代回收前空间->年轻代回收后空间(年轻代总空间)] 堆区的回收前空间->堆区的回收后空间(堆区的总空间), GC耗时] [Times: 用户空间耗时 系统空间耗时, 实际耗时]

Full GC示例解释 

[Full GC (Ergonomics) [PSYoungGen: 20968K->20805K(278016K)] [ParOldGen: 452651K->451654K(864256K)] 473619K->472460K(1142272K), [Metaspace: 5793K->5793K(1056768K)], 0.1565987 secs] [Times: user=0.70 sys=0.00, real=0.16 secs]

解释

[Full GC (产生GC原因,例子中是由于要放入老年代的对象超过了老年代的剩余空间) [PSYoungGen: ->年轻代回收前空间->年轻代回收后空间(年轻代总空间)] [ParOldGen: 老年代回收前空间->老年代回收后空间(老年代总空间)] 堆区的回收前空间->堆区的回收后空间(堆区的总空间), [Metaspace: 元空间的回收前空间->元空间的回收后空间(元空间的总空间)],  GC耗时] [Times: 用户空间耗时 系统空间耗时, 实际耗时]

创建一个10M的大对象,重写finalize方法。finalize()方法会在对象被回收前调用,一个对象只有一次被调用的机会。对象可以在这个方法里进行自救,逃过被垃圾回收。Java设计这个方法可以被覆写是为了让有些对象在回收前做一些检查,完成一些前置条件再被垃圾回收。正式代码不建议使用。因为是测试,所以为了验证效果,这里打印GC日志信息。

   byte[] bytes = new byte[10 * 1024 * 1024];
int index;
public Ref(int index) {
this.index = index;
} public byte[] getBytes() {
return bytes;
} @Override
public void finalize() {
System.out.println("index " + index + "'s " + bytes.length + "is going to be GG");
}
}

为了测试,JVM参数统一为-Xms20M -XX:+PrintGCDetails。Xms20M表示堆内存设置最大为20M,-XX:+PrintGCDetails代表打印详细的GC信息。

强引用

先来做个实验(代码已经上传github:https://github.com/xiexiaojing/yuna)

@Test
public void testRawStrong() {
List<Ref> list = Lists.newArrayList();
for(int i=0; i<100; i++) {
list.add(new Ref(i));
System.out.println(list.get(i));
}
}

这段代码由上线的设置可知,由于最大设置20M堆空间,所以很快触发了GC。

不过Xmx这个值是建议内存最大使用值。如果内存使用超过这个值,jvm认为还有内存可以使用,也会将对象一直往堆里面放。所以2次GC之后JVM自动扩容了,之后就不再频繁GC。最终用到了满足程序需要的内存。

强引用是直接new出来调用的对象,大家都知道。由上面实验可知,在系统内存很富裕的情况下,因为强引用内存不能被释放,所以会多申请了很多内存。

软引用

软引用会在系统将要发生内存溢出异常之前,将会把这些软引用对象列进回收范围进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。

用实验说明一下,为了防止JVM自动调整堆大小,我们把堆设置-Xmx200M。

@Test
public void testRawSoft() {
List<SoftReference<Ref>> list = Lists.newArrayList();
for(int i=0; i<100; i++) {
list.add(new SoftReference<>(new Ref(i)));
System.out.println(list.get(i).get());
}
}

从下面实验结果可以看到数次的 GC之后,内存要撑不住的时候,Ref的软引用对象触发了finalize方法。这意味着它将要被内存回收了。说明GC会引发软引用里对象的内存回收,即使这个软引用本身还被强引用(list调用)着。

最终回收了这些内存也不能避免OOM的结局:

因为软引用通常情况下就是这样,只有内存马上要溢出了才触发它的GC。就好像扁鹊见蔡桓公的时候,蔡桓公的病已经很深了,马上就没救了。所以有了下面弱引用的方法:有病早治。

弱引用

弱引用是发生了一次垃圾回收后,既存的弱引用对象就开始回收。通常,一个弱引用对象仅能生存到下一次垃圾回收前。

用实验说明一下,为了防止JVM自动调整堆大小,我们把堆设置-Xmx200M。

@Test
public void testRawWeak() {
List<WeakReference<Ref>> list = Lists.newArrayList();
for(int i=0; i<100; i++) {
list.add(new WeakReference<>(new Ref(i)));
System.out.println(list.get(i).get());
}
}

从下面的实验结果可知在发生了一次GC之后,已经生成的软引用对象都都回收了。下一次GC,这中间产生的软引用对象也都被回收了。

最终,由于GC及时,整个过程没有爆发OOM,平安的结束了。

虚引用

虚引用也叫幻影引用。任何时候可能被GC回收,就像没有引用一样。

并且他必须和引用队列一起使用,用于跟踪垃圾回收过程,当垃圾回收器回收一个持有虚引用的对象时,在回收对象后,将这个虚引用对象加入到引用队列中,用来通知应用程序垃圾的回收情况。

先来实验一下,从下面结果可看到从一开始取出来就是空对象,基本上刚创建出来就被回收了。

一个像是从来没有存在过的幻影有什么用呢?Java的Unsafe类和NIO都可以直接访问堆外内存。堆外内存GC管不了,这时候虚引用就排上用场了。我们可以通过引用队列跟踪垃圾回收,做好善后。

在Guava中使用强软弱引用

@Test
public void testStrong() {
Cache<Integer, Ref> cache = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.DAYS).build();
for(int i=0; i<100; i++) {
cache.put(i, new Ref(i));
System.out.println(cache.getIfPresent(i));
}
System.out.println(cache.stats().loadSuccessCount());
} @Test
public void testSoft() {
Cache<Integer, Ref> cache = CacheBuilder.newBuilder().softValues().build();
for(int i=0; i<100; i++) {
cache.put(i, new Ref(i));
System.out.println(cache.getIfPresent(i));
}
} @Test
public void testWeak() {
Cache<Integer, Ref> cache = CacheBuilder.newBuilder().weakKeys().weakValues().build();
for(int i=0; i<100; i++) {
cache.put(i, new Ref(i));
System.out.println(cache.getIfPresent(i));
}
}

Guava在没有显示设置强、软、弱引用的情况下默认是强引用。这个结论我没有看任何书,而是通过跟踪源码,debug得到的结论。当显示设置为软引用或者弱引用时,运行时GC触发和对象回收之间的关系和自己手动直接测试的结果是一样的,大家可以动手实践下。

总结

Java的强软弱虚引用被回收的时机不同:强引用是引用被释放才会回收;软引用是没释放,但是快OOM了就会被回收;弱引用是引用没释放,但是发生了GC后就会被回收;虚引用随时会回收,好像没有存在过,但是会有一个队列来跟踪它的垃圾回收情况。

相关阅读

Java异步的2种方式分析

关于Java两点需要更新的知识

阿里巴巴编码规范(Java)证明

Java的强引用、软引用、弱引用、虚引用的更多相关文章

  1. Java中强、软、弱、虚引用

    1.强引用(StrongReference) 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器绝不会回收它.当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使 ...

  2. 0030 Java学习笔记-面向对象-垃圾回收、(强、软、弱、虚)引用

    垃圾回收特点 垃圾:程序运行过程中,会为对象.数组等分配内存,运行过程中或结束后,这些对象可能就没用了,没有变量再指向它们,这时候,它们就成了垃圾,等着垃圾回收程序的回收再利用 Java的垃圾回收机制 ...

  3. Java:对象的强、软、弱、虚引用

    转自: http://zhangjunhd.blog.51cto.com/113473/53092 1.对象的强.软.弱和虚引用 在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无 ...

  4. Java中四种引用:强、软、弱、虚引用

    这篇文章非常棒:http://alinazh.blog.51cto.com/5459270/1276173 Java中四种引用:强.软.弱.虚引用 1.1.强引用当我们使用new 这个关键字创建对象时 ...

  5. Java:对象的强、软、弱和虚引用

    1.对象的强.软.弱和虚引用 在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象.也就是说,只有对象处于可触及(reachable)状态,程序才能使用它.从JDK ...

  6. Java对象的强、软、弱和虚引用详解

    1.对象的强.软.弱和虚引用 转自:http://zhangjunhd.blog.51cto.com/113473/53092/ 在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无 ...

  7. java基础知识再学习--集合框架-对象的强、软、弱和虚引用

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://zhangjunhd.blog.51cto.com/113473/53092 本文 ...

  8. Java对象的强、软、弱和虚引用原理+结合ReferenceQueue对象构造Java对象的高速缓存器

    //转 http://blog.csdn.net/lyfi01/article/details/6415726 1.Java对象的强.软.弱和虚引用 在JDK 1.2以前的版本中,若一个对象不被任何变 ...

  9. Java:对象的强、软、弱和虚引用[转]

    原文链接:http://zhangjunhd.blog.51cto.com/113473/53092/ 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法 ...

  10. Java对象的强、软、弱和虚引用+ReferenceQueue

    Java对象的强.软.弱和虚引用+ReferenceQueue 一.强引用(StrongReference) 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器绝不会回收它.当内存空间不足 ...

随机推荐

  1. MeteoInfoLab脚本示例:MERRA HDF数据

    MERRA是NOAA的一种再分析资料,HDF数据遵循COARDS协议,读取比较简单.脚本程序: #Add data file folder = 'D:/Temp/hdf/' fns = 'MERRA3 ...

  2. python 爬取链家

    import json import requests from lxml import etree from time import sleep url = "https://sz.lia ...

  3. Linux关机时执行指定脚本

    要实现在Linux关机时执行某个脚本的具体思路 1.在文件夹 /etc/init.d/ 下创建关机时需要执行的脚本 file_name; 2.分别在文件夹 /etc/rc0.d/ 和 /etc/rc6 ...

  4. 第三十六章 Linux常用性能检测的指令

    作为一个Linux运维人员,介绍下常用的性能检测指令! 一.uptime 命令返回的信息: 19:08:17              //系统当前时间 up 127 days,  3:00     ...

  5. CentOS 网卡固定地址配置

    修改4个文件后重启网卡 vim /etc/default/grub GRUB_CMDLINE_LINUX="resume=UUID=05dbb36b-dbba-40a3-ba99-1b044 ...

  6. Activity去掉标题不成功的解决方法

    在设置Activity去掉标题的时候遇到的问题,记录一下. 一般会有以下两种方式: 1.Activity中设置 this.requestWindowFeature(Window.FEATURE_NO_ ...

  7. 微信小程序学习心得

    我们写小程序时都要跳转页面的,也会有底部导航来进行切换 这个时候就要介绍下窗口是怎样配置的 要在app.json文件里写一个tabBer对象 里面在定义一个list数组里面放我们定义的几个需要切换的页 ...

  8. javaScript 必会基础知识

    1.JavaScript是一种浏览器解析的轻量级脚本语言. 2.html.jsp等内部js代码写在<script></script>之间:外部js文件中书写js代码不能有< ...

  9. 【设计模式】第一篇:概述、耦合、UML、七大原则,详细分析总结(基于Java)

    迷茫了一周,一段时间重复的 CRUD ,着实让我有点烦闷,最近打算将这些技术栈系列的文章先暂时搁置一下,开启一个新的篇章<设计模式>,毕竟前面写了不少 "武功招式" 的 ...

  10. Yum 命令出现[Errno 256] No more mirrors to try错误的解决方式

    今天我在虚拟机上安装 NetCore 的 SDK 的时候,出现错误,执行命令:"yum install dotnet-sdk-3.1",最后安装失败,很多安装包没有找到镜像.解决方 ...