/* * Copyright (c) 2008-2011 by Bjoern Kolbeck, Jan Stender, * Zuse Institute Berlin * * Licensed under the BSD License, see LICENSE file for details. * */ package de.mxro.thrd.xstreemfs.foundation.buffer; import java.nio.ByteBuffer; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; /** * A concurrent pool for buffer recycling. * * @author bjko */ public final class BufferPool { /** * size of buffers for each class. */ public static final int[] BUFF_SIZES = { 8192, 65536, 131072, 524288, 2097152 }; /** * max pool size for each class */ public static final int[] MAX_POOL_SIZES = { 2000, 200, 100, 10, 5 }; /** * queues to store buffers in */ private final ConcurrentLinkedQueue<ByteBuffer>[] pools; /** * pool sizes to avoid counting elements on each access */ private final AtomicInteger[] poolSizes; /** * stats for num requests and creates of buffers per class */ private AtomicLong[] requests, creates, deletes; /** * singleton pattern. */ private static final BufferPool instance = new BufferPool(); /** * if true all allocate/free operations record the stack trace. Useful to * find memory leaks but slow. */ protected static boolean recordStackTraces = false; /** * Creates a new instance of BufferPool */ @SuppressWarnings("unchecked") private BufferPool() { pools = new ConcurrentLinkedQueue[BUFF_SIZES.length]; creates = new AtomicLong[BUFF_SIZES.length]; for (int i = 0; i < creates.length; i++) { creates[i] = new AtomicLong(); } requests = new AtomicLong[BUFF_SIZES.length + 1]; deletes = new AtomicLong[BUFF_SIZES.length + 1]; for (int i = 0; i < BUFF_SIZES.length + 1; i++) { requests[i] = new AtomicLong(); deletes[i] = new AtomicLong(); } poolSizes = new AtomicInteger[BUFF_SIZES.length]; for (int i = 0; i < BUFF_SIZES.length; i++) { pools[i] = new ConcurrentLinkedQueue<ByteBuffer>(); poolSizes[i] = new AtomicInteger(0); } } /** * Get a new buffer. The Buffer is taken from the pool or created if none is * available or the size exceedes the largest class. * * @param size * the buffer's size in bytes * @return a buffer of requested size * @throws OutOfMemoryError * if a buffer cannot be allocated */ public static ReusableBuffer allocate(int size) { ReusableBuffer tmp = instance.getNewBuffer(size); if (recordStackTraces) { try { throw new Exception("allocate stack trace"); } catch (Exception e) { tmp.allocStack = "\n"; for (StackTraceElement elem : e.getStackTrace()) tmp.allocStack += elem.toString() + "\n"; } } return tmp; } /** * Returns a buffer to the pool, if the buffer is reusable. Other buffers * are ignored. * * @param buf * the buffer to return */ public static void free(ReusableBuffer buf) { if (buf != null) { instance.returnBuffer(buf); } } /** * Returns a buffer which has at least size bytes. * * @attention The returned buffer can be larger than requested! */ private ReusableBuffer getNewBuffer(int size) { try { // if there is a pooled buffer with sufficient capacity ... for (int i = 0; i < BUFF_SIZES.length; i++) { if (size <= BUFF_SIZES[i]) { ByteBuffer buf = pools[i].poll(); // if no free buffer is available in the pool ... if (buf == null) { // ... create // - a direct buffer if the pool is not full yet, // - a non-direct buffer if the pool is full // // Thus, the first MAX_POOL_SIZES[i] buffers will be // pooled, whereas any additional buffers will be // allocated on demand and freed by the garbage // collector. buf = creates[i].get() < MAX_POOL_SIZES[i] ? ByteBuffer.allocateDirect(BUFF_SIZES[i]) : ByteBuffer.allocate(BUFF_SIZES[i]); creates[i].incrementAndGet(); } // otherwise, decrement the pool size to indicate that the // pooled buffer was handed out to the application else { poolSizes[i].decrementAndGet(); } requests[i].incrementAndGet(); return new ReusableBuffer(buf, size); } } // ... otherwise, create an unpooled buffer requests[BUFF_SIZES.length].incrementAndGet(); ByteBuffer buf = ByteBuffer.allocate(size); return new ReusableBuffer(buf, size); } catch (OutOfMemoryError ex) { System.out.println(getStatus()); throw ex; } } /** * return a buffer to the pool */ protected void returnBuffer(ReusableBuffer buffer) { if (!buffer.isReusable()) return; if (buffer.viewParent != null) { // view buffer if (recordStackTraces) { for (StackTraceElement elem : new Exception().getStackTrace()) buffer.freeStack += elem.toString() + "\n"; } assert (!buffer.returned) : "buffer was already released: " + buffer.freeStack; buffer.returned = true; returnBuffer(buffer.viewParent); } else { if (buffer.refCount.getAndDecrement() > 1) { return; } assert (!buffer.returned) : "buffer was already released: " + buffer.freeStack; buffer.returned = true; if (recordStackTraces) { for (StackTraceElement elem : new Exception().getStackTrace()) buffer.freeStack += elem.toString() + "\n"; } ByteBuffer buf = buffer.getParent(); buf.clear(); // determine the pool to which the buffer is supposed to be // returned // ... for (int i = 0; i < BUFF_SIZES.length; i++) { if (buf.capacity() == BUFF_SIZES[i]) { // return direct buffers to the pool if (buf.isDirect()) { poolSizes[i].incrementAndGet(); pools[i].add(buf); // since only direct buffers will be returned to the // pool, which have been counted on allocation, there is // no need to check the pool size here return; } // if the buffer is non-direct, increment the delete counter // and implicitly make the buffer subject to garbage // collection else { deletes[i].incrementAndGet(); return; } } } assert (!buf.isDirect()) : "encountered direct buffer that does not fit in any of the pools (size=" + buf.capacity() + "): " + buffer.freeStack; // if the buffer did not fit in any of the pools, // increment the delete counter for the unpooled buffers deletes[deletes.length - 1].incrementAndGet(); } } /** * Returns a textual representation of the pool status. * * @return a textual representation of the pool status. */ public static String getStatus() { String str = ""; for (int i = 0; i < BUFF_SIZES.length; i++) { str += String.format( "%8d: poolSize = %5d numRequests = %8d creates = %8d deletes = %8d\n", BUFF_SIZES[i], instance.poolSizes[i].get(), instance.requests[i].get(), instance.creates[i] .get(), instance.deletes[i].get()); } str += String.format("unpooled (> %8d) numRequests = creates = %8d deletes = %8d", BUFF_SIZES[BUFF_SIZES.length - 1], instance.requests[instance.requests.length - 1].get(), instance.deletes[instance.deletes.length - 1].get()); return str; } /** * Specifies whether stack traces shall be recorded when allocating and * freeing buffers. Since recording stack traces leads to some overhead, it * should only be enabled for debugging purposes. * * @param record */ public static void enableStackTraceRecording(boolean record) { recordStackTraces = record; } }