java中的Reference类型用法说明

本文简要总结java中的Reference类型。

最近在研读jdk并发框架,其中AQS是重点,由于我打破砂锅问到底的轻微强迫症,google了AQS作者Doug Lea的论文原文[The java.util.concurrent Synchronizer Framework],有兴趣的同学可以自行下载。其中谈到设计同步框架的核心是选择一个严格意义上的FIFO队列,作为阻塞线程队列并对其进行维护。

对此主要由两种选择,一个是MCS锁,另一个时CLH锁。因为CLH锁比MCS对取消和超时的处理更方便,所以AQS就选择将CLH锁作为基础对其进行改进。于是我又打算先弄懂什么是CLH锁,在网上搜索了一圈之后找到很多人对CLH锁进行了java实现,实现中用到了ThreadLocal类型,于是我发现我好像对ThreadLocal也不太熟,于是去看openjdk的源码,又发现ThreadLocal的内部类ThreadLocalMap中的Entry是继承自WeakReference,好了,既然这几个我好像都没弄得很明白过,所以我决定先了解一下Reference。

在我们平时开发过程中很少会遇到需要与各种不同类型的reference打交道的时候,所以很多时候我们在自己写的代码中很少会碰到需要使用不同的reference类型,可能很多人也会向我这样,想要深入学习一下jdk源码或者其他某些框架的源码的时候才会看到诸如WeakReference这样的类型。

问题

假设在一个应用中,需要从一个名为test的数据库表中获取数据。但凡有点经验的开发人员都会避免应用获取相同的数据每次都去查询数据库,因为I/O操作过去频繁势必会降低应用性能。

显然,我们首先想到的就是使用缓存。应用首先查询缓存,如果需要的数据存在直接拿来用就好;如果缓存未命中,才去数据库查询,并且把查询到的数据放入缓存,以便下次应用发起相同请求时可以直接从缓存获取数据而不用再次去数据库查询。

使用缓存会提高性能吗?

答案是这需要根据具体情况分析,如果从test获取需要缓存的数据量较少,使用缓存会非常合适且一定会提升性能。但假若需要从test表查询放到缓存里的数据量非常大,那就会出现一个问题:由于数据量过大可能会导致内存不足,而不单单是提升性能了。假如说把表中所有数据都放入缓存,那么缓存的可能会占据大部分jvm的内存或者索性直接产生一个OOM错误。

解决方案

最佳的方案是如果我们可以创造一种可以按需扩展和收缩的动态缓存,当我们的数据量需要而内存充裕的时候可以适当增加,但内存不足是可以按不同方案对其进行回收。

目的

这里引出的一个问题,就是为什么要在Java中使用不同类型的reference?我们的应用在运行过程中会产生很多对象,这些对象驻留在内存中,它们大小不同,重要性不同,使用频率不同,生命周期不同,比如有些对象只要应用启动就一直存活直到应用停止,而有些对象生命周期与创建它的线程相同,还有些对象只作临时变量短时间就消亡,再比如某些缓存数据,内存充裕的时候可以存活,内存不足的时候可能需要被首先牺牲被回收,所以很容易想象对于不同的对象,我们希望对他们的创建销毁采取不同的策略,可是不幸的是java不像C一样可以由开发者决定对象的析构销毁,而是将管理内存的活统一交给了jvm进行gc,但jvm显然不知道这些对象的区别。

于是设计者们在java 1.2加入了reference,使jvm可以对不同的reference对象采取不同的回收策略以达到提高应用性能的目的。

java.lang.ref 包

实际上java.lang.ref包中就有以下几种不同的reference类型,分别是:

StrongReference

SoftReference

WeakReference

PhantomReference

FinalReference

StrongReference

我们发现在类图中我们并没有发现 StrongReference 类型,原因是我们平时写的代码基本上都是 StrongReference 。我们最常的创建对象方式就是 new 一个对象,然后将其赋值给一个声明为这个对象的类型及其父类的引用。如果对象有一个 StrongReference ,那么这个对象将不会被gc回收。

举例

HelloWorld hello = new HelloWorld();

这里 hello 就是一个 HelloWorld 对象的 StrongReference。

SoftReference

如果一个对象没有 StrongReference 但存在一个 SoftReference ,那么 gc 将会在虚拟机需要释放一些内存的时候回收这个对象。可以通过对对象的 SoftReference 调用 get() 方法获取该对象。如果这个对象没有被 gc 回收,则返回此对象,否则返回 null 。

WeakReference

如果一个对象没有 StrongReference 但有存在一个 WeakReference ,那么 gc 将会在下一次运行时对其进行回收,哪怕虚拟机的内存还足够多。

PhantomReference 与 FinalReference

如果某个对象没有以上这些类型的引用,那么它可能有一个 PhantomReference 。PhantomReference 不能用于直接访问对象。调用 get() 方法都会返回 null 。

FinalReference 与虚拟机密切相关,这里先挖个坑,下次再具体解析。

对象可达性判断

当前主流java虚拟机都是采用 GC Roots Tracing 算法,比如 Sun 的 Hotspot 虚拟机便是采用该算法。java虚拟机进行gc时,判断一个对象的被引用情况决定是否回收,都是从根节点引用(Root set of Reference)开始标识可达路径的。对于某个对象可能会存在其多个引用,且这多个引用的类型不同。

如下图所示:

Root Tracing 算法根据以下两个原则标记对象的可达性:

单一路径中,以最弱的引用为准

多路径中,以最强的引用为准

如上图所示,对对象4存在3条引用路径:(1)(4),(2)(5),(3)(6)。那么从根对象到对象4的最强引用时(2)(5),因为(2)和(5)都是强引用。如果对象4仅存在一条(1)(4)引用,那么对它的引用就是最弱的引用为准,也就是 SoftReference ,对象4就是 softly-reachable 对象。

不同类型 reference java 代码举例

package com.example.reference;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
public class ReferenceExample {
  private String status ="Hi I am active";
  public String getStatus() {
    return status;
  }
  public void setStatus(String status) {
    this.status = status;
  }
  @Override
  public String toString() {
    return "ReferenceExample [status=" + status + "]";
  }
  public void strongReference()
  {
    ReferenceExample ex = new ReferenceExample();
    System.out.println(ex);
  }
  public void softReference()
  {
    SoftReference<ReferenceExample> ex = new SoftReference<ReferenceExample>(getRefrence());
    System.out.println("Soft refrence :: " + ex.get());
  }
  public void weakReference()
  {
    int counter=0;
    WeakReference<ReferenceExample> ex = new WeakReference<ReferenceExample>(getRefrence());
    while(ex.get()!=null)
    {
      counter++;
      System.gc();
      System.out.println("Weak reference deleted after:: " + counter + ex.get());
    }
  }
  public void phantomReference() throws InterruptedException
  {
    final ReferenceQueue queue = new ReferenceQueue();
    PhantomReference<ReferenceExample> ex = new PhantomReference<ReferenceExample>(getRefrence(),queue);
    System.gc();
    queue.remove();
    System.out.println("Phantom reference deleted after");
  }
  private ReferenceExample getRefrence()
  {
    return new ReferenceExample();
  }
  public static void main(String[] args) {
    ReferenceExample ex = new ReferenceExample();
    ex.strongReference();
    ex.softReference();
    ex.weakReference();
    try {
      ex.phantomReference();
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
Output :
ReferenceExample [status=Hi I am active]
Soft refrence :: ReferenceExample [status=Hi I am active]
Weak reference deleted after:: 1null
Phantom reference deleted after

总结

通过对以上各类型的 reference 介绍可以发现其实 reference 主要是用来与虚拟机 gc 进行交互,使得虚拟机根据对象的不同引用类型,对其采用不同的内存回收策略。

strong 引用的对象正常情况下不会被回收,soft 引用的对象会在出现 OOM 错误之前被回收,而 weak 引用的对象在下一次 gc 的时候就会被回收,对 reference 的基本理解就差不多了。

至于 PhantomReference 与 FinalReference 下次再讲。

以上这篇java中的Reference类型用法说明就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java replaceAll()方法报错Illegal group reference的解决办法

    前言 replaceAll(regex, replacement)函数,由于第一个参数支持正则表达式,replacement中出现"$",会按照$1$2的分组模式进行匹配. 当编译器发现"$"后跟的不是整数的时候,就会抛出"非法的组引用"的异常. 例如,如下代码会报错: public class Test { public static void main(String[] args) { String str = "123ABC456

  • 详解Java的call by value和call by reference

    题目如下: Java传参的两种方式 call by value(值传递):传递的是值(针对基本数据类型),如传递一个整型数值.实际上,按值传递在方法调用方法中,参数只是实际参数的一份拷贝. call by reference(引用传递):传递的是对象的引用(针对对象),即传递的是对象的地址.实际上,引用按传递时候会产生一份新的引用拷贝,新旧两份引用同时指向同一个地址. 代码示例 public class TestJavaCallBy { // 测试值传递 public static void t

  • Java中几个Reference常见的作用详解

    前言 Java中几个Reference作用,也是面试的时候经常问到的问题,以前总是记一次忘一次,现在有时间,索性写个demo测试一把.下面来一起看看详细的介绍: 具体代码如下: JVM 参数: -Xmx10m -Xms5m -XX:+PrintGC SoftReference的时候: weakReference的时候: StrongReference: 由于strong是JVM默认的,这里就不做了,直接就是一点都不会被回收,直至OOM PhantomReference: 虚引用并不会改变内存回收

  • 详解Java对象的强、软、弱和虚引用+ReferenceQueue

    详解Java对象的强.软.弱和虚引用+ReferenceQueue 一.强引用(StrongReference) 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器绝不会回收它.当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题. 二.软引用(SoftReference) 如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它:如果内存空间不足了,就会回收这些对象的内存.只要垃圾回

  • 详解Java弱引用(WeakReference)的理解与使用

    看到篇帖子, 国外一个技术面试官在面试senior java developer的时候, 问到一个weak reference相关的问题. 他没有期望有人能够完整解释清楚weak reference是什么, 怎么用, 只是期望有人能够提到这个concept和java的GC相关. 很可惜的是, 20多个拥有5年以上java开发经验的面试者中, 只有两人知道weak reference的存在, 而其中只有一人实际用到过他. 无疑, 在interviewer眼中, 对于weak reference的理

  • 浅谈Java读写注册表的方式Preferences与jRegistry

    本文研究的主要是Java 读写注册表的两种方式 Preferences 与 jRegistry的相关内容,具体介绍如下. 由于java程序是"write once, run everywhere",用java读写注册表,那程序的跨平台性就差了.java对注册表的操作,在jdk1.4以前的版本中,那是不可能的,只能用JNI来实现:然而jdk1.4之后提供的prefs包可以操作windows注册表,不过定死了root只在SOFTWARE/JavaSoft/prefs下,估计也是出于这种两难

  • java中的Reference类型用法说明

    本文简要总结java中的Reference类型. 最近在研读jdk并发框架,其中AQS是重点,由于我打破砂锅问到底的轻微强迫症,google了AQS作者Doug Lea的论文原文[The java.util.concurrent Synchronizer Framework],有兴趣的同学可以自行下载.其中谈到设计同步框架的核心是选择一个严格意义上的FIFO队列,作为阻塞线程队列并对其进行维护. 对此主要由两种选择,一个是MCS锁,另一个时CLH锁.因为CLH锁比MCS对取消和超时的处理更方便,

  • JAVA中的final关键字用法实例详解

    本文实例讲述了JAVA中的final关键字用法.分享给大家供大家参考,具体如下: 根据上下文环境,java的关键字final也存在着细微的区别,但通常指的是"这是无法改变的."不想改变的理由有两种:一种是效率,另一种是设计.由于两个原因相差很远,所以关键子final可能被误用. 接下来介绍一下使用到final的三中情况:数据,方法,类 final数据 许多编程语言都有某种方法,来向编译器告知一块数据是恒定不变的.有时数据的恒定不变是很有用的,例如: 1. 一个编译时恒定不变的常量 2.

  • JAVA 中Spring的@Async用法总结

    JAVA 中Spring的@Async用法总结 引言: 在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的:但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务,其实,在spring 3.x之后,就已经内置了@Async来完美解决这个问题,本文将完成介绍@Async的用法. 1.  何为异步调用? 在解释异步调用之前,我们先来看同步调用的定义:同步就是整个处理过程顺序执行,当各个过程都执行完毕,并返回结果. 异步调用则是只是发送了调用的

  • java arrayList遍历的四种方法及Java中ArrayList类的用法

    java arrayList遍历的四种方法及Java中ArrayList类的用法 package com.test; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class ArrayListDemo { public static void main(String args[]){ List<String> list = new ArrayList<String

  • java中最大的整数用法分析

    本文实例讲述了java中最大的整数用法.分享给大家供大家参考,具体如下: 8种基本数据类型中,long类型所能表示的整数范围是最大的,但还是有限的.另外,基本数据类型中的整数还有一个问题,那就是不是每个数都能够正确的取负数.例如,对int型而言,"-2147483648"取负就不能得到正确的结果,对其他整数类型也有这个问题. 为了解决这些问题,Java中专门提供了用来进行不限制大小的整数计算的类--java.math.BigInteger.该类可以对任意大小的整数进行操作,不过在进行计

  • java 中ThreadLocal 的正确用法

    java 中ThreadLocal 的正确用法 用法一:在关联数据类中创建private static ThreadLocalThreaLocal的JDK文档中说明:ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread.如果我们希望通过某个类将状态(例如用户ID.事务ID)与线程关联起来,那么通常在这个类中定义private s

  • 深入理解java中的null“类型”

    本文研究的主要是java中的null"类型"的相关实例,具体介绍如下. 先给出一道简单的null相关的题目,引发我们对null的探讨,后面会根据官方语言手册对null"类型"进行解读. 题目:下面程序能正确运行吗? 解析: 输出应该为 :haha 因为null 是可以强转为任何类类型的,所以前面((NULL)null)是合法的,但是null强转以后是无效对象,其返回值为null,(后面会作解释) 而haha方法是静态方法,静态方法使用静态绑定,不会抛出空指针异常.

  • 深入理解java中Arrays.sort()的用法

    Java的Arrays类中有一个sort()方法,该方法是Arrays类的静态方法,在需要对数组进行排序时,非常的好用. 但是sort()的参数有好几种,基本上是大同小异,下面是以int型数组为例的Arrays.sort()的典型用法 import java.util.Arrays; import java.util.Comparator; /** * Arrays.sort()排序 */ public class SortTest { public static void main(Strin

  • 简单介绍java中equals以及==的用法

    简单介绍 equals方法是java.lang.Object类的方法有两种用法说明: 一.对于字符串变量来说,使用"=="和"equals()"方法比较字符串时,其比较方法不同. 1."=="比较两个变量本身的值,即两个对象在内存中的首地址.(java中,对象的首地址是它在内存中存放的起始地址,它后面的地址是用来存放它所包含的各个属性的地址,所以内存中会用多个内存块来存放对象的各个参数,而通过这个首地址就可以找到该对象,进而可以找到该对象的各个属

  • java中的按位与(&)用法说明

    为什么4&7结果是4 4 的二进制值是 100,7 的二进制值是 111(实际上 Java 在存储两个数到内存时会自动在有效值 100 和 111 之前补 29 个零来达到整数类型的 4 byte 大小,此处为了方便展示就不补全前面的一串零了), & 是将两数进行位与位的 and 操作(0 & 1 = 0,1 & 1 = 1),所以操作步骤就是 4: 1 0 0 & & & 7: 1 1 1 ---------- = 1 0 0 可见最后得出的结果是

随机推荐