解析java中对象的"引用"

在GC机制中判断一个对象是否为”垃圾”的一个重要指标就是判断该对象是否可以被”引用”到。我们来学习到底什么是java中的对象的”引用”。

首先来介绍java对象引用的类型:

Java.lang.ref包下提供了3个类:SoftReference……..WeakReference…….PhantomReference

分别代表三种引用类型: 软引用 虚引用 弱引用 (当然还有强引用,默认即为强引用)

a) 强引用: 最常见的引用方式。创建一个对象,并且把这个对象赋值给一个引用变量,这个引用变量就是强引用

JVM肯定不会收集被强引用的对象,所以它是造成内存泄露的主要原因.

b) 软引用: 通过SoftReference类来实现,当一个对象只具有软引用时,它有可能被垃圾回收机制回收。当内存空间足够时,它不会被系统回收,当内存空间不够的时候,则它会被回收。

/* * 如下代码的运行jvm环境 -Xms2m -Xmx2m * 即设置堆大小为2M并不允许扩展 */


class Person{
    String name;
    int age;
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
}
public class SoftReferenceTest {

    public static void main(String[] args) {
        //问题: 需要访问1000个Person对象
        //使用SoftReference数组来保存1000个Person对象

        //定义一个长度为1000的SoftReference软引用的数组
        java.lang.ref.SoftReference<Person> softPerson[] = new java.lang.ref.SoftReference[100000];
        for(int i=0;i<softPerson.length;i++){
            //将1000个Person实例依次装入到软引用的数组中
            softPerson[i] = new java.lang.ref.SoftReference<Person>(new Person("wang"+i,i));        
        }
        System.out.println(softPerson[2].get());
        System.out.println(softPerson[888].get());
        //手动调用GC
        System.gc();
        System.runFinalization();
        //再次输出
        System.out.println(softPerson[2].get());
        System.out.println(softPerson[99820].get());
    }

}

运行上面程序我们发现很多输出为null,则说明它被回收了。则避免了OutOfMemoryError

c) 弱引用:弱引用与软引用很相似,区别在于弱引用所引用的对象的生存期更短。弱引用通过WeakReference类实现。
当一个对象被弱引用引用时,当垃圾回收机制运行时,不管内存空间是否足够,它总是会被回收.

public class WeakReferenceTest {
    public static void main(String[] args) {
     //创建一个WeakReference数组
        WeakReference<String> weak[] = new WeakReference[10];
     //创建一个字符串,并把弱引用WeakReference引用到该字符串
    //只能使用new String("StringAll");来创建 不能使用String str = "StringAll"
        WeakReference<String> weak2  = new WeakReference(new String("String")); 
     //为数组的每个值赋值
        for(int i=0;i<weak.length;i++){
              weak[i]= new WeakReference<String>(new String("String"+i));
        }
      //将其输出:
        System.out.println(weak[6].get());
        System.out.println(weak2.get());
        //手动调用GC
        System.gc();
        System.runFinalization();
        //再次输出 全是null
        for(int i=0;i<10;i++)
        System.out.println(weak[i].get());
        System.out.println(weak2.get());
        //如果要使用弱引用所引用的对象
        String str = weak2.get();
        if(str==null){
            str=new String("String");
            weak2=new WeakReference(str);
        }
        System.out.println(str);
    }
}

为什么在使用WeakReferenceTest来引用String类型时要使用new String(“xxx”)而不能使用String =“xxx”呢?

因为采用String str =“###”;JVM的字符串缓存池会缓存这个字符串直接量(会用强引用来引用它),系统则不会回收被缓存的字符串常量.这样就无法看到上述的效果了。

所以我们在平时使用时,尽量使用String str = “###”而不使用 String str = new String(“###”);因为采用new时str所引用的String对象底层还包括一个char[]数组,这个char[]数组依次存放你创建的字符串的每个字符.

由于弱引用具有很大的不确定性,因为垃圾回收是不确定的,你在使用弱引用引用的对象的时可能它是null
所以如果程序需要使用它则为它重新赋值已避免NullPointException

//如果要使用弱引用所引用的对象
        String str = weak2.get();
        //如果为null则重新赋值
        if(str==null){
            str=new String("String");
            weak2=new WeakReference(str);
        }

与WeakReference相似的是WeakHashMap,以key-value的形式来保存数据。当执行垃圾回收后,它的key-value对都会被清空,除非某些key还有强引用在引用它们。

d).虚引用: 软引用和弱引用都可以单独使用,但虚引用不行,因为单独使用虚引用没有意义。虚引用的主要作用就是跟踪对象被垃圾回收的状态,程序可以通过检查与虚引用关联的引用队列中是否已经包含指定的虚引用,来了解虚引用引用的对象是否即将被回收。

引用队列由java.lang.ref.ReferenceQueue类表示,它用于保存被回收后对象的引用。

  • 当把弱引用,软引用和引用队列一起使用的时候。当系统回收被引用的对象之后,将会把被回收对象对应的引用添加到引用队列中。

  • 虚引用在对象被释放之前,把它对应的虚引用添加到它的关联的引用队列中,这使得可以在对象被回收之前采取行动

虚引用主要用来跟踪对象被垃圾回收的状态。虚引用无法获取它的引用对象。

public class PhantomReferenceTest {

    public static void main(String[] args) {
       //创建一个字符串
    String  str = new String("string");
       //创建引用队列
    ReferenceQueue<String> queue = new ReferenceQueue<String>();
       //创建一个虚引用,让此虚引用引用到字符串
    PhantomReference<String> phantom = new PhantomReference<String>(str,queue);
      //切断字符串的强引用
    str = null;
      //试图取出虚引用引用的对象,发现不行
    System.out.println(phantom.get());
    //查看引用队列中最先进入队列的引用与queue比较
    System.out.println(queue.poll()==phantom);
     ////手动调用GC
    System.gc();
    System.runFinalization();
    //查看引用队列中最先进入队列的引用与queue比较
    System.out.println(queue.poll()==phantom);

    }

}

运行上面的程序我们发现,被虚引用引用的对象被垃圾回收之后会把引用添加到对应的引用队列中
当我们在写程序的时候如果希望尽可能的减少程序在其生命周期中占用的内存大小,这些引用类很用好处,但我们要使用这些引用类就必须切断对象的强引用。如果保留了对象的强引用,则没有意义。

你可能感兴趣的:(java,GC)