Java 2 平台引入了 java.lang.ref 包,这个包下面包含了几个Reference相关的类,Reference相关类将Java中的引用也映射成一个对象,这些类还提供了与垃圾收集器(garbage collector)之间有限的交互。

Reference引用类的几种类型

在jvm中,一个对象如果不再被使用就会被当做垃圾给回收掉,判断一个对象是否是垃圾,通常有两种方法:引用计数法和可达性分析法。不管是哪一种方法判断一个对象是否是垃圾的条件总是一个对象的引用是都没有了。

JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用、软引用、弱引用、虚引用4 种。下面就介绍下这些引用类型的区别。

强引用

如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。下面的代码中str就是一个强引用。

public void test1(){
String str = new String("程序员自由之路");
}

软引用(SoftReference)

内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。

上面只是很简单的说了下:当系统没有足够的内存时会回收软引用对象。但是具体什么才是内存不够?具体的回收具体是什么?如果想要了解具体的情况,大家可以参考这篇文章。我简单总结了下,软引用对象具体的回收策略如下:

如果已经没有引用指向软引用对象,那么这个对象会被JVM回收;

如果还有软引用指向这个软引用对象,就判断在某段之间之内(_max_interval),有没有调用过SoftReference的get方法,如果在_max_interval时间内没调用过get方法,那么即使还有软引用指向这个对象,JVM也会回收这个对象,如果在_max_interval时间内调用过get方法,那么就不会回收这个对象。

_max_interval具体的时间是根据JVM的可用内存动态计算出来的,如果JVM的可用内存比较大,那么_max_interval的值也比较大,如果JVM的可用内存比较小,那么max_interval也会比较小。

我自己写了一段代码来展示软引用对象回收的过程。为了让堆内存迅速耗尽,我将最大内存设置为-Xmx5m。

public static void main(String[] args) throws InterruptedException {
SoftReference<String> reference = new SoftReference<>(new String("自由之路..."));
List<String> list = new ArrayList<>();
while (true) {
for (int i = 0; i < 10000; i++) {
// 这边的对象都是强引用,不会被回收
list.add(new String("自由之路"));
}
// 暂停一段时间,为了让_max_interval时间段检测生效
// 没有这段暂停的话,JVM不会回收软引用对象,因为一直有线程在快速地调用软引用的get方法
TimeUnit.MILLISECONDS.sleep(10);
String s = reference.get();
if (s == null) {
logger.info("OMG, reference is gone...");
}else {
logger.info(s);
}
}
}

代码的执行效果,如下:

13:36:52.322 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - 自由之路...
13:36:52.372 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - 自由之路...
13:36:52.385 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - 自由之路...
13:36:52.397 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - 自由之路...
13:36:52.412 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - 自由之路...
13:36:52.423 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - 自由之路...
13:36:52.435 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - 自由之路...
13:36:52.488 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - 自由之路...
13:36:52.499 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - 自由之路...
13:36:52.555 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - 自由之路...
// 从下面开始,软引用对象已经被虚拟机回收了。
13:36:52.666 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - OMG, reference is gone...
13:36:54.750 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - OMG, reference is gone...
13:36:58.686 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - OMG, reference is gone...
// 系统已经不能再分配出内存空间,直接报OutOfMemoryError
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at com.csx.demo.spring.boot.dao.UserMapperTest.main(UserMapperTest.java:54)

弱引用(WeakReference)

如果一个对象具有弱引用,在垃圾回收时候,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。

关于弱引用,我也写了个Bug代码,展示弱引用对象的回收过程。

public static void main(String[] args) throws InterruptedException {
WeakReference<String> reference = new WeakReference<>(new String("自由之路..."));
List<String> list = new ArrayList<>();
while (true) {
list.add(new String("自由之路"));
String s = reference.get();
if (s == null) {
logger.info("OMG, reference is gone...");
} else {
logger.info(s);
}
}
}

代码的执行结果如下:

13:50:54.015 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - 自由之路...
13:50:54.015 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - 自由之路...
13:50:54.015 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - 自由之路...
13:50:54.015 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - 自由之路...
// 这边GC已经将弱引用对象回收
13:50:54.051 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - OMG, reference is gone...
13:50:54.051 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - OMG, reference is gone...
13:50:54.051 [main] INFO com.csx.demo.spring.boot.dao.UserMapperTest - OMG, reference is gone...

关于WeakReference,Java中一个比较典型的应用就是:WeakHashMap。关于这个类的使用情况大家可以参考这篇文章

虚引用(PhantomReference)

虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。虚引用是使用PhantomReference创建的引用,虚引用也称为幽灵引用或者幻影引用,是所有引用类型中最弱的一个。一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用获得一个对象实例。

使用虚引用的目的就是为了得知对象被GC的时机,所以可以利用虚引用来进行销毁前的一些操作,比如说资源释放等。这个虚引用对于对象而言完全是无感知的,有没有完全一样,但是对于虚引用的使用者而言,就像是待观察的对象的把脉线,可以通过它来观察对象是否已经被回收,从而进行相应的处理。

在<<深入理解Java虚拟机>>3.2.3中有这么一句话

为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动

public class Test {
public static boolean isRun = true; @SuppressWarnings("static-access")
public static void main(String[] args) throws Exception {
String abc = new String("abc");
System.out.println(abc.getClass() + "@" + abc.hashCode());
final ReferenceQueue<String> referenceQueue = new ReferenceQueue<String>();
new Thread() {
public void run() {
while (isRun) {
Object obj = referenceQueue.poll();
if (obj != null) {
try {
Field rereferent = Reference.class
.getDeclaredField("referent");
rereferent.setAccessible(true);
Object result = rereferent.get(obj);
System.out.println("gc will collect:"
+ result.getClass() + "@"
+ result.hashCode() + "\t"
+ (String) result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}.start();
PhantomReference<String> abcWeakRef = new PhantomReference<String>(abc,
referenceQueue);
abc = null;
Thread.currentThread().sleep(3000);
System.gc();
Thread.currentThread().sleep(3000);
isRun = false;
}
}

一个线程一直再检测回收队列中有没有被回收的引用。如果有被回收的引用,进行一些操作。

引用队列(ReferenceQueue)

作为一个Java对象,SoftReference对象除了具有保存软引用的特殊性之外,也具有Java对象的一般性。所以,当软可及对象被回收之后,虽然这个SoftReference对象的get()方法返回null,但这个SoftReference对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量SoftReference对象带来的内存泄漏。在java.lang.ref包里还提供了ReferenceQueue。如果在创建SoftReference对象的时候,使用了一个ReferenceQueue对象作为参数提供给SoftReference的构造方法:

ReferenceQueue queue = new ReferenceQueue();
SoftReference ref = new SoftReference(object, queue);

那么当这个SoftReference所软引用的对象被垃圾收集器回收的同时,ref所强引用的SoftReference对象被列入ReferenceQueue。也就是说,ReferenceQueue中保存的对象是Reference对象,而且是已经失去了它所软引用的对象的Reference对象。另外从ReferenceQueue这个名字也可以看出,它是一个队列,当我们调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。

在任何时候,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,我们可以检查哪个SoftReference所软引用的对象已经被回收,于是我们可以把这些失去所软引用的对象的SoftReference对象清除掉。


SoftReference ref = null;
while ((ref = (EmployeeRef) q.poll()) != null) {
// 清除ref
}

参考

Java中的Reference类使用的更多相关文章

  1. 带有静态方法的类(java中的math类)

    带有静态方法的类通常(虽然不一定是这样)不打算被初始化. 可以用私有构造函数来限制非抽象类被初始化. 例如,java中的math类.它让构造函数标记为私有,所以你无法创建Math的实例.但Math类却 ...

  2. java中的File类

    File类 java中的File类其实和文件并没有多大关系,它更像一个对文件路径描述的类.它即可以代表某个路径下的特定文件,也可以用来表示该路径的下的所有文件,所以我们不要被它的表象所迷惑.对文件的真 ...

  3. Java基础(43):Java中的Object类与其方法(转)

    Object类 java.lang.Object java.lang包在使用的时候无需显示导入,编译时由编译器自动导入. Object类是类层次结构的根,Java中所有的类从根本上都继承自这个类. O ...

  4. java中基于TaskEngine类封装实现定时任务

    主要包括如下几个类: 文章标题:java中基于TaskEngine类封装实现定时任务 文章地址: http://blog.csdn.net/5iasp/article/details/10950529 ...

  5. Java中的Unsafe类111

    1.Unsafe类介绍 Unsafe类是在sun.misc包下,不属于Java标准.但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty.Hadoo ...

  6. Java中遍历实体类(处理MongoDB)

    在实际过程中,经常要将实体类进行封装,尤其是处理数据库的过程中:因此,对于遍历实体类能够与数据库中的一行数据对应起来. 我是使用的环境是Spring boot,访问的数据库时MongoDB 实体类遍历 ...

  7. java中遍历实体类,获取属性名和属性值

    方式一(实体类): //java中遍历实体类,获取属性名和属性值 public static void testReflect(Object model) throws Exception{ for ...

  8. Java中的BigDecimal类精度问题

    bigdecimal 能保证精度的原理是:BigDecimal的解决方案就是,不使用二进制,而是使用十进制(BigInteger)+小数点位置(scale)来表示小数,就是把所有的小数变成整数,记录小 ...

  9. java 中常用的类

    java 中常用的类 Math Math 类,包含用于执行基本数学运算的方法 常用API 取整 l  static double abs(double  a) 获取double 的绝对值 l  sta ...

随机推荐

  1. 论文学习笔记 - 高光谱 和 LiDAR 融合分类合集

    A³CLNN: Spatial, Spectral and Multiscale Attention ConvLSTM Neural Network for Multisource Remote Se ...

  2. go-zero之web框架

    go-zero 是一个集成了各种工程实践的 web 和 rpc 框架,其中rest是web框架模块,基于Go语言原生的http包进行构建,是一个轻量的,高性能的,功能完整的,简单易用的web框架 服务 ...

  3. ceph luminous 新功能之内置dashboard

    前言 ceph luminous版本新增加了很多有意思的功能,这个也是一个长期支持版本,所以这些新功能的特性还是很值得期待的,从底层的存储改造,消息方式的改变,以及一些之前未实现的功能的完成,都让ce ...

  4. Ceph删除OSD上一个异常object

    前言 ceph里面的数据是以对象的形式存储在OSD当中的,有的时候因为磁盘的损坏或者其它的一些特殊情况,会引起集群当中的某一个对象的异常,那么我们需要对这个对象进行处理 在对象损坏的情况下,启动OSD ...

  5. Java编码和字符集(详解)

    [1]什么是编码? [2]通过生活案例: [3]由权威机构形成的编码表才可以称之为:字符集 ASCII 英文字符集 用一个字节的7位表示 IOS8859-1 西欧字符集 用一个字节的8位表示 GB23 ...

  6. 统计数字问题(Java)

    Description 一本书的页码从自然数1 开始顺序编码直到自然数n.书的页码按照通常的习惯编排,每个页码都不含多余的前导数字0.例如,第6 页用数字6 表示,而不是06 或006 等.数字计数问 ...

  7. linux中使用head,tail,grep, sed,awk三种方法显示文档中间若干行(指定任意行)

    需要显示文本中间20-25行. 创建一个30行的文档,命名为30.txt并显示在屏幕 [root@v2-ui data]# seq 30 > 30.txt && cat 30.t ...

  8. 《Machine Learning in Action》—— 懂的都懂,不懂的也能懂。非线性支持向量机

    说在前面:前几天,公众号不是给大家推送了第二篇关于决策树的文章嘛.阅读过的读者应该会发现,在最后排版已经有点乱套了.真的很抱歉,也不知道咋回事,到了后期Markdown格式文件的内容就解析出现问题了, ...

  9. 移动端调试Web页面

    移动端调试Web页面 虽然可以在PC下,通过开发者工具,模拟移动端,但是这样只能模拟页面样式,对于代码的执行情况是无法模拟的,所以在此结合实际调试经验,针对安卓与IOS设备,进行总结. IOS 安卓 ...

  10. 在IDM上设置防止过度抓取网站信息

    在使用Internet Download Manager(IDM)下载器时,有时会发现IDM自带的抓取功能过于强大,以至于有时会抓取一些无效的链接.那么,该如何避免IDM的过度抓取呢? 图1:IDM的 ...