在GC 判断对象是否可用时,无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,都与“引用”有关。在JDK 1.2 以前,Java 中的引用定义很传统:如果reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。
在此我们先拓展一下引用,即对象的访问定位
建立对象是为了使用对象,我们的Java 程序需要通过栈上的reference数据来操作堆上的具体对象。由于Java 虚拟机规范中并没有规定引用应该通过何种方式去定位、访问堆中的具体位置,所以对象访问方式取决于虚拟机实现而定的。目前主流的访问方式有使用句柄和直接指针两种
- 如果使用句柄,那么Java 堆中将会划分出一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的及具体地址信息,如下:
- 如果使用直接指针访问,那么Java 堆对象的布局就必须考虑如何防止访问类型数据的相关信息,而reference中存储的直接就是对象地址,如下:
对比:
- 使用句柄来访问的最大好处就是reference中存储的是稳定的句柄地址,在对象移动(垃圾收集时移动对象是非常普遍的)时只会改变句柄中的实例数据指针,而reference本身不需要修改
- 使用直接指针访问的最大好处就是速度更快,节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本
拉回四种引用,在我们实际垃圾收集时,总会有些对象是“食之无味,弃之可惜”的鸡肋,类似于我们平常收集整理自己的物品,有些衣服可以丢,但不丢没准也还能穿一次,那我们可能会考虑橱柜够不够大,还是否有地方 存放这类衣服。所以在我们GC 的时候也希望能达到这种:当内存空间还足够时,则能保存在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。 很多系统的缓存功能都符合这样的场景。
在JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为 强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference) 4种。这4种 引用强度依次逐渐减弱
强引用就是指在程序需代码中普遍存在的,类似“Object obj = new Object()” 这类的引用,只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象。
软引用 是用来描述一些还有用但并非必需的对象,对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2 之后,提供了SoftReference 类来实现软引用。
弱引用 也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用管理连的对象。在JDK 1.2 之后,提供了WeakReference 类来是休闲弱引用
虚引用 也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK1.2之后,提供了PhantomReference类来实现虚引用
我们首先知道 对象在被判定死亡的过程中会调用一次其finalize()方法,我们以此来验证下是否对象是否被垃圾收集器回收。
package com.xnccs.cn.share.model;
public class Product {
private long price; //价格
private String name; //名称
private String desc; //备注
private String size; //大小
private byte[] bytes;
public Product(long price, String name, String desc, String size, byte[] bytes) {
super();
this.price = price;
this.name = name;
this.desc = desc;
this.size = size;
this.bytes = bytes;
}
public void finalize() throws Throwable{
super.finalize();
System.out.println("调用了finalize方法, 产品:"+ this.name +" 被回收");
}
@Override
public String toString() {
return "Product [price=" + price + ", name=" + name + ", desc=" + desc
+ ", size=" + size + "]";
}
public long getPrice() {
return price;
}
public void setPrice(long price) {
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
}
package com.xnccs.cn.share;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.WeakHashMap;
import com.xnccs.cn.share.model.Product;
/**
* VM: -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* @author j_nan
*
*/
public class ReferenceTest {
private static ReferenceQueue<Product> referenceQueue = new ReferenceQueue<Product>();
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws InterruptedException{
reference(); //强引用
// reference_weak(); //弱引用
// reference_weak_queue();
// weak_hashmap(); //WeakHashMap 使用
}
private static void reference() throws InterruptedException{
//new 一个产品
Product pro = createOne();
//给产品加个编号
HashMap<Product,Integer> map = new HashMap();
//map中放入强引用
map.put(pro, 1);
//这个产品不再需要
pro = null;
//发生GC 进行回收
System.gc();
Thread.sleep(500);
// System.gc();
// Thread.sleep(1000*30);
}
/**
*
* @throws InterruptedException
*/
private static void reference_weak() throws InterruptedException{
//new 一个产品
Product pro = createOne();
//给产品加个编号
HashMap<WeakReference,Integer> map = new HashMap();
//弱引用对象
WeakReference<Product> weakReference = new WeakReference<Product>(pro);
// 根据引用获取对象
System.out.println("根据弱引用获取对象:"+weakReference.get());
//map中放入弱引用
map.put(weakReference, 1);
//这个产品不再需要
pro = null;
System.out.println("-----------------------------发生GC------------------------------");
//发生GC 进行回收
System.gc();
// 根据引用获取对象
System.out.println("输出产品:"+weakReference.get());
Thread.sleep(500);
// System.gc();
// Thread.sleep(1000*30);
}
/**
*
* @throws InterruptedException
*/
private static void reference_weak_queue() throws InterruptedException{
//new 一个产品
Product pro = createOne();
//给产品加个编号
HashMap<WeakReference,Integer> map = new HashMap();
//引用队列
WeakReference<Product> weakReference = new WeakReference<Product>(pro,referenceQueue);
// 根据引用获取对象
System.out.println("输出产品:"+weakReference.get());
//map中放入弱引用
map.put(weakReference, 1);
//这个产品不再需要
pro = null;
//
System.out.println("GC前 引用队列:"+referenceQueue.poll());
System.out.println("-----------------------------发生GC------------------------------");
//发生GC 进行回收
System.gc();
Thread.sleep(500);
// 根据引用获取对象
System.out.println("输出产品:"+weakReference.get());
System.out.println("GC1 后 引用队列:"+referenceQueue.poll());
System.gc();
System.out.println("GC2 后 引用队列:"+referenceQueue.poll());
}
private static void weak_hashmap() throws InterruptedException{
Product pro = new Product(10000,"产品A","测试专用","100kg",new byte[3*_1MB]);
WeakHashMap<WeakReference<Product>,Integer> map = new WeakHashMap<WeakReference<Product>,Integer>();
map.put(new WeakReference<Product>(pro), 1);
pro = null;
System.gc();
Thread.sleep(500);
System.out.println(map.size());
}
private static Product createOne(){
return new Product(10000,"产品A","测试专用","100kg",new byte[8*_1MB]);
}
}
这里自己写了一个例子,大家可以自行跑一下
1。 如果是强引用,直接new 的情况下,你会发现即便是将对象Product 置为null 但在发生GC时对象Product 里的finalize() 方法并未执行打印,因为map 还占着它的一个引用,但如果使用弱引用类WeakReference 来存放,发生GC 就会打印
2。WeakHashMap 如果其中某一个引用失效,则Size 会同步减小,不会出现null 的情况导致错误,以及内存泄露的问题
3。类的注释里提供了执行的条件,可以看一下堆栈的实际大小来判断对象的回收情况
4。 ReferenceQueue:Reference这个类里面在构造函数的时候有两种选择,一种是给它传入一个ReferenceQueue,一种是不传,如果不传的话,等这个对象的内存被回收了,直接从Active变为Inactive状态,如果我们传入了ReferenceQueue,那么当对象的内存回收的时候会经历一个过程,从Active->Pending->Enqueued->Inactive。pending状态就是等待着进入ReferenceQueue队列的这样一个状态,说白了它目前还没被回收,只是对象的引用(用户代码中的引用)被移除了,pending保存了这个引用,回收的过程中,ReferenceHandler这个线程会把该对象的引用(pending)放入到我们在构造函数时传入的那个队列里面
在调用:reference_weak() 方法时
输出:
[ParOldGen: 8673k->481k(10240k)] 含义:GC 前该内存区域已使用容量->GC 后该内存区域已使用容量(该内存区域总容量)。方括号外的 8705K->481K(19456K) 表示: GC 前Java 堆已使用容量->GC后Java 堆已使用容量(Java 堆总容量)。 可以看出确实被回收了。