ThreadLocal和Java对象的四种引用

一、Java对象的四种引用

1.引用分类

分为强引用、软引用、弱引用、虚引用

2.强引用

最常用的普通引用,只有没有任何一个变量指向new出的M对象时,它才会被GC回收。

M m = new M();

当对象被回收时,会调用finalize()方法。这个方法一般不要写

3.软引用

SortReference<byte[]> m = new SortReference<>(new byte[1024*1024*10]);
System.out.println(m.get());//不为空
System.gc();
try {
    Thread.sleep(500);
}catch(InterruptedException e){
    e.printStackTrace();
}
System.out.println(m.get());//不为空

//再分配一个数组,heap将装不下,这时候系统会垃圾回收,先回收一次,如果不够,会把软引用的对象回收
byte[] b = new byte[1024*1024*15];
System.out.println(m.get());//为空

栈中变量m指向SoftReference类型的实例对象,该对象指向一个10M的字节数组——这个引用是软引用。(下图中右侧波浪线部分)
在这里插入图片描述
拿到软引用的内容,调用get方法即可。
这个程序是带参数的 -Xmx20M,JVM分配的空间为20M,一开始调用GC,软引用也不会回收。但是后来又创建了15M的数组,堆内存分配不下,此时软引用被回收。
软引用主要用在缓存上面。

4.弱引用

涉及到ThreadLocal的典型应用。

public static void main(String[] args){
    WeakReference<M> m = new WeakReference<>(new M());
    
    System.out.println(m.get());//不为空
    System.gc();
    System.out.println(m.get());//为空,并调用finalize方法
    
    ThreadLocal<M> tl = new ThreadLocal();
    tl.set(new M());
    tl.remove();
}

经过垃圾回收,弱引用就直接被回收了。

5.ThreadLocal——线程本地变量

每个线程自己独立拥有,线程存在,对象就一直存在。

(1)ThreadLocal属于线程独有示例

在第二个线程中向tl中set了一个Person对象。在第一个线程中,两秒以后区读它。结果输出空值。

public class ThreadLocalTest{
    static ThreadLocal<Person> tl = new ThreadLocal<>();
    
    public static void main(String[] args){
        new Thread(()->{
            try{
                TimeUnit.SECONDS.sleep(2);                            
            } catch (InterruptedException e){
                e.printStackTrace();            
            }
            System.out.println(tl.get());//空值     
        }).start();    
        
        new Thread(()->{
            try{
                TimeUnit.SECONDS.sleep(1);                            
            } catch (InterruptedException e){
                e.printStackTrace();              
            }
            tl.set(new Person());       
        }).start();
    }
    
    static class Person{
        String name = "zhangsan";    
    }
}

ThreadLocal往里面set任何值,一定是和当前线程有关系,和其他线程没有关系。

(2)ThreadLocal的使用实例

Spring中有个注解@Transactional ,假如将m()方法标记为事务。

@Transactional
m(){
    m1();
    m2();
}

假设m1 m2都会访问数据库,由于它们位于同一个transaction中,所以必须保证拿到的数据库连接是同一个,就是用ThreadLocal实现的。因为ThreadLocal中的connection是和当前线程有关系的。从当前线程拿,拿到的永远都是同一个。

(3)ThreadLocal的set方法源码

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);//ThreadLocalMap 的set方法
    else
        createMap(t, value);
}

set方法是将传入的对象,set到了一个map里面,当前的threadLocal这个对象作为key,传入的参数作为value。
getMap方法:

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

t是当前线程,返回的是当前线程的threadLocals变量

ThreadLocal.ThreadLocalMap threadLocals = null;
static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        private static final int INITIAL_CAPACITY = 16;
        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;
        ......
        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

Thread类中有一个成员变量threadLocals,里面装的是和当前线程相关的threadLocal。threadLocals是ThreadLocal的内部静态类ThreadLocalMap,里面用了一个Entry数组保存键值对。
在这里插入图片描述
key是threadLocal对象,在线程中new几个ThreadLocal就有几个key。

(4)ThreadLocalMap 的set方法

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);//new了一个Entry对象
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

参数是一个key value对,set方法是new了一个Entry对象,然后把这个Entry对象放到了ThreadLocalMap里(实际是一个Entry数组)。

(5)Entry的源码

Entry是ThreadLocalMap的一个静态子类

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

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

Entry继承了WeakReference类,说明Entry是一个弱引用。
当创建Entry对象时,调用了super(k), new 了一个WeakReference对象,这个WeakReference的弱引用指向key。
在这里插入图片描述
这里为什么要用弱引用指向ThreadLocal对象?(上图虚线部分)
防止内存泄漏。ThreadLocal用一个弱引用解决了垃圾回收问题。
当tl被回收,ThreadLocal对象(ThreadLocalMap对应记录的key)会自动回收(因为弱引用),但map中的记录不会自动被删掉。所以一旦ThreadLocal对象不用了,必须调用remove方法(下图20行),不然还是会产生内存泄漏(value的值不再使用但如果不回收还是会保存)。
在这里插入图片描述

6.虚引用

(1)虚引用特点

虚引用比弱引用还弱,永远get不到

public class T04_PhantomReference{
    private static final List<Object> LIST = new LinkedList<>();
    private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();
    
    public static void main(String[] args){
        PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);
        
        new Thread(() -> {
              while(true){
                  LIST.add(new byte[1024*1024]);//在堆中一直占用内存
                  try{
                      Thread.sleep(1000);                                        
                  } catch (InterruptedException e){
                      e.printStackTrace();
                      Thread.currentThread().interrupt();                  
                  }
                  System.out.println(phantomReference.get());//为空                               
              }                          
        }).start();
        
        //此线程可以当作监控堆外内存的垃圾回收,一直从QUEUE中取元素,如果取到,说明有虚引用对象被回收了。
        new Thread(()->{
              while(true){
                  Reference<? extends M> poll = QUEUE.poll();
                  if(poll != null){
                      System.out.println("---虚引用对象被jvm回收了---" + poll);                  
                  }              
              }      
        }).start();                                                                                                                                                                                                                                                                                    
    }
}

虚引用首先有一个队列,创建虚引用时需要同时指定这个虚引用的队列是哪一个。

(2)NIO

java api : NIO new IO,从网络上访问数据,网卡写入操作系统的buffer中,如果JVM需要,再复制到JVM管理的内存中。JVM要写数据,先写入到os的buffer中,再复制到网卡。中间多了一步没必要的操作,就是从JVM管理内存向操作系统内存这块复制,这个复制过程其实是可以省略的。
在这里插入图片描述
在NIO中就提供了一种直接内存管理,或者叫堆外内存管理。JVM平时管理的内存是一个堆,但是如果启用NIO之后,向网络上写数据时,是可以直接管理堆外的操作系统的那块内存。不需要在JVM里面写一遍数据再拷贝一遍到OS内存了,这叫zero copy,它的效率会高很多。
这里会有java的对象代表这块内存,DirectByteBuffer,叫直接内存或堆外内存。这块内存不在JVM内存范围,所以GC无法回收。所以,在垃圾回收器中,有一个垃圾线程,专门监听有哪些堆外内存的DirectByteBuffer对象。当它被回收时,指向的堆外内存必须随之删掉(否则会发生内存泄漏)。
什么时候能知道DirectByteBuffer对象没有了呢?在它上面加一个虚引用,这个虚引用只有一个作用,当这个对象被回收时,它的某一个信息会被加到队列Queue里。所以GC只要检测什么时候Queue有新内容,说明某一个直接内存管理的对象被回收了,同时就把对应的堆外内存进行处理。
所以,虚引用只有一个作用,管理堆外内存。谁来管理堆外内存:JVM虚拟机专门的GC线程。

(3)虚引用作用:管理堆外内存

在这里插入图片描述
DirectByteBuffer分配的内存空间是在操作系统的外面,是操作系统的内存。

netty里面有一个zero copy的技术,就是用它来实现的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值