001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 package org.apache.hadoop.hdfs.util; 019 020 import java.util.HashMap; 021 import java.util.LinkedList; 022 import java.util.Map; 023 import java.util.Queue; 024 025 import org.apache.commons.logging.Log; 026 import org.apache.commons.logging.LogFactory; 027 import org.apache.hadoop.HadoopIllegalArgumentException; 028 import org.apache.hadoop.classification.InterfaceAudience; 029 import org.apache.hadoop.util.Time; 030 031 import com.google.common.base.Preconditions; 032 033 /** 034 * Manage byte array creation and release. 035 */ 036 @InterfaceAudience.Private 037 public abstract class ByteArrayManager { 038 static final Log LOG = LogFactory.getLog(ByteArrayManager.class); 039 private static final ThreadLocal<StringBuilder> debugMessage = new ThreadLocal<StringBuilder>() { 040 protected StringBuilder initialValue() { 041 return new StringBuilder(); 042 } 043 }; 044 045 private static void logDebugMessage() { 046 final StringBuilder b = debugMessage.get(); 047 LOG.debug(b); 048 b.setLength(0); 049 } 050 051 static final int MIN_ARRAY_LENGTH = 32; 052 static final byte[] EMPTY_BYTE_ARRAY = {}; 053 054 /** 055 * @return the least power of two greater than or equal to n, i.e. return 056 * the least integer x with x >= n and x a power of two. 057 * 058 * @throws HadoopIllegalArgumentException 059 * if n <= 0. 060 */ 061 public static int leastPowerOfTwo(final int n) { 062 if (n <= 0) { 063 throw new HadoopIllegalArgumentException("n = " + n + " <= 0"); 064 } 065 066 final int highestOne = Integer.highestOneBit(n); 067 if (highestOne == n) { 068 return n; // n is a power of two. 069 } 070 final int roundUp = highestOne << 1; 071 if (roundUp < 0) { 072 final long overflow = ((long) highestOne) << 1; 073 throw new ArithmeticException( 074 "Overflow: for n = " + n + ", the least power of two (the least" 075 + " integer x with x >= n and x a power of two) = " 076 + overflow + " > Integer.MAX_VALUE = " + Integer.MAX_VALUE); 077 } 078 return roundUp; 079 } 080 081 /** 082 * A counter with a time stamp so that it is reset automatically 083 * if there is no increment for the time period. 084 */ 085 static class Counter { 086 private final long countResetTimePeriodMs; 087 private long count = 0L; 088 private long timestamp = Time.monotonicNow(); 089 090 Counter(long countResetTimePeriodMs) { 091 this.countResetTimePeriodMs = countResetTimePeriodMs; 092 } 093 094 synchronized long getCount() { 095 return count; 096 } 097 098 /** 099 * Increment the counter, and reset it if there is no increment 100 * for acertain time period. 101 * 102 * @return the new count. 103 */ 104 synchronized long increment() { 105 final long now = Time.monotonicNow(); 106 if (now - timestamp > countResetTimePeriodMs) { 107 count = 0; // reset the counter 108 } 109 timestamp = now; 110 return ++count; 111 } 112 } 113 114 /** A map from integers to counters. */ 115 static class CounterMap { 116 /** @see ByteArrayManager.Conf#countResetTimePeriodMs */ 117 private final long countResetTimePeriodMs; 118 private final Map<Integer, Counter> map = new HashMap<Integer, Counter>(); 119 120 private CounterMap(long countResetTimePeriodMs) { 121 this.countResetTimePeriodMs = countResetTimePeriodMs; 122 } 123 124 /** 125 * @return the counter for the given key; 126 * and create a new counter if it does not exist. 127 */ 128 synchronized Counter get(final Integer key, final boolean createIfNotExist) { 129 Counter count = map.get(key); 130 if (count == null && createIfNotExist) { 131 count = new Counter(countResetTimePeriodMs); 132 map.put(key, count); 133 } 134 return count; 135 } 136 137 synchronized void clear() { 138 map.clear(); 139 } 140 } 141 142 /** Manage byte arrays with the same fixed length. */ 143 static class FixedLengthManager { 144 private final int byteArrayLength; 145 private final int maxAllocated; 146 private final Queue<byte[]> freeQueue = new LinkedList<byte[]>(); 147 148 private int numAllocated = 0; 149 150 FixedLengthManager(int arrayLength, int maxAllocated) { 151 this.byteArrayLength = arrayLength; 152 this.maxAllocated = maxAllocated; 153 } 154 155 /** 156 * Allocate a byte array. 157 * 158 * If the number of allocated arrays >= maximum, the current thread is 159 * blocked until the number of allocated arrays drops to below the maximum. 160 * 161 * The byte array allocated by this method must be returned for recycling 162 * via the {@link FixedLengthManager#recycle(byte[])} method. 163 */ 164 synchronized byte[] allocate() throws InterruptedException { 165 if (LOG.isDebugEnabled()) { 166 debugMessage.get().append(", ").append(this); 167 } 168 for(; numAllocated >= maxAllocated;) { 169 if (LOG.isDebugEnabled()) { 170 debugMessage.get().append(": wait ..."); 171 logDebugMessage(); 172 } 173 174 wait(); 175 176 if (LOG.isDebugEnabled()) { 177 debugMessage.get().append("wake up: ").append(this); 178 } 179 } 180 numAllocated++; 181 182 final byte[] array = freeQueue.poll(); 183 if (LOG.isDebugEnabled()) { 184 debugMessage.get().append(", recycled? ").append(array != null); 185 } 186 return array != null? array : new byte[byteArrayLength]; 187 } 188 189 /** 190 * Recycle the given byte array, which must have the same length as the 191 * array length managed by this object. 192 * 193 * The byte array may or may not be allocated 194 * by the {@link FixedLengthManager#allocate()} method. 195 */ 196 synchronized int recycle(byte[] array) { 197 Preconditions.checkNotNull(array); 198 Preconditions.checkArgument(array.length == byteArrayLength); 199 if (LOG.isDebugEnabled()) { 200 debugMessage.get().append(", ").append(this); 201 } 202 203 if (numAllocated == maxAllocated) { 204 if (LOG.isDebugEnabled()) { 205 debugMessage.get().append(", notifyAll"); 206 } 207 notifyAll(); 208 } 209 numAllocated--; 210 if (numAllocated < 0) { 211 // it is possible to drop below 0 since 212 // some byte arrays may not be created by the allocate() method. 213 numAllocated = 0; 214 } 215 216 if (freeQueue.size() < maxAllocated - numAllocated) { 217 if (LOG.isDebugEnabled()) { 218 debugMessage.get().append(", freeQueue.offer"); 219 } 220 freeQueue.offer(array); 221 } 222 return freeQueue.size(); 223 } 224 225 @Override 226 public synchronized String toString() { 227 return "[" + byteArrayLength + ": " + numAllocated + "/" 228 + maxAllocated + ", free=" + freeQueue.size() + "]"; 229 } 230 } 231 232 /** A map from array lengths to byte array managers. */ 233 static class ManagerMap { 234 private final int countLimit; 235 private final Map<Integer, FixedLengthManager> map = new HashMap<Integer, FixedLengthManager>(); 236 237 ManagerMap(int countLimit) { 238 this.countLimit = countLimit; 239 } 240 241 /** @return the manager for the given array length. */ 242 synchronized FixedLengthManager get(final Integer arrayLength, 243 final boolean createIfNotExist) { 244 FixedLengthManager manager = map.get(arrayLength); 245 if (manager == null && createIfNotExist) { 246 manager = new FixedLengthManager(arrayLength, countLimit); 247 map.put(arrayLength, manager); 248 } 249 return manager; 250 } 251 252 synchronized void clear() { 253 map.clear(); 254 } 255 } 256 257 public static class Conf { 258 /** 259 * The count threshold for each array length so that a manager is created 260 * only after the allocation count exceeds the threshold. 261 */ 262 private final int countThreshold; 263 /** 264 * The maximum number of arrays allowed for each array length. 265 */ 266 private final int countLimit; 267 /** 268 * The time period in milliseconds that the allocation count for each array 269 * length is reset to zero if there is no increment. 270 */ 271 private final long countResetTimePeriodMs; 272 273 public Conf(int countThreshold, int countLimit, long countResetTimePeriodMs) { 274 this.countThreshold = countThreshold; 275 this.countLimit = countLimit; 276 this.countResetTimePeriodMs = countResetTimePeriodMs; 277 } 278 } 279 280 /** 281 * Create a byte array for the given length, where the length of 282 * the returned array is larger than or equal to the given length. 283 * 284 * The current thread may be blocked if some resource is unavailable. 285 * 286 * The byte array created by this method must be released 287 * via the {@link ByteArrayManager#release(byte[])} method. 288 * 289 * @return a byte array with length larger than or equal to the given length. 290 */ 291 public abstract byte[] newByteArray(int size) throws InterruptedException; 292 293 /** 294 * Release the given byte array. 295 * 296 * The byte array may or may not be created 297 * by the {@link ByteArrayManager#newByteArray(int)} method. 298 * 299 * @return the number of free array. 300 */ 301 public abstract int release(byte[] array); 302 303 public static ByteArrayManager newInstance(Conf conf) { 304 return conf == null? new NewByteArrayWithoutLimit(): new Impl(conf); 305 } 306 307 /** 308 * A dummy implementation which simply calls new byte[]. 309 */ 310 static class NewByteArrayWithoutLimit extends ByteArrayManager { 311 @Override 312 public byte[] newByteArray(int size) throws InterruptedException { 313 return new byte[size]; 314 } 315 316 @Override 317 public int release(byte[] array) { 318 return 0; 319 } 320 } 321 322 /** 323 * Manage byte array allocation and provide a mechanism for recycling the byte 324 * array objects. 325 */ 326 static class Impl extends ByteArrayManager { 327 private final Conf conf; 328 329 private final CounterMap counters; 330 private final ManagerMap managers; 331 332 Impl(Conf conf) { 333 this.conf = conf; 334 this.counters = new CounterMap(conf.countResetTimePeriodMs); 335 this.managers = new ManagerMap(conf.countLimit); 336 } 337 338 /** 339 * Allocate a byte array, where the length of the allocated array 340 * is the least power of two of the given length 341 * unless the given length is less than {@link #MIN_ARRAY_LENGTH}. 342 * In such case, the returned array length is equal to {@link #MIN_ARRAY_LENGTH}. 343 * 344 * If the number of allocated arrays exceeds the capacity, 345 * the current thread is blocked until 346 * the number of allocated arrays drops to below the capacity. 347 * 348 * The byte array allocated by this method must be returned for recycling 349 * via the {@link ByteArrayManager#recycle(byte[])} method. 350 * 351 * @return a byte array with length larger than or equal to the given length. 352 */ 353 @Override 354 public byte[] newByteArray(final int arrayLength) throws InterruptedException { 355 if (LOG.isDebugEnabled()) { 356 debugMessage.get().append("allocate(").append(arrayLength).append(")"); 357 } 358 359 final byte[] array; 360 if (arrayLength == 0) { 361 array = EMPTY_BYTE_ARRAY; 362 } else { 363 final int powerOfTwo = arrayLength <= MIN_ARRAY_LENGTH? 364 MIN_ARRAY_LENGTH: leastPowerOfTwo(arrayLength); 365 final long count = counters.get(powerOfTwo, true).increment(); 366 final boolean aboveThreshold = count > conf.countThreshold; 367 // create a new manager only if the count is above threshold. 368 final FixedLengthManager manager = managers.get(powerOfTwo, aboveThreshold); 369 370 if (LOG.isDebugEnabled()) { 371 debugMessage.get().append(": count=").append(count) 372 .append(aboveThreshold? ", aboveThreshold": ", belowThreshold"); 373 } 374 array = manager != null? manager.allocate(): new byte[powerOfTwo]; 375 } 376 377 if (LOG.isDebugEnabled()) { 378 logDebugMessage(); 379 } 380 return array; 381 } 382 383 /** 384 * Recycle the given byte array. 385 * 386 * The byte array may or may not be allocated 387 * by the {@link ByteArrayManager#allocate(int)} method. 388 */ 389 @Override 390 public int release(final byte[] array) { 391 Preconditions.checkNotNull(array); 392 if (LOG.isDebugEnabled()) { 393 debugMessage.get().append("recycle: array.length=").append(array.length); 394 } 395 396 final int freeQueueSize; 397 if (array.length == 0) { 398 freeQueueSize = -1; 399 } else { 400 final FixedLengthManager manager = managers.get(array.length, false); 401 freeQueueSize = manager == null? -1: manager.recycle(array); 402 } 403 404 if (LOG.isDebugEnabled()) { 405 debugMessage.get().append(", freeQueueSize=").append(freeQueueSize); 406 logDebugMessage(); 407 } 408 return freeQueueSize; 409 } 410 411 CounterMap getCounters() { 412 return counters; 413 } 414 415 ManagerMap getManagers() { 416 return managers; 417 } 418 } 419 }