org.xnio.ByteBufferSlicePool Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
The newest version!
/*
* JBoss, Home of Professional Open Source
*
* Copyright 2010 Red Hat, Inc. and/or its affiliates, and individual
* contributors as indicated by the @author tags.
*
* Licensed 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 org.xnio;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import static org.xnio._private.Messages.msg;
/**
* A buffer pooled allocator. This pool uses a series of buffer regions to back the
* returned pooled buffers. When the buffer is no longer needed, it should be freed back into the pool; failure
* to do so will cause the corresponding buffer area to be unavailable until the buffer is garbage-collected.
*
* If the buffer pool is no longer used, it is advisable to invoke {@link #clean()} to make
* sure that direct allocated buffers can be reused by a future instance.
*
* @author David M. Lloyd
* @author Flavia Rainone
* @deprecated See {@link ByteBufferPool}.
*/
public final class ByteBufferSlicePool implements Pool {
private static final int LOCAL_LENGTH;
private static final Queue FREE_DIRECT_BUFFERS;
static {
// read thread local size property
String value = AccessController.doPrivileged(new ReadPropertyAction("xnio.bufferpool.threadlocal.size", "12"));
int val;
try {
val = Integer.parseInt(value);
} catch (NumberFormatException ignored) {
val = 12;
}
LOCAL_LENGTH = val;
// free direct buffers queue to keep direct buffers that are out of reach because of garbage collection of pools
FREE_DIRECT_BUFFERS = new ConcurrentLinkedQueue<>();
}
private final Set refSet = Collections.synchronizedSet(new HashSet<>());
private final Queue sliceQueue;
private final BufferAllocator allocator;
private final int bufferSize;
private final int buffersPerRegion;
private final int threadLocalQueueSize;
private final List directBuffers;
private final ThreadLocal localQueueHolder = new ThreadLocalCacheWrapper(this);
/**
* Construct a new instance.
*
* @param allocator the buffer allocator to use
* @param bufferSize the size of each buffer
* @param maxRegionSize the maximum region size for each backing buffer
* @param threadLocalQueueSize the number of buffers to cache on each thread
*/
public ByteBufferSlicePool(final BufferAllocator allocator, final int bufferSize, final int maxRegionSize, final int threadLocalQueueSize) {
if (bufferSize <= 0) {
throw msg.parameterOutOfRange("bufferSize");
}
if (maxRegionSize < bufferSize) {
throw msg.parameterOutOfRange("bufferSize");
}
buffersPerRegion = maxRegionSize / bufferSize;
this.bufferSize = bufferSize;
this.allocator = allocator;
sliceQueue = new ConcurrentLinkedQueue<>();
this.threadLocalQueueSize = threadLocalQueueSize;
// handle direct byte buffer allocation for reuse of direct buffers
if (allocator == BufferAllocator.DIRECT_BYTE_BUFFER_ALLOCATOR) {
directBuffers = Collections.synchronizedList(new ArrayList<>());
} else {
directBuffers = null;
}
}
/**
* Construct a new instance.
*
* @param allocator the buffer allocator to use
* @param bufferSize the size of each buffer
* @param maxRegionSize the maximum region size for each backing buffer
*/
public ByteBufferSlicePool(final BufferAllocator allocator, final int bufferSize, final int maxRegionSize) {
this(allocator, bufferSize, maxRegionSize, LOCAL_LENGTH);
}
/**
* Construct a new instance, using a direct buffer allocator.
*
* @param bufferSize the size of each buffer
* @param maxRegionSize the maximum region size for each backing buffer
*/
public ByteBufferSlicePool(final int bufferSize, final int maxRegionSize) {
this(BufferAllocator.DIRECT_BYTE_BUFFER_ALLOCATOR, bufferSize, maxRegionSize);
}
/** {@inheritDoc} */
public Pooled allocate() {
Slice slice;
if (threadLocalQueueSize > 0) {
ThreadLocalCache localCache = localQueueHolder.get();
if(localCache.outstanding != threadLocalQueueSize) {
localCache.outstanding++;
}
slice = localCache.queue.poll();
if (slice != null) {
return new PooledByteBuffer(slice, slice.slice());
}
}
final Queue sliceQueue = this.sliceQueue;
slice = sliceQueue.poll();
if (slice != null) {
return new PooledByteBuffer(slice, slice.slice());
}
synchronized (sliceQueue) {
slice = sliceQueue.poll();
if (slice != null) {
return new PooledByteBuffer(slice, slice.slice());
}
final Slice newSlice = allocateSlices(buffersPerRegion, bufferSize);
return new PooledByteBuffer(newSlice, newSlice.slice());
}
}
private Slice allocateSlices(final int buffersPerRegion, final int bufferSize) {
// only true if using direct allocation
if (directBuffers != null) {
ByteBuffer region = FREE_DIRECT_BUFFERS.poll();
try {
if (region != null) {
return sliceReusedBuffer(region, buffersPerRegion, bufferSize);
}
region = allocator.allocate(buffersPerRegion * bufferSize);
return sliceAllocatedBuffer(region, buffersPerRegion, bufferSize);
} finally {
// add all directly allocated memory to directBuffers, so it can
// be added to FREE_DIRECT_BUFFERS on clean()
directBuffers.add(region);
}
}
return sliceAllocatedBuffer(
allocator.allocate(buffersPerRegion * bufferSize),
buffersPerRegion, bufferSize);
}
private Slice sliceReusedBuffer(final ByteBuffer region, final int buffersPerRegion, final int bufferSize) {
int maxI = Math.min(buffersPerRegion, region.capacity() / bufferSize);
// create slices
int idx = bufferSize;
for (int i = 1; i < maxI; i++) {
sliceQueue.add(new Slice(region, idx, bufferSize));
idx += bufferSize;
}
if (maxI == 0)
return allocateSlices(buffersPerRegion, bufferSize);
if (maxI < buffersPerRegion)
sliceQueue.add(allocateSlices(buffersPerRegion - maxI, bufferSize));
return new Slice(region, 0, bufferSize);
}
private Slice sliceAllocatedBuffer(final ByteBuffer region, final int buffersPerRegion, final int bufferSize) {
// create slices
int idx = bufferSize;
for (int i = 1; i < buffersPerRegion; i++) {
sliceQueue.add(new Slice(region, idx, bufferSize));
idx += bufferSize;
}
return new Slice(region, 0, bufferSize);
}
/**
* Cleans the pool, removing references to any buffers inside it.
* Should be invoked on pool disposal, when the pool will no longer be
* used.
*/
public void clean() {
ThreadLocalCache localCache = localQueueHolder.get();
if (!localCache.queue.isEmpty()) {
localCache.queue.clear();
}
if(!sliceQueue.isEmpty()) {
sliceQueue.clear();
}
// only true if using direct allocation
if (directBuffers != null) {
// pass everything that is directly allocated to free direct buffers
FREE_DIRECT_BUFFERS.addAll(directBuffers);
}
}
/**
* Return the size of the {@link ByteBuffer}s that are returned by {@link #allocate()}.
*/
public int getBufferSize() {
return bufferSize;
}
private ThreadLocalCache createThreadLocalCache() {
return new ThreadLocalCache(this);
}
private void freeThreadLocalCache(ThreadLocalCache cache) {
final ArrayDeque deque = cache.queue;
Slice slice = deque.poll();
while (slice != null) {
doFree(slice);
slice = deque.poll();
}
}
private void doFree(Slice region) {
if (threadLocalQueueSize > 0) {
final ThreadLocalCache localCache = localQueueHolder.get();
boolean cacheOk = false;
if(localCache.outstanding > 0) {
localCache.outstanding--;
cacheOk = true;
}
ArrayDeque localQueue = localCache.queue;
if (localQueue.size() == threadLocalQueueSize || !cacheOk) {
sliceQueue.add(region);
} else {
localQueue.add(region);
}
} else {
sliceQueue.add(region);
}
}
private final class PooledByteBuffer implements Pooled {
private final Slice region;
ByteBuffer buffer;
PooledByteBuffer(final Slice region, final ByteBuffer buffer) {
this.region = region;
this.buffer = buffer;
}
public void discard() {
final ByteBuffer buffer = this.buffer;
this.buffer = null;
if (buffer != null) {
// free when GC'd, no sooner
refSet.add(new Ref(buffer, region));
}
}
public void free() {
ByteBuffer buffer = this.buffer;
this.buffer = null;
if (buffer != null) {
// trust the user, repool the buffer
doFree(region);
}
}
public ByteBuffer getResource() {
final ByteBuffer buffer = this.buffer;
if (buffer == null) {
throw msg.bufferFreed();
}
return buffer;
}
public void close() {
free();
}
public String toString() {
return "Pooled buffer " + buffer;
}
}
// to prevent memory leaks via thread internal map for thread local, we need to
// make this class static or else the outer ByteBufferSlicePool
// is never collected while the thread is active
// Thread -> thread local map -> ThreadLocalCacheWrapper -> ThreadLocalCache -> queue -> Slices -> ByteBufferSlicePool
private static final class Slice {
private final ByteBuffer parent;
private Slice(final ByteBuffer parent, final int start, final int size) {
this.parent = (ByteBuffer) parent.duplicate().position(start).limit(start+size);
}
ByteBuffer slice() {
return parent.slice();
}
}
final class Ref extends AutomaticReference {
private final Slice region;
private Ref(final ByteBuffer referent, final Slice region) {
super(referent, AutomaticReference.PERMIT);
this.region = region;
}
protected void free() {
doFree(region);
refSet.remove(this);
}
}
final static class ThreadLocalCache {
// to prevent memory leaks via thread internal map for thread local, we need to
// weakly reference the outer ByteBufferSlicePool
// or else the pool is never collected while the thread is active
// Thread -> thread local map -> ThreadLocalCache -> pool
final WeakReference pool;
// internal queue of slices; used to prevent all threads synchronizing on a single queue
final ArrayDeque queue;
// indicates how many slices should be returned to queue on free
int outstanding = 0;
ThreadLocalCache(ByteBufferSlicePool pool) {
this.pool = new WeakReference<>(pool);
this.queue = new ArrayDeque(pool.threadLocalQueueSize) {
/**
* This sucks but there's no other way to ensure these buffers are returned to the pool.
*/
protected void finalize() {
final ByteBufferSlicePool pool = ThreadLocalCache.this.pool.get();
if (pool == null)
return;
final ArrayDeque deque = queue;
Slice slice = deque.poll();
while (slice != null) {
pool.doFree(slice);
slice = deque.poll();
}
}
};
}
}
private static class ThreadLocalCacheWrapper extends ThreadLocal {
// to prevent memory leaks via thread internal map for thread local, we need to
// weakly reference the outer ByteBufferSlicePool
// or else the pool is never collected while the thread is active
// Thread -> thread local map -> ThreadLocalCacheWrapper -> pool
private final WeakReference pool;
ThreadLocalCacheWrapper(ByteBufferSlicePool pool) {
this.pool = new WeakReference<>(pool);
}
protected ThreadLocalCache initialValue() {
final ByteBufferSlicePool pool = this.pool.get();
if (pool != null) {
//noinspection serial
return pool.createThreadLocalCache();
}
return null;
}
public void remove() {
final ByteBufferSlicePool pool = this.pool.get();
final ThreadLocalCache cache = get();
if (pool != null && cache != null) {
//noinspection serial
pool.freeThreadLocalCache(cache);
}
super.remove();
}
}
}