View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.io;
19  
20  import java.nio.ByteBuffer;
21  import java.util.Queue;
22  import java.util.concurrent.atomic.AtomicLong;
23  import java.util.concurrent.locks.ReentrantLock;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.hadoop.hbase.classification.InterfaceAudience;
28  import org.apache.hadoop.hbase.util.BoundedArrayQueue;
29  
30  import com.google.common.annotations.VisibleForTesting;
31  
32  /**
33   * Like Hadoops' ByteBufferPool only you do not specify desired size when getting a ByteBuffer.
34   * This pool keeps an upper bound on the count of ByteBuffers in the pool and on the maximum size
35   * of ByteBuffer that it will retain (Hence the pool is 'bounded' as opposed to, say,
36   * Hadoop's ElasticByteBuffferPool).
37   * If a ByteBuffer is bigger than the configured threshold, we will just let the ByteBuffer go
38   * rather than add it to the pool. If more ByteBuffers than the configured maximum instances,
39   * we will not add the passed ByteBuffer to the pool; we will just drop it
40   * (we will log a WARN in this case that we are at capacity).
41   *
42   * <p>The intended use case is a reservoir of bytebuffers that an RPC can reuse; buffers tend to
43   * achieve a particular 'run' size over time give or take a few extremes. Set TRACE level on this
44   * class for a couple of seconds to get reporting on how it is running when deployed.
45   *
46   * <p>This class is thread safe.
47   */
48  @InterfaceAudience.Private
49  @SuppressWarnings("NonAtomicVolatileUpdate") // Suppress error-prone warning, see HBASE-21162
50  public class BoundedByteBufferPool {
51    private static final Log LOG = LogFactory.getLog(BoundedByteBufferPool.class);
52  
53    @VisibleForTesting
54    final Queue<ByteBuffer> buffers;
55  
56    // Maximum size of a ByteBuffer to retain in pool
57    private final int maxByteBufferSizeToCache;
58  
59    // A running average only it only rises, it never recedes
60    @VisibleForTesting
61    volatile int runningAverage;
62  
63    // Scratch that keeps rough total size of pooled bytebuffers
64    private volatile int totalReservoirCapacity;
65  
66    // For reporting
67    private AtomicLong allocations = new AtomicLong(0);
68  
69    private ReentrantLock lock =  new ReentrantLock();
70  
71    private boolean createDirectByteBuffer;
72  
73    /**
74     * @param maxByteBufferSizeToCache
75     * @param initialByteBufferSize
76     * @param maxToCache
77     * @param createDirectByteBuffer whether the buffers created by this pool to be off heap
78     */
79    public BoundedByteBufferPool(final int maxByteBufferSizeToCache, final int initialByteBufferSize,
80        final int maxToCache, final boolean createDirectByteBuffer) {
81      this.maxByteBufferSizeToCache = maxByteBufferSizeToCache;
82      this.runningAverage = initialByteBufferSize;
83      this.buffers = new BoundedArrayQueue<ByteBuffer>(maxToCache);
84      this.createDirectByteBuffer = createDirectByteBuffer;
85    }
86  
87    public ByteBuffer getBuffer() {
88      ByteBuffer bb = null;
89      lock.lock();
90      try {
91        bb = this.buffers.poll();
92        if (bb != null) {
93          this.totalReservoirCapacity -= bb.capacity();
94        }
95      } finally {
96        lock.unlock();
97      }
98      if (bb != null) {
99        // Clear sets limit == capacity. Postion == 0.
100       bb.clear();
101     } else {
102       bb = this.createDirectByteBuffer ? ByteBuffer.allocateDirect(this.runningAverage)
103           : ByteBuffer.allocate(this.runningAverage);
104       this.allocations.incrementAndGet();
105     }
106     if (LOG.isTraceEnabled()) {
107       LOG.trace("runningAverage=" + this.runningAverage +
108         ", totalCapacity=" + this.totalReservoirCapacity + ", count=" + this.buffers.size() +
109         ", allocations=" + this.allocations.get());
110     }
111     return bb;
112   }
113 
114   public void putBuffer(ByteBuffer bb) {
115     // If buffer is larger than we want to keep around, just let it go.
116     if (bb.capacity() > this.maxByteBufferSizeToCache) return;
117     boolean success = false;
118     int average = 0;
119     lock.lock();
120     try {
121       success = this.buffers.offer(bb);
122       if (success) {
123         this.totalReservoirCapacity += bb.capacity();
124         average = this.totalReservoirCapacity / this.buffers.size(); // size will never be 0.
125       }
126     } finally {
127       lock.unlock();
128     }
129     if (!success) {
130       if (LOG.isDebugEnabled()) {
131         LOG.debug("At capacity: " + this.buffers.size());
132       }
133     } else {
134       if (average > this.runningAverage && average < this.maxByteBufferSizeToCache) {
135         this.runningAverage = average;
136       }
137     }
138   }
139 }