JAVA的四种引用类型:强软弱虚

分类依据

  • 根据垃圾回收器的回收情况来分类

类型介绍以及用处

  • 强引用:普通引用,即Object o = new Object这样的引用的统称,一旦启用垃圾回收器,会对强引用进行可达性分析,只有垃圾对象才会被回收
  • 软引用(SoftReference):软引用内部包含一个引用,常用于缓存,淘汰机制有点类似LRU,通过SoftReference m声明的m也是一个强引用,但是m会指向SoftReference中包含的那个软引用,但是强引用指向的是软引用,m可以通过调用get()来访问内部的软引用,只要内存不够用的情况下,之前的软引用就会被当做垃圾回收,测试程序如下,事先设置JVM的最大内存为20M-Xmx20M,输出为两个对象地址和一个null
public class SoftRef {
    public static void main(String[] args) {
        SoftReference<byte[]> sr = new SoftReference<>(new byte[1024*1024*10]);
        System.out.println(sr.get());
        System.gc();
        try {
            Thread.sleep(500);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(sr.get());
        byte[] b = new byte[1024*1024*15];
        System.out.println(sr.get());
    }
}
  • 弱引用(WeakReference):面试常问,弱引用比软引用更脆弱,只要垃圾回收器执行,弱引用就会被回收,在ThreadLocal中使用,防止内存泄露。测试程序如下:
public class WeakRef {
    public static void main(String[] args) {
        WeakReference<byte[]> w = new WeakReference<>(new byte[1024*1024]);
        System.out.println(w.get());
        System.gc();
        System.out.println(w.get());
   }
  • 虚引用:
    • 虚引用指向的对象是无法被访问的,这种引用的作用在于管理JVM的堆外内存(一般是在操作系统而不是在JVM中的对象,比如DirectorByteBuffer)。
    • 创建的时候会有两个参数,一个是引用的对象T,另一个是ReferenceQueue队列,其中,队列用于记录对象是否被回收,换句话说,一旦虚引用指向的对象被回收,那么就往队列中插入回收消息。
    • 用于判断堆外内存的情况,用于配合NIO中的DirectedByteBuffer(这个缓冲数组减少了一次从操作系统到JVM的拷贝开销),一旦回收队列中有了消息,说明对应引用所指向的空间已经是垃圾空间,需要被回收。

ThreadLocal

  • ThreadLocal是什么:线程局部变量,该变量所有线程都可见,每个线程都可以通过set()方法改变里面的属性,通过get()方法获取里面的属性。但是每个线程对它的操作都只是自身线程可见。
  • ThreadLocal应用场景:用于在同一个线程中需要保持一致,在不同线程中可能不同的变量,比如线程优先级, 线程对数据库的连接等。
  • ThreadLocal实现原理:关键在于set(T value)get()方法
  • set
    public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                map.set(this, value);
            } else {
                createMap(t, value);
            }
        }
    
    • set方法中,关键就是一个map,这个map对象是来自于当前线程的,它将ThreadLocal的this指针作为key,并将传入的对象作为value
    • 因此隔离的原理并不在于ThreadLocal对象的独一无二,而是在于这个map是每个线程独有的,因此实现的时候,相当于把ThreadLocal这个东西装进了不同的瓶子里面。
  • get
	    public T get() {
	        Thread t = Thread.currentThread();
	        ThreadLocalMap map = getMap(t);
	        if (map != null) {
	            ThreadLocalMap.Entry e = map.getEntry(this);
	            if (e != null) {
	                @SuppressWarnings("unchecked")
	                T result = (T)e.value;
	                return result;
	            }
	        }
	        return setInitialValue();
	    }
  • 看完set之后,看get就是一个道理了,同样也是一个map,将this指针传入之后,得到的元素就是我们需要的ThreadLocal对象。

  • 总结:其实ThreadLocal对外表现像是一个变量,但是在理解的时候,最好不要认为是一个变量,而是一种方法类,这个方法类利用currentThread结合map,实现了一个对象能够适配多个线程的情况。

ThreadLocal与弱引用

  • ThreadLocal为什么会引起内存泄露?

    • 我们需要再深入了解一下set方法中的getMap,里面其实就一个returnthreadLocalsThread类中的一个Map类型变量。
    	//ThreadLocal.java
        ThreadLocalMap getMap(Thread t) {
    	    return t.threadLocals;
    	}
    	//Thread.java
    	ThreadLocal.ThreadLocalMap threadLocals = null;
    
  • ThreadLocal与弱引用

    • 此时需要再深究一下ThreadLocalMap这个容器,这个容器放入的是一个个Entry对象,而Entry类则是继承自WeakReference>类的,代码如下
    static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
    
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    
    • super(k)我们可以看到,这是调用了父类的构造函数来初始化ThreadLocal对象,这就意味着传进来的ThreadLocal对象会被弱引用指向。
    • 为什么一定要弱引用呢?
      • 如果不是弱引用的话,这里会导致内存泄漏(也就是本应该清理的时候没有清理)
      • 内存泄漏:对于ThreadLocal对象而言,除了在方法中创建的tl引用指向它,还有线程中的ThreadLocalMapkey是指向它的,这会导致一个很严重的问题,就是即便对应方法已经结束,tl已经设置为null,此时逻辑上tl已经是垃圾对象,但是因为线程中,map有一个key仍指向它,这会导致tl仍然可达,不被认为是垃圾对象,一直要等到对应线程结束才会被清理。当线程是一个持久线程时,可能会有大量的无用局部变量未被回收。
      • 不过光回收key仍然是不够的,因为当key==null的时候,map中就会存在一个Entry,这就让map中始终会有一个无法访问的value存在,因此在最后需要调用ThreadLocal.remove()方法来删除这个slot

参考

  • 强软弱虚四种引用类型与ThreadLocal原理

你可能感兴趣的:(JVM相关,JAVA基础知识)