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    }