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. Python_opencv库

    1.车牌检测 ''' 项目名称:opencv/cv2 车牌检测 简介: 1.训练级联表 ***.xml [跳过...] 2.用如下代码加载级联表和目标图片识别车牌 注:推荐用anconda安装open ...

  2. Linux_CentOS 7下Nginx服务器的安装配置

    1.安装 1.1 配置epel yum 源 wget http://dl.Fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm rpm ...

  3. GreenDao增删改查

    3.GreenDao增删改查 (1)插入 常用API //这是最简单的插入语句,新增一行数据,返回值为行号 public long insert(T entity) //传递一个数组,新增多行数据 p ...

  4. Hibernate初识

    1. 持久化框架 狭义的概念:数据存储在物理存储介质不会丢失. 广义的概念:对数据的crud操作都叫持久化. 加载:hibernate的概念,数据从数据库中加载到session. 2. ORM(obj ...

  5. MySQL第01课- CentOS + 单实例MySql编译安装总结

    2016年2月,从oracle转向MySql ,碰上几个坑,特此记录 总结 1.注意环境变量.配置文件,操作过程不能出错 2.相比rpm方式安装,编译安装方式可以指定安装路径,再说安装是简单活,将来安 ...

  6. ci爬坑

    1.row_array() 问题描述:没有数据返回NULL,直接foreach,报错

  7. Navicat总是提示主键不存在问题

    Windows 和 Linux:打开navicat > 找到工具 > 点击选项- > 外观 > 点击数据 & 网格 > 取消勾选显示主键警告 > 确定. M ...

  8. First day,beginning!

    beginning 在闲暇的时光记录当下的生活,一直是自己所期盼的: 由于种种原因(懒惰),一直未能开始,那么就从今天开始吧! 看下日期,从实习到现在一个月刚刚好: 公司很不错,师傅特别好,感觉自己是 ...

  9. windbg 分析cpu异常

    1.   !threadpool  查看当前CPU状况 线程数等等 2.   !runaway 查看那几个线程使用的高 建议多抓几个dump 然后确定到底是哪个线程 3.   ~线程IDs 跳转到那个 ...

  10. Java8用了这么久了,Stream 流用法及语法你都知道吗?

    1.简介 Stream流 最全的用法Stream 能用来干什么?用来处理集合,通过 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询,Stream API 提供了一 ...