# HG changeset patch # User tonyp # Date 1526580412 14400 # Thu May 17 14:06:52 2018 -0400 # Node ID c7269f610de789b2951dd15d3a624a34e67581a6 # Parent 1d683e243d8d8c595f61200f161b9bffe2a41d6a 8202788: Explicitly reclaim cached thread-local direct buffers at thread exit Reviewed-by: diff --git a/src/java.base/share/classes/java/lang/Thread.java b/src/java.base/share/classes/java/lang/Thread.java --- a/src/java.base/share/classes/java/lang/Thread.java +++ b/src/java.base/share/classes/java/lang/Thread.java @@ -838,6 +838,7 @@ * a chance to clean up before it actually exits. */ private void exit() { + ThreadLocal.callThreadTerminated(threadLocals); if (group != null) { group.threadTerminated(this); group = null; diff --git a/src/java.base/share/classes/java/lang/ThreadLocal.java b/src/java.base/share/classes/java/lang/ThreadLocal.java --- a/src/java.base/share/classes/java/lang/ThreadLocal.java +++ b/src/java.base/share/classes/java/lang/ThreadLocal.java @@ -28,6 +28,7 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import jdk.internal.misc.JdkThreadLocal; /** * This class provides thread-local variables. These variables differ from @@ -84,6 +85,23 @@ */ private final int threadLocalHashCode = nextHashCode(); + static void callThreadTerminated(ThreadLocalMap map) { + if (map == null) { + return; + } + + // We expect to have a very small number of hooks and + // potentially a lot of entries in the map. So, it should be + // faster to iterate over the hooks and do look-ups on the map + // instead of iterating over the map. + JdkThreadLocal.forEach(tl -> { + final ThreadLocalMap.Entry entry = map.getEntry(tl); + if (entry != null) { + tl.callThreadTerminated(entry.value); + } + }); + } + /** * The next hash code to be given out. Updated atomically. Starts at * zero. diff --git a/src/java.base/share/classes/jdk/internal/misc/JdkThreadLocal.java b/src/java.base/share/classes/jdk/internal/misc/JdkThreadLocal.java new file mode 100644 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/misc/JdkThreadLocal.java @@ -0,0 +1,57 @@ +package jdk.internal.misc; + +import java.util.Arrays; +import java.util.function.Consumer; + +public class JdkThreadLocal extends ThreadLocal { + protected void threadTerminated(T value) { + } + + public void callThreadTerminated(Object value) { + @SuppressWarnings("unchecked") + final T v = (T) value; + threadTerminated(v); + } + + public JdkThreadLocal() { + add(this); + } + + private static final class Entry { + private final JdkThreadLocal threadLocal; + + private Entry(JdkThreadLocal threadLocal) { + this.threadLocal = threadLocal; + } + + public JdkThreadLocal threadLocal() { + return threadLocal; + } + } + + private static volatile Entry[] entries = new Entry[0]; + + private static synchronized void add(JdkThreadLocal threadLocal) { + if (threadLocal == null) { + throw new IllegalArgumentException("threadLocal is null"); + } + forEach(tl -> { + if (tl == threadLocal) { + throw new IllegalArgumentException("threadLocal has already been added"); + } + }); + + final Entry entry = new Entry<>(threadLocal); + final Entry[] entries0 = Arrays.copyOf(entries, entries.length + 1); + entries0[entries0.length - 1] = entry; + entries = entries0; + } + + public static void forEach(Consumer> f) { + final Entry[] entries0 = entries; + for (Entry e: entries0) { + final JdkThreadLocal tl = e.threadLocal(); + f.accept(tl); + } + } +} diff --git a/src/java.base/share/classes/sun/nio/ch/Util.java b/src/java.base/share/classes/sun/nio/ch/Util.java --- a/src/java.base/share/classes/sun/nio/ch/Util.java +++ b/src/java.base/share/classes/sun/nio/ch/Util.java @@ -35,6 +35,7 @@ import java.util.Collection; import java.util.Iterator; import java.util.Set; +import jdk.internal.misc.JdkThreadLocal; import jdk.internal.misc.Unsafe; import sun.security.action.GetPropertyAction; import java.io.IOException; @@ -51,12 +52,17 @@ // Per-thread cache of temporary direct buffers private static ThreadLocal bufferCache = - new ThreadLocal() + new JdkThreadLocal<>() { @Override protected BufferCache initialValue() { return new BufferCache(); } + + @Override + protected void threadTerminated(BufferCache cache) { + cache.freeAll(); + } }; /** @@ -205,6 +211,25 @@ count--; return buf; } + + void freeAll() { + if (isEmpty()) { + return; + } + + for (int i = 0; i < buffers.length; i += 1) { + final ByteBuffer buf = buffers[i]; + if (buf == null) { + continue; + } + + free(buf); + // just in case + buffers[i] = null; + } + // just in case + count = 0; + } } /**