--- old/src/java.base/share/classes/java/lang/Thread.java 2018-06-06 20:45:06.812558509 +0200 +++ new/src/java.base/share/classes/java/lang/Thread.java 2018-06-06 20:45:06.711560225 +0200 @@ -36,6 +36,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.LockSupport; + +import jdk.internal.misc.TerminatingThreadLocal; import sun.nio.ch.Interruptible; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; @@ -838,6 +840,9 @@ * a chance to clean up before it actually exits. */ private void exit() { + if (TerminatingThreadLocal.REGISTRY.isPresent()) { + TerminatingThreadLocal.threadTerminated(); + } if (group != null) { group.threadTerminated(this); group = null; --- old/src/java.base/share/classes/java/lang/ThreadLocal.java 2018-06-06 20:45:07.137552986 +0200 +++ new/src/java.base/share/classes/java/lang/ThreadLocal.java 2018-06-06 20:45:07.038554668 +0200 @@ -24,6 +24,8 @@ */ package java.lang; +import jdk.internal.misc.TerminatingThreadLocal; + import java.lang.ref.*; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; @@ -171,6 +173,19 @@ } /** + * Returns {@code true} if there is a value in the current thread's copy of + * this thread-local variable, even if that values is {@code null}. + * + * @return {@code true} if current thread has associated value in this + * thread-local variable; {@code false} if not + */ + boolean isPresent() { + Thread t = Thread.currentThread(); + ThreadLocalMap map = getMap(t); + return map != null && map.getEntry(this) != null; + } + + /** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * @@ -184,6 +199,7 @@ map.set(this, value); else createMap(t, value); + TerminatingThreadLocal.register(this); return value; } --- old/src/java.base/share/classes/sun/nio/ch/Util.java 2018-06-06 20:45:07.449547684 +0200 +++ new/src/java.base/share/classes/sun/nio/ch/Util.java 2018-06-06 20:45:07.348549400 +0200 @@ -26,6 +26,7 @@ package sun.nio.ch; import java.io.FileDescriptor; +import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.nio.ByteBuffer; @@ -35,9 +36,10 @@ import java.util.Collection; import java.util.Iterator; import java.util.Set; + +import jdk.internal.misc.TerminatingThreadLocal; import jdk.internal.misc.Unsafe; import sun.security.action.GetPropertyAction; -import java.io.IOException; public class Util { @@ -50,13 +52,18 @@ private static final long MAX_CACHED_BUFFER_SIZE = getMaxCachedBufferSize(); // Per-thread cache of temporary direct buffers - private static ThreadLocal bufferCache = - new ThreadLocal() - { + private static ThreadLocal bufferCache = new TerminatingThreadLocal<>() { @Override protected BufferCache initialValue() { return new BufferCache(); } + @Override + protected void threadTerminated(BufferCache cache) { // will never be null + while (!cache.isEmpty()) { + ByteBuffer bb = cache.removeFirst(); + free(bb); + } + } }; /** --- old/src/java.base/share/classes/sun/nio/fs/NativeBuffers.java 2018-06-06 20:45:07.758542433 +0200 +++ new/src/java.base/share/classes/sun/nio/fs/NativeBuffers.java 2018-06-06 20:45:07.658544133 +0200 @@ -25,6 +25,7 @@ package sun.nio.fs; +import jdk.internal.misc.TerminatingThreadLocal; import jdk.internal.misc.Unsafe; /** @@ -37,8 +38,21 @@ private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final int TEMP_BUF_POOL_SIZE = 3; - private static ThreadLocal threadLocal = - new ThreadLocal(); + private static ThreadLocal threadLocal = new TerminatingThreadLocal<>() { + @Override + protected void threadTerminated(NativeBuffer[] buffers) { + // threadLocal may be initialized but with initialValue of null + if (buffers != null) { + for (int i = 0; i < TEMP_BUF_POOL_SIZE; i++) { + NativeBuffer buffer = buffers[i]; + if (buffer != null) { + buffer.free(); + buffers[i] = null; + } + } + } + } + }; /** * Allocates a native buffer, of at least the given size, from the heap. --- /dev/null 2018-06-06 18:45:34.877978224 +0200 +++ new/src/java.base/share/classes/jdk/internal/misc/TerminatingThreadLocal.java 2018-06-06 20:45:07.968538865 +0200 @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.misc; + +import java.util.Collection; +import java.util.Collections; +import java.util.IdentityHashMap; + +/** + * A thread-local variable that is notified when a thread terminates and + * it has been initialized in the terminating thread (even if it was + * initialized with a null value). + */ +public class TerminatingThreadLocal extends ThreadLocal { + + @Override + public void set(T value) { + super.set(value); + register(this); + } + + @Override + public void remove() { + super.remove(); + unregister(this); + } + + /** + * Invoked by a thread when terminating and this thread-local has an associated + * value for the terminating thread (even if that value is null), so that any + * native resources maintained by the value can be released. + * + * @param value current thread's value of this thread-local variable + * (may be null but only if null value was explicitly initialized) + */ + protected void threadTerminated(T value) { + } + + // following methods and field are implementation details and should only be + // called from the corresponding code int Thread/ThreadLocal class. + + /** + * Invokes the TerminatingThreadLocal's {@link #threadTerminated()} method + * on all instances registered in current thread. + */ + public static void threadTerminated() { + for (TerminatingThreadLocal ttl : REGISTRY.get()) { + ttl._threadTerminated(); + } + } + + private void _threadTerminated() { threadTerminated(get()); } + + /** + * Register given thread local if it is a TerminatingThreadLocal + * + * @param tl the ThreadLocal to register + */ + public static void register(ThreadLocal tl) { + if (tl instanceof TerminatingThreadLocal) { + REGISTRY.get().add((TerminatingThreadLocal) tl); + } + } + + /** + * Unregister given thread local if it is a TerminatingThreadLocal + * + * @param tl the ThreadLocal to unregister + */ + private static void unregister(TerminatingThreadLocal tl) { + REGISTRY.get().remove(tl); + } + + /** + * a per-thread registry of TerminatingThreadLocal(s) that have been registered + * but later not unregistered in a particular thread. + */ + public static final ThreadLocal>> REGISTRY = + new ThreadLocal<>() { + @Override + protected Collection> initialValue() { + return Collections.newSetFromMap(new IdentityHashMap<>(4)); + } + }; +}