Java中的强软弱虚四种引用和ThreadLocal

8 篇文章 0 订阅
8 篇文章 0 订阅

1 强引用

1.1 什么是强引用

// 情况1
static Object o = new Object();

// 情况2
public void get(){
    Object o = new Object();
}

就是最普通的创建对象方式,当使用new关键字经过创建对象的三个步骤完成之后,对象o此时就有了一个指向堆内存中这个对象的一个引用。

1.2 与垃圾回收的关系

1.2.1 什么可以作为GC Root

由于Java使用的垃圾回收机制是根可达算法,使用为GC Root的对象都有以下几种

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。

  • 本地方法栈中JNI(即一般说的native方法)引用的对象。

  • 方法区中的静态变量和常量引用的对象。

1.2.2 何时回收

  • 如果在线程执行方法的的过程中创建的o对象,在此方法在仍然在执行过程中时(也就是这个方法的栈帧仍然存在于在线程栈中时)创建的变量,只要不显示声明为o = null,此对象会一直存在于堆中,即使发生GC,也不会回收

  • 执行完成此方法,栈帧弹出,o自动消失,也会发生不可达情况,在发生GC的时候被回收

  • 静态变量在类被卸载的时候才会不再可达,才会在GC中回收

public class NormalReference {
    public static void main(String[] args) throws IOException {
        MyObject o = new MyObject();
        o = null;
        System.gc(); // Full GC
    }
}

public class MyObject {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("finalize");
    }
}

2 软引用

2.1 什么是软引用

简单说就是当创建对象需要的空间大小不够的时候,会触发一次GC,如果GC完成之后仍然不够此对象分配,则回收掉软引用指向的对象(而不是回收软引用本身)

2.2 代码实验

在这里插入图片描述

// VM -Xms20M -Xmx20M
public class SoftReference {
    public static void main(String[] args) {
        //此时的对象指引关系如上图所示(上图并不规范,讲道理o应该在main线程栈空间中,而softreference也应该位于堆空间中,理解意思就行哈)
        SoftReference<byte[]> o = new SoftReference<>(new byte[1024*1024*10]);
        
        System.out.println(o.get());
        // Full GC 之后仍可获得软引用指向的对象
        System.gc();
        
        System.out.println(o.get());

        //再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用回收
        byte[] b = new byte[1024*1024*15];
        // 此时软引用指向的对象已经不存在了
        System.out.println(o.get());
        // 但是软引用对象仍然健在
        System.out.println(o);
    }
}

2.3 使用场景

可以用作缓存,如果内存够用,则继续缓存保留加快程序运行速度,如果需要创建对象需要更多的空间,暂时牺牲运行速度,别爆出OOM错误,干掉软引用指向的缓存。


3 弱引用

3.1 什么是弱引用

简单说就是当发生GC时,不管堆空间剩下多少、够不够用,都回收掉弱引用指向的对象(而不是回收弱引用本身)

3.2 代码实验

1749

public class WeakReference {
    public static void main(String[] args) {
        WeakReference<Obejct> o = new WeakReference<>(new Obejct());
		// 仍能获取到弱引用指向的对象
        System.out.println(o.get());
        System.gc();
        // 弱引用指向的对象经过 GC之后被回收
        System.out.println(o.get());
        // 但是弱引用对象本身并没有被回收
        System.out.println(o);
    }
}

3.3 使用场景

3.3.1 ThreadLocal源码分析

每个线程都有自己的 ThreadLocal 作为线程本地存储变量,有一个内部类THreadLocalMap

public class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

threadLocals在每次new Thread(Runnable)的时候,都会有一个自己的ThreadLocalMap类型的threadLocals对象。

class ThreadLocal{
    
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        	//新创建一个Entry对象, Entry这个类很有意思
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        	// 把键值对放到table中
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
    }
}

ThreadLocalMap又使用一个关键的对象 table 来进行键值对的存储,所以关键点还是得看 Entry这个类

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

此时我们发现,Entry是一个弱引用的子类,这个弱引用“弱弱的”指向了一个ThreadLocal对象

OK!此时我们再用一段很简单的Java代码来看一下现在的关系:

3.3.2 ThreadLocal小实验

public class WeakReferenceAndThreadLocal {
    public static void main(String[] args) {
        

        ThreadLocal<Obejct> tl = new ThreadLocal<>();
        tl.set(new Object());
        tl.remove();
    }
}

我们逐句来解释每句话到底是什么意思:

  • tl.set(new Object());
    • 往main线程的ThreadLocalMap中放入一个 new Objectset()方法最终调用如下:
      • map.set(this, value);,在这个Map中,刚刚创建的tl作为key(而且这个key指向所用的引用类型是弱引用),new Object 作为 V

此时各个对象之间的引用关系如下图所示

在这里插入图片描述

  • tl.remove();

    • 最终调用m.remove(this);

      • private void remove(ThreadLocal<?> key) {
            ...
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    //断掉K的弱引用
                    e.clear();
                    //断掉V的强引用,移除value占用的槽位
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
        
        public void clear() {
            this.referent = null;
        }
        
        private int expungeStaleEntry(int staleSlot) {
            ...
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            ...
        }
        

方法栈帧弹出后,tl 消失,强引用消失,之前map的key的弱引用和value的强引用也消失。
在这里插入图片描述

3.4 ThreadLocal的内存泄漏问题

K的弱引用换成强引用

假设我们K的虚引用变成了强引用,而且恰巧因为程序员的失误没有调用remove方法,那么即使这个创建ThreadLocal对象的方法从栈帧弹出,也仍然无法对K-V所指向的对象进行垃圾回收。

K仍然弱引用,但是没有调用remove方法

程序庞大运行时间久之后,结果就会导致这个threadLocals这个map结构越来越庞大,但是V指向的对象却已经获取不到,因为虚引用指向K对象已经被GC回收,从而造成了内存泄漏问题


4. 虚引用

// TODO
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值