ThreadLocal和Java对象的四种引用
创始人
2025-05-29 11:34:43

文章目录

  • 一、Java对象的四种引用
    • 1.引用分类
    • 2.强引用
    • 3.软引用
    • 4.弱引用
    • 5.ThreadLocal——线程本地变量
      • (1)ThreadLocal属于线程独有示例
      • (2)ThreadLocal的使用实例
      • (3)ThreadLocal的set方法源码
      • (4)ThreadLocalMap 的set方法
      • (5)Entry的源码
    • 6.虚引用
      • (1)虚引用特点
      • (2)NIO
      • (3)虚引用作用:管理堆外内存

一、Java对象的四种引用

1.引用分类

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

2.强引用

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

M m = new M();

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

3.软引用

SortReference 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 = new WeakReference<>(new M());System.out.println(m.get());//不为空System.gc();System.out.println(m.get());//为空,并调用finalize方法ThreadLocal tl = new ThreadLocal();tl.set(new M());tl.remove();
}

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

5.ThreadLocal——线程本地变量

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

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

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

public class ThreadLocalTest{static ThreadLocal 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方法elsecreateMap(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> {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> {/** 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 LIST = new LinkedList<>();private static final ReferenceQueue QUEUE = new ReferenceQueue<>();public static void main(String[] args){PhantomReference 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 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的技术,就是用它来实现的。

相关内容

热门资讯

实测分享“玄灵大厅透视挂下载”... 您好:玄灵大厅这款游戏可以开挂,确实是有挂的,需要软件加微信【5951795】,很多玩家在玄灵大厅这...
实测分享“九五娱乐真的有挂吗”... 您好,九五娱乐辅助软件这款游戏可以开挂的,确实是有挂的,需要了解加微【3696223】很多玩家在这款...
今日重大通报“毛豆大厅其实真有... 您好:毛豆大厅这款游戏可以开挂,确实是有挂的,需要软件加微信【5951795】,很多玩家在毛豆大厅这...
玩家实测“wepoker到底有... 您好:wepoker这款游戏可以开挂,确实是有挂的,需要软件加微信【5951795】,很多玩家在we...
今日分享“掌心麻将圈能不能开挂... 【无需打开直接搜索微信;3696223】 操作使用教程:1.亲,实际上掌心麻将圈是可以开挂的,确实有...