Motivation: PooledByteBufAllocator uses a PoolThreadCache per Thread that allocates / deallocates to minimize the performance overhead. This PoolThreadCache is trimmed after X allocations to free up buffers that are not allocated for a long time. This works out quite well when the app continues to allocate but fails if the app stops to allocate frequently (for whatever reason) and so a lot of memory is wasted and not given back to the arena / freed. Modifications: - Add a ThreadExecutorMap that offers multiple methods that wrap Runnable / ThreadFactory / Executor and allow to call ThreadExecutorMap.currentEventExecutor() to get the current executing EventExecutor for the calling Thread. - Use these methods in the constructors of our EventExecutor implementations (which also covers the EventLoop implementations) - Add io.netty.allocator.cacheTrimIntervalMillis system property which can be used to specify a fixed rate / interval on which we should try to trim the PoolThreadCache for a EventExecutor that allocates. - Add PooledByteBufAllocator.trimCurrentThreadCache() to allow the user to trim the cache of the calling thread manually. - Add testcases - Introduce FastThreadLocal.getIfExists() Result: Allow to better / more frequently trim PoolThreadCache and so give back memory to the area / system.
277 lines
9.8 KiB
Java
277 lines
9.8 KiB
Java
/*
|
|
* Copyright 2014 The Netty Project
|
|
*
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at:
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
package io.netty.util.concurrent;
|
|
|
|
import io.netty.util.internal.InternalThreadLocalMap;
|
|
import io.netty.util.internal.PlatformDependent;
|
|
|
|
import java.util.Collections;
|
|
import java.util.IdentityHashMap;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* A special variant of {@link ThreadLocal} that yields higher access performance when accessed from a
|
|
* {@link FastThreadLocalThread}.
|
|
* <p>
|
|
* Internally, a {@link FastThreadLocal} uses a constant index in an array, instead of using hash code and hash table,
|
|
* to look for a variable. Although seemingly very subtle, it yields slight performance advantage over using a hash
|
|
* table, and it is useful when accessed frequently.
|
|
* </p><p>
|
|
* To take advantage of this thread-local variable, your thread must be a {@link FastThreadLocalThread} or its subtype.
|
|
* By default, all threads created by {@link DefaultThreadFactory} are {@link FastThreadLocalThread} due to this reason.
|
|
* </p><p>
|
|
* Note that the fast path is only possible on threads that extend {@link FastThreadLocalThread}, because it requires
|
|
* a special field to store the necessary state. An access by any other kind of thread falls back to a regular
|
|
* {@link ThreadLocal}.
|
|
* </p>
|
|
*
|
|
* @param <V> the type of the thread-local variable
|
|
* @see ThreadLocal
|
|
*/
|
|
public class FastThreadLocal<V> {
|
|
|
|
private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
|
|
|
|
/**
|
|
* Removes all {@link FastThreadLocal} variables bound to the current thread. This operation is useful when you
|
|
* are in a container environment, and you don't want to leave the thread local variables in the threads you do not
|
|
* manage.
|
|
*/
|
|
public static void removeAll() {
|
|
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
|
|
if (threadLocalMap == null) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
|
|
if (v != null && v != InternalThreadLocalMap.UNSET) {
|
|
@SuppressWarnings("unchecked")
|
|
Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
|
|
FastThreadLocal<?>[] variablesToRemoveArray =
|
|
variablesToRemove.toArray(new FastThreadLocal[0]);
|
|
for (FastThreadLocal<?> tlv: variablesToRemoveArray) {
|
|
tlv.remove(threadLocalMap);
|
|
}
|
|
}
|
|
} finally {
|
|
InternalThreadLocalMap.remove();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the number of thread local variables bound to the current thread.
|
|
*/
|
|
public static int size() {
|
|
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
|
|
if (threadLocalMap == null) {
|
|
return 0;
|
|
} else {
|
|
return threadLocalMap.size();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Destroys the data structure that keeps all {@link FastThreadLocal} variables accessed from
|
|
* non-{@link FastThreadLocalThread}s. This operation is useful when you are in a container environment, and you
|
|
* do not want to leave the thread local variables in the threads you do not manage. Call this method when your
|
|
* application is being unloaded from the container.
|
|
*/
|
|
public static void destroy() {
|
|
InternalThreadLocalMap.destroy();
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
|
|
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
|
|
Set<FastThreadLocal<?>> variablesToRemove;
|
|
if (v == InternalThreadLocalMap.UNSET || v == null) {
|
|
variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<>());
|
|
threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
|
|
} else {
|
|
variablesToRemove = (Set<FastThreadLocal<?>>) v;
|
|
}
|
|
|
|
variablesToRemove.add(variable);
|
|
}
|
|
|
|
private static void removeFromVariablesToRemove(
|
|
InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
|
|
|
|
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
|
|
|
|
if (v == InternalThreadLocalMap.UNSET || v == null) {
|
|
return;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
|
|
variablesToRemove.remove(variable);
|
|
}
|
|
|
|
private final int index;
|
|
|
|
public FastThreadLocal() {
|
|
index = InternalThreadLocalMap.nextVariableIndex();
|
|
}
|
|
|
|
/**
|
|
* Returns the current value for the current thread
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public final V get() {
|
|
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
|
|
Object v = threadLocalMap.indexedVariable(index);
|
|
if (v != InternalThreadLocalMap.UNSET) {
|
|
return (V) v;
|
|
}
|
|
|
|
return initialize(threadLocalMap);
|
|
}
|
|
|
|
/**
|
|
* Returns the current value for the current thread if it exists, {@code null} otherwise.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public final V getIfExists() {
|
|
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
|
|
if (threadLocalMap != null) {
|
|
Object v = threadLocalMap.indexedVariable(index);
|
|
if (v != InternalThreadLocalMap.UNSET) {
|
|
return (V) v;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns the current value for the specified thread local map.
|
|
* The specified thread local map must be for the current thread.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public final V get(InternalThreadLocalMap threadLocalMap) {
|
|
Object v = threadLocalMap.indexedVariable(index);
|
|
if (v != InternalThreadLocalMap.UNSET) {
|
|
return (V) v;
|
|
}
|
|
|
|
return initialize(threadLocalMap);
|
|
}
|
|
|
|
private V initialize(InternalThreadLocalMap threadLocalMap) {
|
|
V v = null;
|
|
try {
|
|
v = initialValue();
|
|
} catch (Exception e) {
|
|
PlatformDependent.throwException(e);
|
|
}
|
|
|
|
threadLocalMap.setIndexedVariable(index, v);
|
|
addToVariablesToRemove(threadLocalMap, this);
|
|
return v;
|
|
}
|
|
|
|
/**
|
|
* Set the value for the current thread.
|
|
*/
|
|
public final void set(V value) {
|
|
if (value != InternalThreadLocalMap.UNSET) {
|
|
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
|
|
setKnownNotUnset(threadLocalMap, value);
|
|
} else {
|
|
remove();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the value for the specified thread local map. The specified thread local map must be for the current thread.
|
|
*/
|
|
public final void set(InternalThreadLocalMap threadLocalMap, V value) {
|
|
if (value != InternalThreadLocalMap.UNSET) {
|
|
setKnownNotUnset(threadLocalMap, value);
|
|
} else {
|
|
remove(threadLocalMap);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return see {@link InternalThreadLocalMap#setIndexedVariable(int, Object)}.
|
|
*/
|
|
private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
|
|
if (threadLocalMap.setIndexedVariable(index, value)) {
|
|
addToVariablesToRemove(threadLocalMap, this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if and only if this thread-local variable is set.
|
|
*/
|
|
public final boolean isSet() {
|
|
return isSet(InternalThreadLocalMap.getIfSet());
|
|
}
|
|
|
|
/**
|
|
* Returns {@code true} if and only if this thread-local variable is set.
|
|
* The specified thread local map must be for the current thread.
|
|
*/
|
|
public final boolean isSet(InternalThreadLocalMap threadLocalMap) {
|
|
return threadLocalMap != null && threadLocalMap.isIndexedVariableSet(index);
|
|
}
|
|
/**
|
|
* Sets the value to uninitialized; a proceeding call to get() will trigger a call to initialValue().
|
|
*/
|
|
public final void remove() {
|
|
remove(InternalThreadLocalMap.getIfSet());
|
|
}
|
|
|
|
/**
|
|
* Sets the value to uninitialized for the specified thread local map;
|
|
* a proceeding call to get() will trigger a call to initialValue().
|
|
* The specified thread local map must be for the current thread.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public final void remove(InternalThreadLocalMap threadLocalMap) {
|
|
if (threadLocalMap == null) {
|
|
return;
|
|
}
|
|
|
|
Object v = threadLocalMap.removeIndexedVariable(index);
|
|
removeFromVariablesToRemove(threadLocalMap, this);
|
|
|
|
if (v != InternalThreadLocalMap.UNSET) {
|
|
try {
|
|
onRemoval((V) v);
|
|
} catch (Exception e) {
|
|
PlatformDependent.throwException(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the initial value for this thread-local variable.
|
|
*/
|
|
protected V initialValue() throws Exception {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Invoked when this thread local variable is removed by {@link #remove()}. Be aware that {@link #remove()}
|
|
* is not guaranteed to be called when the `Thread` completes which means you can not depend on this for
|
|
* cleanup of the resources in the case of `Thread` completion.
|
|
*/
|
|
protected void onRemoval(@SuppressWarnings("UnusedParameters") V value) throws Exception { }
|
|
}
|