ThreadLocal causes memory leaks with weak references ThreadLocal causes memory leaks with weak references ThreadLocal causes memory leaks with weak references

A weak reference

A weak reference is the holder of a reference to an object called the Referent. With weak references, a reference to the referent can be maintained without preventing it from being garbage collected. When the garbage collector tracks the heap, if there are only weak references to an object, the referent becomes a candidate for garbage collection as if there were no remaining references, and all remaining weak references are cleared. (Objects with only weak references are called weakly reachable.

Here’s an example:

public class Car { private String name; public Car(String name) { this.name = name; } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("car gc"); } public static void main(String[] args) { Car car = new Car("baoma"); WeakReference<Car> carWeakReference = new WeakReference<Car>(car); System.out.println(car); System.out.println(carWeakReference.get()); car = null; System.gc(); System.out.println(carWeakReference.get()); }}Copy the code

In the above example, car is a strong reference to the CAR object (Referent) and is GC Root. CarWeakReference is a weak reference of a reference CAR that holds the referent. When CAR =null, weak references do not prevent car objects from being collected by GC.

com.xiaowei.Car@5cad8086 com.xiaowei.Car@5cad8086 null CAR GC // Car objects have been reclaimedCopy the code

Global Map causes memory leak

public class SocketManager { private Map<Socket, User> m = new HashMap<Socket, User>(); public void setUser(Socket s, User u) { m.put(s, u); } public User getUser(Socket s) { return m.get(s); } public void removeUser(Socket s) { m.remove(s); } public boolean isEmpty() { return m.isEmpty(); }}Copy the code

A common scenario is that the Socket on the client needs to be bound to the User. In this scenario, we expect the mapping to be removed from the Map after the Socket is closed, but unless we manually remove the mapping, the Socket and User objects will never be collected by GC.

public static void main(String[] args) { SocketManager socketManager = new SocketManager(); int i = 0; while (i < 5000) { Socket s = new Socket(); User u = new User(); socketManager.setUser(s, u); s = null; u = null; System.gc(); }}Copy the code

As you can see, even if the reference is null, the Socket and User cannot be collected by GC due to the reference relationship of the Map Node.

public static void main(String[] args) throws InterruptedException { SocketManager socketManager = new SocketManager(); int i = 0; while (i < 5000) { Socket s = new Socket(); User u = new User(); socketManager.setUser(s, u); // Remove mapping socketManager.removeUser(s); s = null; u = null; System.gc(); }}Copy the code

After the Map mapping is removed, GC is performed normally.

Normally we would use HashMap, but is there a way to automatically delete the Map after the Socket is closed? The answer is to use weak references.

private static ReferenceQueue<Socket> rq = new ReferenceQueue<Socket>(); public static void main(String[] args) throws InterruptedException { SocketManager socketManager = new SocketManager(); Thread thread = new Thread(() -> { try { int cnt = 0; WeakReference<Socket> k; while ((k = (WeakReference) rq.remove()) ! = null) {System. Out. Println ((cnt++) + "recalled:" + k); Socketmanager.removeuser (k); Catch (InterruptedException e) {// End loop}}); thread.setDaemon(true); thread.start(); Socket s = new Socket(); User u = new User(); WeakReference<Socket> weakReference = new WeakReference<Socket>(s, rq); socketManager.setUser(weakReference, u); // help gc s = null; System.gc(); TimeUnit.SECONDS.sleep(1); System.out.println("map.size->" + socketManager.size()); }Copy the code

Output:

0 recycling: Java. Lang. Ref. WeakReference @ 5 fd0d5ae map. Size - > 0Copy the code

Using WeakHashMap

WeakHashMap hosts mapped keys with weak references, which allows key objects to be garbage collected when applications are no longer using them. Get () implementations can distinguish dead mappings from live mappings based on whether WeakReference.get() returns null. But this is only half of what needs to be done to prevent Map memory consumption from increasing over the life of the application, and some work needs to be done to remove dead items from the Map once the key objects are collected. Otherwise, the Map is filled with entries that correspond to dead keys. Although this is not visible to the application, it still causes the application to run out of memory, because map.entry and value objects are not collected even if the key is collected.

WeakHashMap has a private method called expungeStaleEntries(), which is called in most Map operations and removes all invalid references from the reference queue and the associated mapping map.entry.

private Map<Socket, User> m = new WeakHashMap<Socket, User>(); public static void main(String[] args) throws InterruptedException { SocketManager socketManager = new SocketManager(); Socket s = new Socket(); User u = new User(); socketManager.setUser(s, u); // help gc s = null; System.gc(); TimeUnit.SECONDS.sleep(2); // expungeStaleEntries will be called to delete map.entry System.out.println(socketManager.isempty ()); }Copy the code

Output:

Socket GC
true
Copy the code

Why does ThreadLocal leak memory

The principle of TheadLocal is to maintain a ThreadLocalMap within each Thread. The ThreadLocal object is the key of the ThreadLocalMap.

Static class ThreadLocalMap {/** * This Entry in the hash map extends WeakReference by using its primary REF field as the key (it is always a *ThreadLocal object). Note that an empty key (i.e. entry.get() == null) means that the key is no longer referenced, * so entries can be removed from the table. In the code below, such entries are referred to as "stale entries." */ static class Entry extends WeakReference<ThreadLocal<? >> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; }}}Copy the code

So when a strong reference to a ThreadLocal object is NULL, threadLocalMap. Entry and ThreadLocal objects are GC, but value is not.

Reference:

[^] : Plug memory leaks with weak references