001 /* 002 * Copyright (c) 2003, The Regents of the University of California, through 003 * Lawrence Berkeley National Laboratory (subject to receipt of any required 004 * approvals from the U.S. Dept. of Energy). All rights reserved. 005 */ 006 package gov.lbl.dsd.sea.nio.util; 007 008 import java.nio.ByteBuffer; 009 import java.nio.ByteOrder; 010 import java.util.LinkedList; 011 import java.util.List; 012 013 /** 014 * Efficient thread-safe pool of {@link ByteBuffer}s for high performance NIO 015 * applications. Using a buffer pool can drastically reduce memory allocation, 016 * memory copying and garbage collection by taking buffers from the pool when 017 * needed, and recycling them back to the pool when they are no more needed. 018 * <p> 019 * There is a trade-off here: The improved performance a pool promises comes at 020 * the expense of larger overall memory footprint, since buffers in the pool are 021 * not subject to intermediate garbage collection (unless the entire pool is no 022 * more referenced or cleared, of course). 023 * <p> 024 * Once you have taken a buffer via the <code>take</code> method from the 025 * pool, you can modify it in any way desired. Once you have recycled a buffer 026 * via the <code>put</code> method back to the pool you MUST NOT modify it 027 * anymore, NOT EVEN it's mark, position or limit, whether directly or 028 * indirectly! 029 * <p> 030 * On <code>put</code> the pool will ignore buffers with 031 * <code>buffer.capacity() < bufferCapacity</code> or when the aggregate 032 * capacity of all buffers in the pool would become larger than 033 * <code>maxPoolCapacity</code>. 034 * <p> 035 * On <code>take</code> the pool will return a cleared buffer with at least 036 * the given <code>bufferCapacity</code>, which will be a direct or heap 037 * buffer, depending on the <code>preferDirect</code> flag. In any case, the 038 * returned buffer will have the given <code>byteOrder</code>, or BIG_ENDIAN 039 * byte order if <code>byteOrder</code> is null. 040 * <p> 041 * If empty on <code>take</code> the pool will create a new buffer and return 042 * that. (The buffer pool is smart in avoiding allocating too many direct 043 * buffers and in its preference strategies). 044 * <p> 045 * Hint: At least in jdk-1.4.2 the total maximum amount of direct buffers that 046 * may be allocated is 64MB by default. You can change this via 047 * <code>java -XX:MaxDirectMemorySize=256m</code>. See bug 4879883 on Java 048 * Bug Parade. See http://iais.kemsu.ru/odocs/javax/JSDK.Src/java/nio/Bits.java 049 * 050 * @author whoschek@lbl.gov 051 * @author $Author: hoschek3 $ 052 * @version $Revision: 1.5 $, $Date: 2004/07/16 23:44:51 $ 053 */ 054 public class BufferPool { 055 056 private final List heapBuffers; 057 private final List directBuffers; 058 private final int bufferCapacity; 059 private final long maxPoolCapacity; 060 private final boolean preferDirect; 061 private final ByteOrder byteOrder; 062 private long currentPoolCapacity; 063 064 // statistics 065 protected long nrAllocations; 066 protected long nrAllocated; 067 protected long nrTakes; 068 protected long nrTakesReused; 069 protected long nrTakenBytes; 070 protected long nrReusedBytes; 071 protected long nrReplacingPuts; 072 073 private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(BufferPool.class); 074 075 /** 076 * Creates a new pool with the given properties. 077 */ 078 public BufferPool(long maxPoolCapacity, int bufferCapacity, boolean preferDirect, ByteOrder byteOrder) { 079 if (bufferCapacity <= 1) throw new IllegalArgumentException("bufferCapacity must be > 1"); 080 this.maxPoolCapacity = maxPoolCapacity; 081 this.bufferCapacity = bufferCapacity; 082 this.preferDirect = preferDirect; 083 this.byteOrder = (byteOrder == null ? ByteOrder.BIG_ENDIAN : byteOrder); 084 this.heapBuffers = new LinkedList(); 085 this.directBuffers = new LinkedList(); 086 087 this.clear(); 088 } 089 090 /** 091 * Recycles a buffer back into the pool (adds it to the pool). 092 * @param buffer the buffer to put into the pool. 093 */ 094 synchronized public void put(ByteBuffer buffer) { 095 if (buffer == null || buffer.capacity() < this.bufferCapacity) { 096 return; // ignore 097 } 098 099 if (this.currentPoolCapacity + buffer.capacity() > this.maxPoolCapacity) { 100 if (buffer.isDirect() != preferDirect) return; // ignore 101 102 // try to drop a non-preferred buffer and see if new buffer fits 103 List dropBuffers = buffer.isDirect() ? heapBuffers : directBuffers; 104 if (dropBuffers.size() == 0) return; // ignore 105 106 int cap = ((ByteBuffer) dropBuffers.get(dropBuffers.size()-1)).capacity(); 107 //int cap = this.bufferCapacity; 108 if (this.currentPoolCapacity - cap + buffer.capacity() > this.maxPoolCapacity) { 109 return; // ignore 110 } 111 else { 112 dropBuffers.remove(dropBuffers.size()-1); 113 this.currentPoolCapacity -= cap; 114 this.nrReplacingPuts++; 115 } 116 } 117 118 List buffers = buffer.isDirect() ? directBuffers : heapBuffers; 119 buffers.add(0, buffer); 120 this.currentPoolCapacity += buffer.capacity(); 121 } 122 123 /** 124 * Returns a cleared buffer from the pool, or creates and returns a new 125 * buffer. 126 * 127 * @return a buffer from the pool. 128 */ 129 public ByteBuffer take() { 130 ByteBuffer buffer = null; 131 synchronized (this) { 132 this.nrTakes ++; 133 List buffers = preferDirect ? directBuffers : heapBuffers; 134 135 if (buffers.size() > 0) { // try preferred buffers 136 buffer = (ByteBuffer) buffers.get(0); 137 } 138 else { // try non-preferred buffers 139 buffers = preferDirect ? heapBuffers : directBuffers; 140 if (buffers.size() > 0) { 141 buffer = (ByteBuffer) buffers.get(0); 142 } 143 } 144 if (buffer != null) { 145 buffers.remove(0); 146 this.currentPoolCapacity -= buffer.capacity(); 147 this.nrReusedBytes += buffer.capacity(); 148 this.nrTakenBytes += buffer.capacity(); 149 this.nrTakesReused++; 150 } 151 } 152 153 if (buffer == null) { 154 boolean allocateDirect; 155 synchronized (this) { 156 // fix for vm bugs limiting max amount of direct buffer mem that may 157 // be allocated 158 allocateDirect = this.preferDirect 159 && nrAllocated + this.bufferCapacity > maxPoolCapacity ? false 160 : this.preferDirect; 161 this.nrAllocated += this.bufferCapacity; 162 this.nrTakenBytes += this.bufferCapacity; 163 this.nrAllocations++; 164 } 165 buffer = this.createBuffer(this.bufferCapacity, allocateDirect); 166 } 167 168 buffer.clear(); 169 if (buffer.order() != this.byteOrder) { 170 buffer.order(this.byteOrder); 171 } 172 return buffer; 173 } 174 175 /** 176 * Override this method to create custom bytebuffers. 177 */ 178 protected ByteBuffer createBuffer(int capacity, boolean direct) { 179 if (direct) { 180 try { 181 return ByteBuffer.allocateDirect(capacity); 182 } catch (OutOfMemoryError e) { 183 log.warn("OutOfMemoryError: No more direct buffers available; trying heap buffer instead"); 184 } 185 } 186 return ByteBuffer.allocate(capacity); 187 } 188 189 /** 190 * Removes all buffers from the pool. 191 */ 192 synchronized public void clear() { 193 this.heapBuffers.clear(); 194 this.directBuffers.clear(); 195 this.currentPoolCapacity = 0; 196 197 this.nrAllocations = 0; 198 this.nrAllocated = 0; 199 this.nrTakes = 0; 200 this.nrTakesReused = 0; 201 this.nrTakenBytes = 0; 202 this.nrReusedBytes = 0; 203 this.nrReplacingPuts = 0; 204 } 205 206 /** 207 * Returns a summary statistics representation of the receiver. 208 */ 209 public synchronized String toString() { 210 String s = this.getClass().getName() + ": "; 211 s += "nrAllocated=" + mb(nrAllocated) + " MB"; 212 s += ", nrAllocations=" + nrAllocations; 213 s += ", nrTakes=" + nrTakes; 214 s += ", nrTakesReused=" + nrTakesReused; 215 s += ", nrReplacingPuts=" + nrReplacingPuts; 216 s += ", nrTakenBytes=" + mb(nrTakenBytes) + " MB"; 217 s += ", nrReusedBytes=" + mb(nrReusedBytes) + " MB"; 218 s += ", maxPoolCapacity=" + mb(maxPoolCapacity) + " MB"; 219 s += ", currentPoolCapacity=" + mb(currentPoolCapacity) + " MB"; 220 s += " --> EFFICIENCY=" + (100.0f * nrReusedBytes / nrTakenBytes) + " %"; 221 return s; 222 } 223 224 private static float mb(long bytes) { 225 return bytes / (1024.0f * 1024.0f); 226 } 227 }