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. 在Docker上部署自动更新ssl证书的nginx + .NET CORE

    突发奇想要搞一个ssl的服务器,然后我就打起了docker的主意,想着能不能搞一个基于Docker的服务器,这样维护起来也方便一点. 设想 想法是满足这么几点: .NET CORE on Docker ...

  2. minishell的实现

    直接上各个模块的代码,注释都在文档代码中,非常详细,加上最后的Makefile文件完全可以自行运行看懂: main函数一个文件main.c 1 /* 2 minishell实现的功能:简单命令解析.管 ...

  3. spring boot实现超轻量级网关(反向代理、转发)

    在我们的rest服务中,需要暴露一个中间件的接口给用户,但是需要经过rest服务的认证,这是典型的网关使用场景.可以引入网关组件来搞定,但是引入zuul等中间件会增加系统复杂性,这里实现一个超轻量级的 ...

  4. java实现 阿拉伯数字转换为汉字数字(转载)

    public class VedioExtractSpeech { public static void main(String[] args) { System.out.println(" ...

  5. python之路《五》字符串的操作

    python的里的字符串的操作是可以说是最常见也是最实用的 我们通常使用双引号来表示字符串" "创建字符串很简单,定义一个变量就可以了 1 name = 'my name \t i ...

  6. ssh2中的添,删,查,改。

    1.spring封装的HibernateTemplate类的一些操作方法. 2.session提供的根据主键ID进行添.删.查.改的基本方法. 由session得到的hql语句 由session得到的 ...

  7. ubuntu12.10安装sun java jdk

    add-apt-repository ppa:webupd8team/java apt-get update apt-get install oracle-java6-installer root@k ...

  8. rgw配置删除快速回收对象

    前言 做rgw测试的时候,经常会有删除文件的操作,而用默认的参数的时候,rgw是通过gc回收机制来处理删除对象的,这个对于生产环境是有好处的,把删除对业务系统的压力分摊到不同的时间点,但是测试的时候, ...

  9. C++中内存布局 以及自由存储区和堆的理解

    文章搬运自https://www.cnblogs.com/QG-whz/p/5060894.html,如有侵权请告知删除 当我问你C++的内存布局时,你大概会回答: "在C++中,内存区分为 ...

  10. 如何合理的安排Folx的下载任务

    搭配使用Folx专业版的智能速控与任务计划功能,用户可以实现更加自动化.智能化的下载功能.通过使用任务计划功能,用户以时间表的方式安排Folx的下载任务:而智能速控的功能又能确保用户在下载的同时,有足 ...