Java四种引用类型与垃圾回收

前言

从初识java的时候,我们就知道java不同于C语言,是没有指针的概念的,有两种类型,基础类型和引用类型,java中一切皆对象......这里的对象其实就是所谓的引用类型,但是很少有人真的去关注引用类型,机缘巧合,今天看到了引用类型的四种模式的讨论资料,那作为一个不称职的底层程序狗,就来总结一下自己的认识。

引用与对象

上面已经说了,java不同于C语言,是通过“引用”来操作内存的,也可以说我们经常在程序中传递的对象参数,其实都是对这个对象的一种引用。

在 JDK1.2 之前,Java中的定义很传统:如果类型的数据中存储的数值代表的是另外一块内存的起始地址,就称为这块内存代表着一个引用。
Java 中的垃圾回收机制在判断是否回收某个对象的时候,都需要依据“引用”这个概念。

在不同垃圾回收算法中,对引用的判断方式有所不同:

  • 引用计数法:为每个对象添加一个引用计数器,每当有一个引用指向它时,计数器就加1,当引用失效时,计数器就减1,当计数器为0时,则认为该对象可以被回收(目前在Java中已经弃用这种方式了)。
  • 可达性分析算法:从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。

JDK1.2 之前,一个对象只有“已被引用”和"未被引用"两种状态,这将无法描述某些特殊情况下的对象,比如,当内存充足时需要保留,而内存紧张时才需要被抛弃的一类对象。

四种引用类型

在 JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱。这个改动的影响有两方面:

  1. 可以在代码中决定某些对象的生命周期;
  2. 优化JVM的垃圾回收机制

对于JVM垃圾回收机制是一种极大的补充。

强引用

import java.util.ArrayList;
import java.util.List;

public class StrongReferenceTest {

    static class BigObject {
        private Byte[] bytes = new Byte[1024 * 1024];
    }


    public static void main(String[] args) {
        List<BigObject> list = new ArrayList<>();
        while (true) {
            BigObject obj = new BigObject();
            list.add(obj);
        }
    }
}

强引用是最普遍的引用,如果一个对象具有强引用,垃圾回收器不会回收该对象,当内存空间不足时,JVM 宁愿抛出 OutOfMemoryError异常;只有当这个对象没有被引用时,才有可能会被回收。BigObject obj = new BigObject()创建的这个obj对象时就是强引用,这段代码最终会抛出内存溢出异常

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

软引用

软引用顾名思义,这个“引用”的动作并不是绝对的

  1. 当前环境的内存充足的情况下,并不会对软引用对象进行垃圾回收
  2. 当内存吃紧后,JVM触发内存回收机制,寻找可以释放的软对象,优先回收长时间没有被使用或者操作的“软引用对象”,尽可能保留刚创建的“软引用对象”
  3. 在JVM进行内存回收后还是内存吃紧,才会抛出内存溢出的异常

软引用在Java中用java.lang.ref.SoftReference类来表示。

将JVM内存设置为8m

代码如下:

import java.lang.ref.SoftReference;

/**
 * @Auther: LeoLee
 * @Date: 2019/12/30 17:36
 * @Description:
 * @version: 1.0
 */
public class Tests {

	static class Person {

		private String name;
		private Byte[] bytes = new Byte[1024 * 1024];

		public Person(String name) {
			this.name = name;
		}
	}


	public static void main(String[] args) throws InterruptedException {
		Person person = new Person("张三");
		SoftReference<Person> softReference = new SoftReference<>(person);

		person = null;  //去掉强引用,new Person("张三")的这个对象就只有软引用了

		System.gc();
		Thread.sleep(1000);

		System.err.println("软引用的对象 ------->" + softReference.get());
	}
}

结果如下:

由结果可以看出该软引用对象没有被gc()回收,表明,内存是够用的。

修改代码如下:

import java.lang.ref.SoftReference;

/**
 * @Auther: LeoLee
 * @Date: 2019/12/30 17:36
 * @Description:
 * @version: 1.0
 */
public class Tests {

	static class Person {

		private String name;
		private Byte[] bytes = new Byte[1024 * 1024];

		public Person(String name) {
			this.name = name;
		}
	}


	public static void main(String[] args) throws InterruptedException {
		Person person = new Person("张三");
		SoftReference<Person> softReference = new SoftReference<>(person);

		person = null;  //去掉强引用,new Person("张三")的这个对象就只有软引用了

//		System.gc();
		Person anotherPerson = new Person("李四");
		Thread.sleep(1000);

		System.err.println("软引用的对象 ------->" + softReference.get());
	}

结果如下:

表明由于创建了第二个Person对象导致内存不足,软引用已经被回收了

弱引用

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期,它只能生存到下一次垃圾收集发生之前。当垃圾回收器扫描到只具有弱引用的对象时,无论当前内存空间是否足够,都会回收它。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

    public static void main(String[] args) throws InterruptedException {
        Person person = new Person("张三");
        ReferenceQueue<Person> queue = new ReferenceQueue<>();
        WeakReference<Person> weakReference = new WeakReference<Person>(person, queue);

        person = null;//去掉强引用,new Person("张三")的这个对象就只有软引用了

        System.gc();
        Thread.sleep(1000);
        System.err.println("弱引用的对象 ------->" + weakReference.get());

        Reference weakPollRef = queue.poll();   //poll()方法是有延迟的
        if (weakPollRef != null) {
            System.err.println("WeakReference对象中保存的弱引用对象已经被GC,下一步需要清理该Reference对象");
            //清理softReference
        } else {
            System.err.println("WeakReference对象中保存的软引用对象还没有被GC,或者被GC了但是获得对列中的引用对象出现延迟");
        }
    }

虚引用

作为最弱的引用类型,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象虚引用必须要和 ReferenceQueue 引用队列一起使用

public class PhantomReference<T> extends Reference<T> {
    /**
     * Returns this reference object's referent.  Because the referent of a
     * phantom reference is always inaccessible, this method always returns
     * <code>null</code>.
     *
     * @return  <code>null</code>
     */
    public T get() {
        return null;
    }
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}
Object object = new Object();
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue); 

当垃 圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

引用队列(ReferenceQueue)

引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。

与软引用、弱引用不同,虚引用必须和引用队列一起使用。

参考资料:

https://www.cnblogs.com/liyutian/p/9690974.html

https://segmentfault.com/a/1190000015282652

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值