Biography: Swing unruly, love life. Java Cultivator (wechat official ID: Java Cultivator), welcome to follow. Access to 2000G of detailed information on the 2020 interview questions

reference

Object o = new Object();

This o, we can call an Object reference, and the new Object() we can say creates an Object instance in memory.

When you write o=null, it simply means that o no longer refers to the object instance in the heap. It does not mean that the object instance does not exist.

Strong reference

Always alive: References such as “Object obj=new Object ()” will never be reclaimed by the garbage collector as long as strong references exist.

Soft references

There is a live chance that soft references will be associated with objects that will be listed in the collection scope for a second collection before the system is about to run out of memory. An out-of-memory exception is thrown if there is not enough memory for this collection. After JDK 1.2, a SoftReference class was provided to implement soft references.

A weak reference

Collection is dead: object instances associated with weak references can only survive until the next garbage collection occurs. When the garbage collector works, object instances associated only with weak references are reclaimed, regardless of whether there is currently sufficient memory. After JDK 1.2, WeakReference classes were provided to implement weak references.

Phantom reference

Also known as ghost reference or phantom reference, it is the weakest type of reference relationship. The existence of a virtual reference does not affect the lifetime of an object instance, nor can an object instance be obtained by virtual reference. The only purpose of setting a virtual reference association for an object is to receive a system notification when the object instance is reclaimed by the collector. After JDK 1.2, the PhantomReference class was provided to implement virtual references.

The Map in the figure above is a ThreadLocalMap. ThreadLocalMap key attributes:

Static class ThreadLocalMap {/** * where entry inherits WeakReference<ThreadLocal<? > >. * Entry is also a key-value data format. Key is a weak reference to a ThreadLocal object. Value is the actual stored value */ static class Entry extends WeakReference<ThreadLocal<? >> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; } } /** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16; /** * There may be multiple ThreadLocal objects stored in the table array. * */ private Entry[] table; /** * The number of entries in the table. */ private int size = 0; /** * The next size value at which to resize. */ private int threshold; // Default to 0}Copy the code

ThreadLocalMap is actually stored in the Thread object. Each Thread maintains a ThreadLocalMap map whose key is the ThreadLocal instance itself and whose value is the actual Object that needs to be stored.

public class Thread implements Runnable { ThreadLocal.ThreadLocalMap threadLocals = null; }Copy the code

Take a quick look at the procedure for storing data using the set method.

public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) { map.set(this, value); } else { createMap(t, value); } }ThreadLocalMap getMap(Thread t) { return t.threadLocals; ThreadLocals is an attribute in the Thread object}Copy the code

ThreadLocal itself does not store values; it simply acts as a key for the thread to retrieve values from ThreadLocalMap. Note the dotted lines in the figure, which indicate that ThreadLocalMap uses a weak reference to ThreadLocal as the Key, and that the weakly referenced object is reclaimed during GC.

A ThreadLocalMap uses a weak reference to a ThreadLocal as its key. If a ThreadLocal has no external strong reference to it, then the ThreadLocal will be reclaimed during GC. There is no way to access the values of these null-key entries. If the current thread does not terminate, the values of these null-key entries will always have a strong reference chain: Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value can never be reclaimed, causing a memory leak.

Is on the whole, ThreadLocal used inside the presence of a weak reference of the map, the type of the map is a ThreadLocal ThreadLocalMap. The key in the map as a ThreadLocal instance. The Map does use weak references, but only for keys. Each key has a weak reference to threadLocal. When a ThreadLocal instance is null, there are no strong references to the threadLocal instance, so threadLocal will be collected by the GC.

However, our value cannot be reclaimed, and the value is never accessed, so there is a memory leak. There is a strong reference connected from the current thread. After the current thread ends, the current thread will not exist in the stack, the strong reference will be disconnected, and the current thread and Map value will be collected by GC. The best thing to do is to call threadLocal’s remove method, which I’ll talk about later.

Get (),set(), and remove() remove all null-key values from the ThreadLocalMap. This was also mentioned in the previous section!

But these passive precautions are no guarantee against memory leaks:

(1) Static ThreadLocal extends the lifetime of ThreadLocal and may cause memory leaks. (2) If ThreadLocal is allocated and the get(),set(), and remove() methods are not called, then the memory will leak because the memory will always be there.Copy the code

Why weak references? Is OOM a weak reference pot?

1. The apparent root cause of the memory leak is the use of weak references. Most of the articles on the web have focused on analyzing memory leaks caused by ThreadLocal’s use of weak references, but another question is also worth pondering: why weak references instead of strong references?

Let’s start with what the official document says:

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. To cope with very large and long-running uses, hash tables use weakly referenced keys.Copy the code

Let’s discuss the following two cases:

(1) The key uses a strong reference: The referenced ThreadLocal object is reclaimed, but ThreadLocalMap still holds a strong reference to ThreadLocal. Without manual deletion, ThreadLocal will not be reclaimed, resulting in Entry memory leakage.

(2) Key uses weak references: The referenced ThreadLocal object is reclaimed. Since ThreadLocalMap holds a weak reference to ThreadLocal, the ThreadLocal is reclaimed even if it is not manually deleted. The value is cleared the next time ThreadLocalMap calls set, get, and remove.

Since ThreadLocalMap has the same lifetime as Thread, if the corresponding key is not manually removed, memory leaks will occur, but using weak references provides an additional layer of protection: Weak reference ThreadLocal does not leak memory and the corresponding value is cleared the next time ThreadLocalMap calls set, GET, or remove.

Thus, the root cause of a ThreadLocal memory leak is that since ThreadLocalMap has the same lifetime as Thread, a memory leak would result if the corresponding key was not manually removed, not because of weak references.

ThreadLocal best practices

1. How to avoid memory leaks from ThreadLocal?

The answer is to call ThreadLocal’s remove() method every time ThreadLocal is finished using it.

In the case of thread pools, not cleaning up ThreadLocal in a timely manner is not only a memory leak problem, but more importantly, it can lead to problems with business logic. So using ThreadLocal is like unlocking a lock and cleaning it up when you’re done.

Note:

Not all places that use ThreadLocal are removed () at the end, and their lifetime may need to be as long as the lifetime of the project, so choose appropriately to avoid business logic errors! But the first thing to keep in mind is that the size of the data stored in ThreadLocal is not very large!

ThreadLocal memory leak code demonstration

Do not use the ThreadLocal

The following program creates a thread pool with five threads. Each thread request a heap size of 5M.

public class MyThreadLocalOOM1 { public static final Integer SIZE = 500; static ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>()); Static class LocalVariable {// Total 5M private byte[] locla = new byte[1024 * 1024 * 5]; } public static void main(String[] args) { try { for (int i = 0; i < SIZE; i++) { executor.execute(() -> { new LocalVariable(); System.out.println(" start executing "); }); Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); }}}Copy the code

The jagged blue area in the figure below is the amount of space the heap has used. You can see that it is in the 0-70 range because each thread is requesting 5M space. After a short period of time, youngGC is triggered and memory is freed. At 19:30:36 I manually triggered a GC and saw that the heap space was almost free. LocalVariable is released, and no memory leakage occurs.

Use ThreadLocal, but do not remove

public class MyThreadLocalOOM2 { public static final Integer SIZE = 500; static ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>()); Static class LocalVariable {// Total 5M private byte[] locla = new byte[1024 * 1024 * 5]; } static ThreadLocal<LocalVariable> local = new ThreadLocal<>(); public static void main(String[] args) { try { for (int i = 0; i < SIZE; i++) { executor.execute(() -> { local.set(new LocalVariable()); System.out.println(" start executing "); }); Thread.sleep(100); } local = null; // Null still causes a memory leak} catch (InterruptedException e) {e.printStackTrace(); }}}Copy the code

The above code defines the static ThreadLocal variable local, but sets local to null when the for loop completes. Normal objects, which have no strong references at this point, are reclaimed during GC. But as you can see from the figure below, even after the for loop ends and the GC is triggered manually, the heap still occupies about 25MB of space, which is exactly the sum of LocalVariable objects for five threads in the thread pool. So there was a memory leak. See ThreadLocal Memory Leak Cause Analysis [1]

Use Thread Local and remove

public class MyThreadLocalOOM3 { public static final Integer SIZE = 500; static ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>()); Static class LocalVariable {// Total 5M private byte[] locla = new byte[1024 * 1024 * 5]; } final static ThreadLocal<LocalVariable> local = new ThreadLocal<>(); public static void main(String[] args) { try { for (int i = 0; i < SIZE; i++) { executor.execute(() -> { local.set(new LocalVariable()); System.out.println(" start executing "); local.remove(); }); Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); }}}Copy the code

Remove () is called to remove objects from threadLocal after thread compliance is complete. As you can see in the figure below, after manually triggering GC, all memory is freed without memory leaks.

Single-threaded demonstration of memory leaks

public class MyThreadLocalOOM4 { public static final Integer SIZE = 500; Private byte[] locla = new byte[1024 * 1024 * 50]; private byte[] locla = new byte[1024 * 1024 * 50]; } static ThreadLocal<LocalVariable> local = new ThreadLocal<>(); static LocalVariable localVariable; public static void main(String[] args) throws InterruptedException { try { TimeUnit.SECONDS.sleep(2); localVariable = new LocalVariable(); local.set(new LocalVariable()); System.out.println(" start executing "); Thread.sleep(100); local = null; localVariable = null; } catch (InterruptedException e) { e.printStackTrace(); } while (true) { TimeUnit.SECONDS.sleep(1); }}}Copy the code

As you can see from the result, the 50MB of localVariable space is freed, but the 50MB of ThreadLocal space is not freed.