深入探讨 java.lang.ref 包--转

概述

Java.lang.ref 是 Java 类库中比较特殊的一个包,它提供了与 Java 垃圾回收器密切相关的引用类。这些引用类对象可以指向其它对象,但它们不同于一般的引用,因为它们的存在并不防碍 Java 垃圾回收器对它们所指向的对象进行回收。其好处就在于使者可以保持对使用对象的引用,同时 JVM 依然可以在内存不够用的时候对使用对象进行回收。因此这个包在用来实现与缓存相关的应用时特别有用。同时该包也提供了在对象的“可达”性发生改变时,进行提醒的机制。本文通过对该包进行由浅入深的介绍与分析,使读者可以加深对该包的理解,从而更好地利用该包进行开发。

回页首

java.lang.ref 包的介绍

我们可以先来看一下 java.lang.ref 这个包的结构,如图 1 所示

图 1. java.lang.ref 包结构

该包中各类的继承关系如图 2 所示

图 2. java.lang.ref 包中类的继承关系 :

Reference 是一个抽象类,而 SoftReference,WeakReference,PhantomReference 以及 FinalReference 都是继承它的具体类。

接下来我们来分别介绍和分析强引用以及 java.lang.ref 包下各种虚引用的特性及用法。

回页首

StrongReference, SoftReference, WeakReference 以及 PhantomReference 的特性及用法

StrongReference:

我们都知道 JVM 中对象是被分配在堆(heap)上的,当程序行动中不再有引用指向这个对象时,这个对象就可以被垃圾回收器所回收。这里所说的引用也就是我们一般意义上申明的对象类型的变量(如 String, Object, ArrayList 等),区别于原始数据类型的变量(如 int, short, long 等)也称为强引用。

在了解虚引用之前,我们一般都是使用强引用来对对象进行引用。如:

清单 1. StrongReference usage
 String tag = new String("T");

此处的 tag 引用就称之为强引用。而强引用有以下特征:

  • 强引用可以直接访问目标对象。
  • 强引用所指向的对象在任何时候都不会被系统回收。
  • 强引用可能导致内存泄漏。

我们要讨论的这三种 Reference 较之于强引用而言都属于“弱引用”,也就是他们所引用的对象只要没有强引用,就会根据条件被 JVM 的垃圾回收器所回收,它们被回收的时机以及用法各不相同。下面分别来进行讨论。

SoftReference:

SoftReference 在“弱引用”中属于最强的引用。SoftReference 所指向的对象,当没有强引用指向它时,会在内存中停留一段的时间,垃圾回收器会根据 JVM 内存的使用情况(内存的紧缺程度)以及 SoftReference 的 get() 方法的调用情况来决定是否对其进行回收。(后面章节会用几个实验进行阐述)

具体使用一般是通过 SoftReference 的构造方法,将需要用弱引用来指向的对象包装起来。当需要使用的时候,调用 SoftReference 的 get() 方法来获取。当对象未被回收时 SoftReference 的 get() 方法会返回该对象的强引用。如下:

清单 2. SoftReference usage
 SoftReference<Bean> bean = new SoftReference<Bean>(new Bean("name", 10));
 System.out.println(bean.get());// “name:10”
引用类型 取得目标对象方式 垃圾回收条件 是否可能内存泄漏
强引用 直接调用 不回收 可能
软引用 视内存情况回收 不可能
弱引用 通过 get() 方法 永远回收 不可能
虚引用 无法取得 不回收 可能
类型 是否抛出异常 示例代码 运行结果
StrongReference 抛出异常 见清单 6 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
SoftReference 不抛异常,之前的引用自动清空并返回 null 见清单 7 null
WeakReference 同上 见清单 8 null
PhantomReference 抛出异常 见清单 9 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

回页首

FinalReference 以及 Finzlizer

FinalReference 作为 java.lang.ref 里的一个不能被公开访问的类,又起到了一个什么样的作用呢?作为他的子类, Finalizer 又在垃圾回收机制里扮演了怎么样的角色呢?

实际上,FinalReference 代表的正是 Java 中的强引用,如这样的代码 :

Bean bean = new Bean();

在虚拟机的实现过程中,实际采用了 FinalReference 类对其进行引用。而 Finalizer,除了作为一个实现类外,更是在虚拟机中实现一个 FinalizerThread,以使虚拟机能够在所有的强引用被解除后实现内存清理。

让我们来看看 Finalizer 是如何工作的。首先,通过声明 FinalizerThread,并将该线程实例化,设置为守护线程后,加入系统线程中去。

清单 11
 static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
 Thread finalizer = new FinalizerThread(tg);
 finalizer.setPriority(Thread.MAX_PRIORITY - 2);
 finalizer.setDaemon(true);
 finalizer.start();
 }

在 GC 的过程中,当一个强引用被释放,由系统垃圾收集器标记后的对象,会被加入 Finalizer 对象中的 ReferenceQueue 中去,并调用 Finalizer.runFinalizer() 来执行对象的 finalize 方法。

清单 12
 private void runFinalizer() {
 synchronized (this) {
    if (hasBeenFinalized()) return;
    remove();
 }
 try {
    Object finalizee = this.get();
    if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
 invokeFinalizeMethod(finalizee);
 /* 注意,这里需要清空栈中包含该变量的的 slot,
                   ** 从而来减少因为一个保守的 GC 实现所造成的变量未被回收的假象 */
 finalizee = null;
    }
 } catch (Throwable x) { }
 super.clear();
 }

注意,标记处所调用的 invokeFinalizeMethod 为 native 方法,由于 finalize 方法在 Object 类中被声明为 protected,这里必须采用 native 方法才能调用。随后通过将本地强引用设置为空,以便使垃圾回收器清理内存。

可以看到,通过这样的方法,Java 将四种引用对象类型:软引用 (SoftReference),弱引用 (WeakReference),强引用 (FinalReference),虚引用 (PhantomReference) 平等地对待,并在垃圾回收器中进行统一调度和管理。

回页首

不同 Java 虚拟机上的表现与分析

让我们来回顾一下四种引用类型的表现以及在垃圾回收器回收清理内存时的表现 .

  1. 软引用 (SoftReference), 引用类型表现为当内存接近满负荷 , 或对象由 SoftReference.get() 方法的调用没有发生一段时间后 , 垃圾回收器将会清理该对象 . 在运行对象的 finalize 方法前 , 会将软引用对象加入 ReferenceQueue 中去 .
  2. 弱引用 (WeakReference), 引用类型表现为当系统垃圾回收器开始回收时 , 则立即会回收该对象的引用 . 与软引用一样 , 弱引用也会在运行对象的 finalize 方法之前将弱引用对象加入 ReferenceQueue.
  3. 强引用 (FinalReference), 这是最常用的引用类型 . JVM 系统采用 Finalizer 来管理每个强引用对象 , 并将其被标记要清理时加入 ReferenceQueue, 并逐一调用该对象的 finalize() 方法 .
  4. 虚引用 (PhantomReference), 这是一个最虚幻的引用类型 . 无论是从哪里都无法再次返回被虚引用所引用的对象 . 虚引用在系统垃圾回收器开始回收对象时 , 将直接调用 finalize() 方法 , 但不会立即将其加入回收队列 . 只有在真正对象被 GC 清除时 , 才会将其加入 Reference 队列中去 .

这里比较两个比较典型的 JVM 环境,Oracle Java SE6 和 IBM JDK 6。采用了如下的测试代码 :

清单 13. 类 RefTestObj
 public class RefTestObj {
 private int id; 

 public int getId() {
 return id;
 } 

 public void setId(int id) {
 this.id = id;
 } 

 @Override
 public int hashCode() {
 return super.hashCode();
 } 

 @Override
 public String toString() {
 return super.toString() + "[id=" + this.id + "]";
 } 

 @Override
 protected void finalize() {
 System.out.println("Object [" + this.hashCode() + "][
 id=" + this.id + "] come into finalize");
 try {
 super.finalize();
 } catch (Throwable e) {
 e.printStackTrace();
 }
 }
 }
清单 14. 类 RefMainThread
 import java.lang.ref.PhantomReference;
 import java.lang.ref.Reference;
 import java.lang.ref.ReferenceQueue;
 import java.lang.ref.SoftReference;
 import java.lang.ref.WeakReference; 

 public class RefMainThread {
 public static void main(String[] args) {
 // 创建三种不同的引用类型所需对象
 RefTestObj softRef = new RefTestObj();
 RefTestObj weakRef = new RefTestObj();
 RefTestObj phanRef = new RefTestObj(); 

 softRef.setId(1);
 weakRef.setId(2);
 phanRef.setId(3); 

 ReferenceQueue<RefTestObj> softRefQueue = new ReferenceQueue<RefTestObj>();
 ReferenceQueue<RefTestObj> weakRefQueue = new ReferenceQueue<RefTestObj>();
 ReferenceQueue<RefTestObj> phanRefQueue = new ReferenceQueue<RefTestObj>(); 

 SoftReference<RefTestObj> softRefObj =
 new SoftReference<RefTestObj>(softRef, softRefQueue);
 WeakReference<RefTestObj> weakRefObj =
 new WeakReference<RefTestObj>(weakRef, weakRefQueue);
 PhantomReference<RefTestObj> phanRefObj =
 new PhantomReference<RefTestObj>(phanRef, phanRefQueue); 

 // 打印正常情况下三种对象引用
 print(softRefObj);
 print(weakRefObj);
 print(phanRefObj); 

 // 将对象清空
 softRef = null;
 weakRef = null;
 phanRef = null; 

 // 打印引用队列及 get() 方法所能取到的对象自身
 if (softRefObj != null) {
 System.out.println("Soft Reference Object run get():" + softRefObj.get());
 System.out.println("Check soft queue:" + softRefQueue.poll());
 } 

 if (weakRefObj != null) {
 System.out.println("Weak Reference Object run get():" + weakRefObj.get());
 System.out.println("Check weak queue:" + weakRefQueue.poll());
 } 

 if (phanRefObj != null) {
 System.out.println("Phantom Reference Object run get():" + phanRefObj.get());
 System.out.println("Check Phantom queue:" + phanRefQueue.poll());
 } 

 // 开始执行垃圾回收
 System.gc();
 System.runFinalization(); 

 // 检查队列,是否已经被加入队列,是否还能取回对象
 if (softRefObj != null) {
 System.out.println("Soft Reference Object run get():" + softRefObj.get());
 System.out.println("Check soft queue:" + softRefQueue.poll());
 } 

 if (weakRefObj != null) {
 System.out.println("Weak Reference Object run get():" + weakRefObj.get());
 System.out.println("Check weak queue:" + weakRefQueue.poll());
 } 

 if (phanRefObj != null) {
 System.out.println("Phantom Reference Object run get():" + phanRefObj.get());
 System.out.println("Check Phantom queue:" + phanRefQueue.poll());
 } 

 // 对于虚引用对象,在经过多次 GC 之后, 才会加入到队列中去
 Reference<? extends RefTestObj> mynewphan = null;
 int refCount = 1;
 while (mynewphan == null) {
 mynewphan = phanRefQueue.poll();
 System.gc();
 System.runFinalization();
 if (mynewphan != null) {
 System.out.println("Check Phantom queue:" + mynewphan);
 System.out.println("Count for " + refCount + " times");
 break;
 }
 refCount ++;
 }
 } 

 public static void print(Reference<RefTestObj> ref) {
 RefTestObj obj = ref.get();
 System.out.println("The Reference is " + ref.toString() + " and with object " + obj +
      " which is " + (obj == null ? "null" : "not null"));
 }
 }

通过执行 RefMainThread, 我们可以清晰地根据打印结果看到对象在内存中被加入队列 , 以及调用 finalize 方法的顺序及过程 .

为了测试不同的 JVM 环境并消除其他因素的印象 , 本例采用的背景环境均为 Windows2003 下的 32bit JVM.

首先采用了环境为 Oracle Java SE 6 update 23 进行测试 , 结果如下 :

清单 15. Oracle Java SE 6 update 23 下测试结果
 The Reference is [email protected] and
 with object [email protected][id=1] which is not null
 The Reference is [email protected] and
 with object [email protected][id=2] which is not null
 The Reference is [email protected] and with object null which is null
 Soft Reference Object run get():[email protected][id=1]
 Check soft queue:null
 Weak Reference Object run get():[email protected][id=2]
 Check weak queue:null
 Phantom Reference Object run get():null
 Check Phantom queue:null
 Object [27744459][id=3] come into finalize
 Object [21174459][id=2] come into finalize
 Soft Reference Object run get():[email protected][id=1]
 Check soft queue:null
 Weak Reference Object run get():null
 Check weak queue:[email protected]
 Phantom Reference Object run get():null
 Check Phantom queue:null
 Check Phantom queue:[email protected]
 Count for 2 times

可以看到 , 当运行了系统回收后 , 虚引用与弱引用被回收 , 由于内存并不吃紧 , 软引用依然保持原样 . 弱引用立即被加入了队列 , 而虚引用则在循环两次的手动调用 GC 后被加入了队列 . 其次 , 采用的环境是 IBM JDK 6, 结果如下 :

清单 16. IBM JDK 6 下测试结果
 The Reference is [email protected] and
 with object [email protected][id=1] which is not null
 The Reference is [email protected] and
 with object [email protected][id=2] which is not null
 The Reference is [email protected]
 and with object null which is null
 Soft Reference Object run get():[email protected][id=1]
 Check soft queue:null
 Weak Reference Object run get():[email protected][id=2]
 Check weak queue:null
 Phantom Reference Object run get():null
 Check Phantom queue:null
 Object [958544162][id=3] come into finalize
 Object [958413088][id=2] come into finalize
 Soft Reference Object run get():[email protected][id=1]
 Check soft queue:null
 Weak Reference Object run get():null
 Check weak queue:[email protected]
 Phantom Reference Object run get():null
 Check Phantom queue:null
 Object [958282014][id=1] come into finalize
 ............

程序运行到这里进入了无限循环,必须手动终止。比对上下两份结果可以看到,当多次运行系统垃圾回收后,IBM JVM 将软引用一并加入了回收队列中,并运行了其 finalize 方法。另外,即使经过很多次系统垃圾回收,虚引用也没有被加入到队列中去。不知道这是不是 IBM JVM 的一个小小的 BUG 所在。

结论

  • SoftReference 中 Oracle JVM 的表现满足规范,只当内存不足时才进行回收。而 IBM JVM 的策略则更为积极,在内存尚且充足的情况下也进行了回收,值得注意。
  • PhantomReference 中 Oracle JVM 的表现满足规范,执行 finalize 后若干次 GC 就被添加到了 Queue 中。而 IBM JVM 则始终没有被添加到 Queue 中导致了死循环。所以在使用 PhantomReference 时出现类似的情况时,可以考虑是否是因为使用了不同 JVM 所导致。

回页首

小结

本文深入地介绍了 java.lang.ref 包使用方法,并结合实验分析了包内不同类的表现。同时对该包在不同 Java 虚拟机上的表现进行了深入地分析。

原文:http://www.ibm.com/developerworks/cn/java/j-lo-langref/

时间: 2024-10-25 00:09:02

深入探讨 java.lang.ref 包--转的相关文章

JAVA中反射机制六(java.lang.reflect包)

一.简介 java.lang.reflect包提供了用于获取类和对象的反射信息的类和接口.反射API允许对程序访问有关加载类的字段,方法和构造函数的信息进行编程访问.它允许在安全限制内使用反射的字段,方法和构造函数对其底层对等进行操作. 二.java.lang.reflect AccessibleObject类 java.lang.reflect.AccessibleObject类是Field,Method和Constructor类对象的基类. 它提供了将反射对象标记为在使用它时抑制默认Java

moon 反射机制---java.lang.reflect包

java反射机制:在运行状态中,对于一个已经加载到JVM的java对象/类 在程序中实现访问.检查.修改.描述java对象本身的信息(构造方法.方法.成员变量.类本身的信息) 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制. C++,Java,C#不是动态语言.但是JAVA有着一个非常突出的动态相关机制:Reflection, 反射是java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接. 二,反射机制的作用:

java.lang.UnsatisfiedLinkError: 包名.方法名([BLjava

Tomcat启动时,输出如下错误信息: 24-May-2015 18:27:39.057 SEVERE [localhost-startStop-1] org.apache.catalina.core.ContainerBase.addChildInternal ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catal

java.lang.io包的使用

1 String source = "ABCDEF123456"; 2 int mid = source.length() / 2; 3 4 ByteArrayInputStream bytesIS = new ByteArrayInputStream(source.getBytes()); 5 6 // 1. 顺序读取流 7 int b = 0; 8 while( -1 != (b = bytesIS.read())){ 9 System.out.print(Integer.toHe

java.lang.instument包超详解

1 java.lang.instrument简介 Java5之后,增加了一个包java.lang.instrument,这个包的东西很少,两个接口,ClassFileTransformer和Instrumentation,一个类ClassDefinition,还有两个Exception:IllegalClassFormatException和UnmodifiableClassException: 先剧透一下整个包最重要的接口Instrumentation提供的两个功能,总体的功能就是Instru

java.lang.ref.Reference&lt;T&gt;

//看之前先要知道java里面的四种引用.package com.zby.ref; import sun.misc.Cleaner; /** * 引用对象的抽象基础类.这个类定义了所有引用对象的公共操作.因为引用对象在跟垃圾收集器紧密合作中被实现,所以这个类不能被引用对象直接继承. * * @author zhoubaiyun * * @param <T> */ public abstract class Reference<T> { /* * 一个引用实例是在这四个可能的内部状态

JDK框架简析--java.lang包中的基础类库、基础数据类型

题记 JDK.Java Development Kit. 我们必须先认识到,JDK不过,不过一套Java基础类库而已,是Sun公司开发的基础类库,仅此而已,JDK本身和我们自行书写总结的类库,从技术含量来说.还是在一个层级上,它们都是须要被编译成字节码.在JRE中执行的,JDK编译后的结果就是jre/lib下的rt.jar,我们学习使用它的目的是加深对Java的理解,提高我们的Java编码水平. 本系列全部文章基于的JDK版本号都是1.7.16. 源代码下载地址:https://jdk7.jav

java.lang包

作者:gnuhpc 出处:http://www.cnblogs.com/gnuhpc/ 1.特性——不用import 2.String String x = "abc"; <=> String x= new String("abc"); 因为public final class java.lang.String; 而String x="The number " + y;中,在JAVA中不管是什么变量或者对象,在对String进行加和时

JDK源码简析--java.lang包中的基础类库

题记 JDK,Java Development Kit. 我们必须先认识到,JDK只是,仅仅是一套Java基础类库而已,是Sun公司开发的基础类库,仅此而已,JDK本身和我们自行书写总结的类库,从技术含量来说,还是在一个层级上,它们都是需要被编译成字节码,在JRE中运行的,JDK编译后的结果就是jre/lib下得rt.jar,我们学习使用它的目的是加深对Java的理解,提高我们的Java编码水平. 本系列所有文章基于的JDK版本都是1.7.16. 本节内容 在本节中,简析java.lang包所包