☰
ThreadLocal系列(三):用引用队列兜底内存泄漏
2026/10/8 6:04:07 网站建设 项目流程

引用队列思路兜底

当GC回收了弱引用A、B所指向的对象a、b之后,弱引用对象A和B会被加入到引用队列中
ThreadLocalMap内部并没有使用ReferenceQueue,它的清理依赖set/get/remove中的expungeStaleEntry。
下面我们用自定义map来演示,如果借助ReferenceQueue的思路,也可以实现兜底清理。

自定义map

自定义一个map,key是弱引用,value是强引用,然后调用GC回收,看下面的内容

public class TestWeak { static ReferenceQueue<Object> queue = new ReferenceQueue<>(); static class WeakMap { static class Entry extends WeakReference<String> { String value; public Entry(String key, String value) { super(key); this.value = value; } } Entry[] table = new Entry[5]; public void put(int index, String key, String value) { table[index] = new Entry(key, value); } public String get(String key) { for (Entry entry : table) { if (entry != null) { String k = entry.get(); if (k != null && k.equals(key)) { return entry.value; } } } return null; } @Override public String toString() { StringBuilder sb = new StringBuilder("["); boolean first = true; for (Entry entry : table) { if (entry != null) { // 这个get是WeakReference的父的Reference的get // get返回的就是super(key)的key String k = entry.get(); if (!first) { sb.append(","); } if (k != null) { sb.append(k).append(":").append(entry.value); } else { sb.append("null").append(":").append(entry.value); } first = false; } } sb.append("]"); return sb.toString(); } } public static void main(String[] args) { WeakMap weakMap = new WeakMap(); weakMap.put(0, new String("a"), "0"); weakMap.put(1, new String("b"), "1"); weakMap.put(2, new String("c"), "2"); weakMap.put(3, new String("d"), "3"); weakMap.put(4, new String("e"), "4"); System.out.println(weakMap); // System.gc(); // System.out.println(weakMap.get("a")); // System.out.println(weakMap.get("b")); // System.out.println(weakMap.get("c")); // System.out.println(weakMap.get("d")); // System.out.println(weakMap.get("e")); // System.out.println(weakMap); } }

下面是没调用GC和调用GC的结果
没调用GC

[a:0,b:1,c:2,d:3,e:4]

调用GC,去掉下面注释

[a:0,b:1,c:2,d:3,e:4] null null null null null [null:0,null:1,null:2,null:3,null:4]

可以看到,GC之后,弱引用对象没了,但是value还在

加入引用队列

在Entry构造方法的super中,还能传一个queue参数,可以看到GC回收a之后,a的弱引用A会被加入到引用队列里

public class TestWeak { static ReferenceQueue<Object> queue = new ReferenceQueue<>(); static class WeakMap{ static class Entry extends WeakReference<String>{ String value; public Entry(String key, String value){ // super(key); //这里的queue里放入的是entry对象 super(key, queue); this.value = value; } } public void cleanQueue(){ Object object; while ((object = queue.poll()) != null){ System.out.println("队列里有对象:" + object); } } Entry[] table = new Entry[5]; public void put(int index, String key, String value){ table[index] = new Entry(key, value); } public String get(String key){ for (Entry entry : table) { if (entry != null){ String k = entry.get(); if (k != null && k.equals(key)){ return entry.value; } } } return null; } @Override public String toString() { StringBuilder sb = new StringBuilder("["); boolean first = true; for (Entry entry : table) { if (entry != null) { // 这个get是WeakReference的父的Reference的get // get返回的就是super(key)的key String k = entry.get(); if (!first) { sb.append(","); } if (k != null) { sb.append(k).append(":").append(entry.value); } else { sb.append("null").append(":").append(entry.value); } first = false; } } sb.append("]"); return sb.toString(); } } public static void main(String[] args) { WeakMap weakMap = new WeakMap(); weakMap.put(0, new String("a"), "0"); weakMap.put(1, new String("b"), "1"); weakMap.put(2, new String("c"), "2"); weakMap.put(3, new String("d"), "3"); weakMap.put(4, new String("e"), "4"); System.out.println(weakMap); System.gc(); System.out.println(weakMap.get("a")); System.out.println(weakMap.get("b")); System.out.println(weakMap.get("c")); System.out.println(weakMap.get("d")); System.out.println(weakMap.get("e")); System.out.println(weakMap); weakMap.cleanQueue(); } }

运行结果

[a:0,b:1,c:2,d:3,e:4] null null null null null [null:0,null:1,null:2,null:3,null:4] 队列里有对象:threadlocal.TestWeak$WeakMap$Entry@1d44bcfa 队列里有对象:threadlocal.TestWeak$WeakMap$Entry@266474c2 队列里有对象:threadlocal.TestWeak$WeakMap$Entry@6f94fa3e 队列里有对象:threadlocal.TestWeak$WeakMap$Entry@5e481248 队列里有对象:threadlocal.TestWeak$WeakMap$Entry@66d3c617

Entry对key是弱引用,对value是强引用,GC之后,弱引用所指向的对象abcde没了,而entry对象(即弱引用本身)被放入了引用队列里
所以可以对引用队列里的entry对象进行处理,断开entry对value的强引用关系,这样就可以回收value了

public class TestWeak { static ReferenceQueue<Object> queue = new ReferenceQueue<>(); static class WeakMap{ static class Entry extends WeakReference<String>{ String value; public Entry(String key, String value){ // super(key); //这里的queue里放入的是entry对象 super(key, queue); this.value = value; } } public void cleanQueue(){ Object object; while ((object = queue.poll()) != null){ System.out.println("队列里有对象:" + object); for (int i = 0; i < table.length; i++) { // 将table里的所有entry和queue里现在拿到的entry对比, // 如果这俩同一个对象,就把整个entry干掉, //原来 //table[i] ──强──→ Entry ──弱──→ key // │ // └──强──→ value // table[i] = null之后 //table[i] → null // Entry ──弱──→ key(已被GC) // Entry ──强──→ value // 没人引用entry了,entry被回收,Entry被回收导致没人引用value了,value被回收 if (table[i] == object){ table[i] = null; } } } } Entry[] table = new Entry[5]; public void put(int index, String key, String value){ table[index] = new Entry(key, value); } public String get(String key){ for (Entry entry : table) { if (entry != null){ String k = entry.get(); if (k != null && k.equals(key)){ return entry.value; } } } return null; } @Override public String toString() { StringBuilder sb = new StringBuilder("["); boolean first = true; for (Entry entry : table) { if (entry != null) { // 这个get是WeakReference的父的Reference的get // get返回的就是super(key)的key String k = entry.get(); if (!first) { sb.append(","); } if (k != null) { sb.append(k).append(":").append(entry.value); } else { sb.append("null").append(":").append(entry.value); } first = false; } } sb.append("]"); return sb.toString(); } } public static void main(String[] args) { WeakMap weakMap = new WeakMap(); weakMap.put(0, new String("a"), "0"); weakMap.put(1, new String("b"), "1"); weakMap.put(2, new String("c"), "2"); weakMap.put(3, new String("d"), "3"); weakMap.put(4, new String("e"), "4"); System.out.println(weakMap); System.gc(); System.out.println(weakMap.get("a")); System.out.println(weakMap.get("b")); System.out.println(weakMap.get("c")); System.out.println(weakMap.get("d")); System.out.println(weakMap.get("e")); System.out.println(weakMap); weakMap.cleanQueue(); System.out.println(weakMap); } }

运行结果

[a:0,b:1,c:2,d:3,e:4] null null null null null [null:0,null:1,null:2,null:3,null:4] 队列里有对象:threadlocal.TestWeak$WeakMap$Entry@1d44bcfa 队列里有对象:threadlocal.TestWeak$WeakMap$Entry@266474c2 队列里有对象:threadlocal.TestWeak$WeakMap$Entry@6f94fa3e 队列里有对象:threadlocal.TestWeak$WeakMap$Entry@5e481248 队列里有对象:threadlocal.TestWeak$WeakMap$Entry@66d3c617 []

当然,在**weakMap.put(0, new String("a"), "0");**中,是new了个String,如果直接把"a“放进去了?

public class TestWeak { static ReferenceQueue<Object> queue = new ReferenceQueue<>(); static class WeakMap { static class Entry extends WeakReference<String> { String value; public Entry(String key, String value) { // super(key); //这里的queue里放入的是entry对象 super(key, queue); this.value = value; } } public void cleanQueue() { Object object; while ((object = queue.poll()) != null) { System.out.println("队列里有对象:" + object); for (int i = 0; i < table.length; i++) { // 将table里的所有entry和queue里现在拿到的entry对比, // 如果这俩同一个对象,就把整个entry干掉, //原来 //table[i] ──强──→ Entry ──弱──→ key // │ // └──强──→ value // table[i] = null;之后 //table[i] → null // Entry ──弱──→ key(已被GC) // Entry ──强──→ value // 没人引用entry了,entry被回收,Entry被回收导致没人引用value了,value被回收 if (table[i] == object) { table[i] = null; } } } } Entry[] table = new Entry[5]; public void put(int index, String key, String value) { table[index] = new Entry(key, value); } public String get(String key) { for (Entry entry : table) { if (entry != null) { String k = entry.get(); if (k != null && k.equals(key)) { return entry.value; } } } return null; } @Override public String toString() { StringBuilder sb = new StringBuilder("["); boolean first = true; for (Entry entry : table) { if (entry != null) { // 这个get是WeakReference的父的Reference的get // get返回的就是super(key)的key String k = entry.get(); if (!first) { sb.append(","); } if (k != null) { sb.append(k).append(":").append(entry.value); } else { sb.append("null").append(":").append(entry.value); } first = false; } } sb.append("]"); return sb.toString(); } } public static void main(String[] args) { WeakMap weakMap = new WeakMap(); // weakMap.put(0, new String("a"), "0"); // weakMap.put(1, new String("b"), "1"); // weakMap.put(2, new String("c"), "2"); // weakMap.put(3, new String("d"), "3"); // weakMap.put(4, new String("e"), "4"); weakMap.put(0, "a", "0"); weakMap.put(1, "b", "1"); weakMap.put(2, "c", "2"); weakMap.put(3, "d", "3"); weakMap.put(4, "e", "4"); System.out.println(weakMap); System.gc(); System.out.println(weakMap.get("a")); System.out.println(weakMap.get("b")); System.out.println(weakMap.get("c")); System.out.println(weakMap.get("d")); System.out.println(weakMap.get("e")); System.out.println(weakMap); weakMap.cleanQueue(); System.out.println(weakMap); } }

运行结果

[a:0,b:1,c:2,d:3,e:4] 0 1 2 3 4 [a:0,b:1,c:2,d:3,e:4] [a:0,b:1,c:2,d:3,e:4]

原因:

new String("a"):堆上new出来的"a" → 没有强引用 → GC回收 → entry.get()=null
现在用“a”:字符串常量池 ──强引用──→ "a" → 有强引用 → GC不回收 → entry.get()="a",再调用get方法,在for里找,就找到了对应的0。

以上就是从ReferenceQueue的角度思考如何防范OOM,相当于remove,这更是一个兜底措施。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询