mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +02:00
8202788: Explicitly reclaim cached thread-local direct buffers at thread exit
Add internal TerminatingThreadLocal and use it to free cached thread-local direct buffers and nio-fs native buffers Reviewed-by: tonyp, alanb
This commit is contained in:
parent
578576f523
commit
6ec2cfcc49
8 changed files with 398 additions and 47 deletions
|
@ -36,6 +36,8 @@ import java.util.HashMap;
|
|||
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 @@ class Thread implements Runnable {
|
|||
* 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;
|
||||
|
|
|
@ -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;
|
||||
|
@ -170,6 +172,19 @@ public class ThreadLocal<T> {
|
|||
return setInitialValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -180,10 +195,14 @@ public class ThreadLocal<T> {
|
|||
T value = initialValue();
|
||||
Thread t = Thread.currentThread();
|
||||
ThreadLocalMap map = getMap(t);
|
||||
if (map != null)
|
||||
if (map != null) {
|
||||
map.set(this, value);
|
||||
else
|
||||
} else {
|
||||
createMap(t, value);
|
||||
}
|
||||
if (this instanceof TerminatingThreadLocal) {
|
||||
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -199,10 +218,11 @@ public class ThreadLocal<T> {
|
|||
public void set(T value) {
|
||||
Thread t = Thread.currentThread();
|
||||
ThreadLocalMap map = getMap(t);
|
||||
if (map != null)
|
||||
if (map != null) {
|
||||
map.set(this, value);
|
||||
else
|
||||
} else {
|
||||
createMap(t, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -218,8 +238,9 @@ public class ThreadLocal<T> {
|
|||
*/
|
||||
public void remove() {
|
||||
ThreadLocalMap m = getMap(Thread.currentThread());
|
||||
if (m != null)
|
||||
if (m != null) {
|
||||
m.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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<T> extends ThreadLocal<T> {
|
||||
|
||||
@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 TerminatingThreadLocal
|
||||
*
|
||||
* @param tl the ThreadLocal to register
|
||||
*/
|
||||
public static void register(TerminatingThreadLocal<?> tl) {
|
||||
REGISTRY.get().add(tl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister given 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<Collection<TerminatingThreadLocal<?>>> REGISTRY =
|
||||
new ThreadLocal<>() {
|
||||
@Override
|
||||
protected Collection<TerminatingThreadLocal<?>> initialValue() {
|
||||
return Collections.newSetFromMap(new IdentityHashMap<>(4));
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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.security.PrivilegedAction;
|
|||
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 @@ public class Util {
|
|||
private static final long MAX_CACHED_BUFFER_SIZE = getMaxCachedBufferSize();
|
||||
|
||||
// Per-thread cache of temporary direct buffers
|
||||
private static ThreadLocal<BufferCache> bufferCache =
|
||||
new ThreadLocal<BufferCache>()
|
||||
{
|
||||
private static ThreadLocal<BufferCache> 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
package sun.nio.fs;
|
||||
|
||||
import jdk.internal.misc.TerminatingThreadLocal;
|
||||
import jdk.internal.misc.Unsafe;
|
||||
|
||||
/**
|
||||
|
@ -37,8 +38,21 @@ class NativeBuffers {
|
|||
private static final Unsafe unsafe = Unsafe.getUnsafe();
|
||||
|
||||
private static final int TEMP_BUF_POOL_SIZE = 3;
|
||||
private static ThreadLocal<NativeBuffer[]> threadLocal =
|
||||
new ThreadLocal<NativeBuffer[]>();
|
||||
private static ThreadLocal<NativeBuffer[]> 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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue