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 */ 018package org.apache.hadoop.hbase.regionserver; 019 020import java.util.Map; 021import java.util.Set; 022import java.util.concurrent.BlockingQueue; 023import java.util.concurrent.ConcurrentHashMap; 024import java.util.concurrent.Executors; 025import java.util.concurrent.LinkedBlockingQueue; 026import java.util.concurrent.ScheduledExecutorService; 027import java.util.concurrent.TimeUnit; 028import java.util.concurrent.atomic.AtomicInteger; 029import java.util.concurrent.atomic.AtomicLong; 030import java.util.concurrent.atomic.LongAdder; 031 032import org.apache.yetus.audience.InterfaceAudience; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035import org.apache.hadoop.hbase.regionserver.HeapMemoryManager.HeapMemoryTuneObserver; 036import org.apache.hadoop.hbase.util.Bytes; 037import org.apache.hadoop.util.StringUtils; 038 039import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; 040import org.apache.hbase.thirdparty.com.google.common.util.concurrent.ThreadFactoryBuilder; 041 042/** 043 * Does the management of memstoreLAB chunk creations. A monotonically incrementing id is associated 044 * with every chunk 045 */ 046@InterfaceAudience.Private 047public class ChunkCreator { 048 private static final Logger LOG = LoggerFactory.getLogger(ChunkCreator.class); 049 // monotonically increasing chunkid. Starts at 1. 050 private AtomicInteger chunkID = new AtomicInteger(1); 051 // maps the chunk against the monotonically increasing chunk id. We need to preserve the 052 // natural ordering of the key 053 // CellChunkMap creation should convert the weak ref to hard reference 054 055 // chunk id of each chunk is the first integer written on each chunk, 056 // the header size need to be changed in case chunk id size is changed 057 public static final int SIZEOF_CHUNK_HEADER = Bytes.SIZEOF_INT; 058 059 /** 060 * Types of chunks, based on their sizes 061 */ 062 public enum ChunkType { 063 // An index chunk is a small chunk, allocated from the index chunks pool. 064 // Its size is fixed and is 10% of the size of a data chunk. 065 INDEX_CHUNK, 066 // A data chunk is a regular chunk, allocated from the data chunks pool. 067 // Its size is fixed and given as input to the ChunkCreator c'tor. 068 DATA_CHUNK, 069 // A jumbo chunk isn't allocated from pool. Its size is bigger than the size of a 070 // data chunk, and is determined per chunk (meaning, there is no fixed jumbo size). 071 JUMBO_CHUNK 072 } 073 074 // mapping from chunk IDs to chunks 075 private Map<Integer, Chunk> chunkIdMap = new ConcurrentHashMap<Integer, Chunk>(); 076 077 private final boolean offheap; 078 @VisibleForTesting 079 static ChunkCreator instance; 080 @VisibleForTesting 081 static boolean chunkPoolDisabled = false; 082 private MemStoreChunkPool dataChunksPool; 083 private int chunkSize; 084 private MemStoreChunkPool indexChunksPool; 085 086 @VisibleForTesting 087 ChunkCreator(int chunkSize, boolean offheap, long globalMemStoreSize, float poolSizePercentage, 088 float initialCountPercentage, HeapMemoryManager heapMemoryManager, 089 float indexChunkSizePercentage) { 090 this.offheap = offheap; 091 this.chunkSize = chunkSize; // in case pools are not allocated 092 initializePools(chunkSize, globalMemStoreSize, poolSizePercentage, indexChunkSizePercentage, 093 initialCountPercentage, heapMemoryManager); 094 } 095 096 @VisibleForTesting 097 private void initializePools(int chunkSize, long globalMemStoreSize, 098 float poolSizePercentage, float indexChunkSizePercentage, 099 float initialCountPercentage, 100 HeapMemoryManager heapMemoryManager) { 101 this.dataChunksPool = initializePool("data", globalMemStoreSize, 102 (1 - indexChunkSizePercentage) * poolSizePercentage, 103 initialCountPercentage, chunkSize, heapMemoryManager); 104 // The index chunks pool is needed only when the index type is CCM. 105 // Since the pools are not created at all when the index type isn't CCM, 106 // we don't need to check it here. 107 this.indexChunksPool = initializePool("index", globalMemStoreSize, 108 indexChunkSizePercentage * poolSizePercentage, 109 initialCountPercentage, (int) (indexChunkSizePercentage * chunkSize), 110 heapMemoryManager); 111 } 112 113 /** 114 * Initializes the instance of ChunkCreator 115 * @param chunkSize the chunkSize 116 * @param offheap indicates if the chunk is to be created offheap or not 117 * @param globalMemStoreSize the global memstore size 118 * @param poolSizePercentage pool size percentage 119 * @param initialCountPercentage the initial count of the chunk pool if any 120 * @param heapMemoryManager the heapmemory manager 121 * @return singleton MSLABChunkCreator 122 */ 123 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "LI_LAZY_INIT_STATIC", 124 justification = "Method is called by single thread at the starting of RS") 125 @VisibleForTesting 126 public static ChunkCreator initialize(int chunkSize, boolean offheap, long globalMemStoreSize, 127 float poolSizePercentage, float initialCountPercentage, 128 HeapMemoryManager heapMemoryManager) { 129 if (instance != null) { 130 return instance; 131 } 132 instance = new ChunkCreator(chunkSize, offheap, globalMemStoreSize, poolSizePercentage, 133 initialCountPercentage, heapMemoryManager, 134 MemStoreLABImpl.INDEX_CHUNK_PERCENTAGE_DEFAULT); 135 return instance; 136 } 137 138 static ChunkCreator getInstance() { 139 return instance; 140 } 141 142 /** 143 * Creates and inits a chunk. The default implementation for a specific chunk size. 144 * @return the chunk that was initialized 145 */ 146 Chunk getChunk(ChunkType chunkType) { 147 return getChunk(CompactingMemStore.IndexType.ARRAY_MAP, chunkType); 148 } 149 150 /** 151 * Creates and inits a chunk. The default implementation. 152 * @return the chunk that was initialized 153 */ 154 Chunk getChunk() { 155 return getChunk(CompactingMemStore.IndexType.ARRAY_MAP, ChunkType.DATA_CHUNK); 156 } 157 158 /** 159 * Creates and inits a chunk. The default implementation for a specific index type. 160 * @return the chunk that was initialized 161 */ 162 Chunk getChunk(CompactingMemStore.IndexType chunkIndexType) { 163 return getChunk(chunkIndexType, ChunkType.DATA_CHUNK); 164 } 165 166 /** 167 * Creates and inits a chunk with specific index type and type. 168 * @return the chunk that was initialized 169 */ 170 Chunk getChunk(CompactingMemStore.IndexType chunkIndexType, ChunkType chunkType) { 171 switch (chunkType) { 172 case INDEX_CHUNK: 173 if (indexChunksPool != null) { 174 return getChunk(chunkIndexType, indexChunksPool.getChunkSize()); 175 } 176 case DATA_CHUNK: 177 if (dataChunksPool == null) { 178 return getChunk(chunkIndexType, chunkSize); 179 } else { 180 return getChunk(chunkIndexType, dataChunksPool.getChunkSize()); 181 } 182 default: 183 throw new IllegalArgumentException( 184 "chunkType must either be INDEX_CHUNK or DATA_CHUNK"); 185 } 186 } 187 188 /** 189 * Creates and inits a chunk. 190 * @return the chunk that was initialized 191 * @param chunkIndexType whether the requested chunk is going to be used with CellChunkMap index 192 * @param size the size of the chunk to be allocated, in bytes 193 */ 194 Chunk getChunk(CompactingMemStore.IndexType chunkIndexType, int size) { 195 Chunk chunk = null; 196 MemStoreChunkPool pool = null; 197 198 // if the size is suitable for one of the pools 199 if (dataChunksPool != null && size == dataChunksPool.getChunkSize()) { 200 pool = dataChunksPool; 201 } else if (indexChunksPool != null && size == indexChunksPool.getChunkSize()) { 202 pool = indexChunksPool; 203 } 204 205 // if we have a pool 206 if (pool != null) { 207 // the pool creates the chunk internally. The chunk#init() call happens here 208 chunk = pool.getChunk(); 209 // the pool has run out of maxCount 210 if (chunk == null) { 211 if (LOG.isTraceEnabled()) { 212 LOG.trace("The chunk pool is full. Reached maxCount= " + pool.getMaxCount() 213 + ". Creating chunk onheap."); 214 } 215 } 216 } 217 218 if (chunk == null) { 219 // the second parameter explains whether CellChunkMap index is requested, 220 // in that case, put allocated on demand chunk mapping into chunkIdMap 221 chunk = createChunk(false, chunkIndexType, size); 222 } 223 224 // now we need to actually do the expensive memory allocation step in case of a new chunk, 225 // else only the offset is set to the beginning of the chunk to accept allocations 226 chunk.init(); 227 return chunk; 228 } 229 230 /** 231 * Creates and inits a chunk of a special size, bigger than a regular chunk size. 232 * Such a chunk will never come from pool and will always be on demand allocated. 233 * @return the chunk that was initialized 234 * @param jumboSize the special size to be used 235 */ 236 Chunk getJumboChunk(int jumboSize) { 237 int allocSize = jumboSize + SIZEOF_CHUNK_HEADER; 238 if (allocSize <= dataChunksPool.getChunkSize()) { 239 LOG.warn("Jumbo chunk size " + jumboSize + " must be more than regular chunk size " 240 + dataChunksPool.getChunkSize() + ". Converting to regular chunk."); 241 return getChunk(CompactingMemStore.IndexType.CHUNK_MAP); 242 } 243 // the new chunk is going to hold the jumbo cell data and needs to be referenced by 244 // a strong map. Therefore the CCM index type 245 return getChunk(CompactingMemStore.IndexType.CHUNK_MAP, allocSize); 246 } 247 248 /** 249 * Creates the chunk either onheap or offheap 250 * @param pool indicates if the chunks have to be created which will be used by the Pool 251 * @param chunkIndexType whether the requested chunk is going to be used with CellChunkMap index 252 * @param size the size of the chunk to be allocated, in bytes 253 * @return the chunk 254 */ 255 private Chunk createChunk(boolean pool, CompactingMemStore.IndexType chunkIndexType, int size) { 256 Chunk chunk = null; 257 int id = chunkID.getAndIncrement(); 258 assert id > 0; 259 // do not create offheap chunk on demand 260 if (pool && this.offheap) { 261 chunk = new OffheapChunk(size, id, pool); 262 } else { 263 chunk = new OnheapChunk(size, id, pool); 264 } 265 if (pool || (chunkIndexType == CompactingMemStore.IndexType.CHUNK_MAP)) { 266 // put the pool chunk into the chunkIdMap so it is not GC-ed 267 this.chunkIdMap.put(chunk.getId(), chunk); 268 } 269 return chunk; 270 } 271 272 // Chunks from pool are created covered with strong references anyway 273 // TODO: change to CHUNK_MAP if it is generally defined 274 private Chunk createChunkForPool(CompactingMemStore.IndexType chunkIndexType, int chunkSize) { 275 if (chunkSize != dataChunksPool.getChunkSize() && 276 chunkSize != indexChunksPool.getChunkSize()) { 277 return null; 278 } 279 return createChunk(true, chunkIndexType, chunkSize); 280 } 281 282 @VisibleForTesting 283 // Used to translate the ChunkID into a chunk ref 284 Chunk getChunk(int id) { 285 // can return null if chunk was never mapped 286 return chunkIdMap.get(id); 287 } 288 289 boolean isOffheap() { 290 return this.offheap; 291 } 292 293 private void removeChunks(Set<Integer> chunkIDs) { 294 this.chunkIdMap.keySet().removeAll(chunkIDs); 295 } 296 297 Chunk removeChunk(int chunkId) { 298 return this.chunkIdMap.remove(chunkId); 299 } 300 301 @VisibleForTesting 302 // the chunks in the chunkIdMap may already be released so we shouldn't relay 303 // on this counting for strong correctness. This method is used only in testing. 304 int numberOfMappedChunks() { 305 return this.chunkIdMap.size(); 306 } 307 308 @VisibleForTesting 309 void clearChunkIds() { 310 this.chunkIdMap.clear(); 311 } 312 313 /** 314 * A pool of {@link Chunk} instances. 315 * 316 * MemStoreChunkPool caches a number of retired chunks for reusing, it could 317 * decrease allocating bytes when writing, thereby optimizing the garbage 318 * collection on JVM. 319 */ 320 private class MemStoreChunkPool implements HeapMemoryTuneObserver { 321 private final int chunkSize; 322 private int maxCount; 323 324 // A queue of reclaimed chunks 325 private final BlockingQueue<Chunk> reclaimedChunks; 326 private final float poolSizePercentage; 327 328 /** Statistics thread schedule pool */ 329 private final ScheduledExecutorService scheduleThreadPool; 330 /** Statistics thread */ 331 private static final int statThreadPeriod = 60 * 5; 332 private final AtomicLong chunkCount = new AtomicLong(); 333 private final LongAdder reusedChunkCount = new LongAdder(); 334 private final String label; 335 336 MemStoreChunkPool(String label, int chunkSize, int maxCount, int initialCount, 337 float poolSizePercentage) { 338 this.label = label; 339 this.chunkSize = chunkSize; 340 this.maxCount = maxCount; 341 this.poolSizePercentage = poolSizePercentage; 342 this.reclaimedChunks = new LinkedBlockingQueue<>(); 343 for (int i = 0; i < initialCount; i++) { 344 Chunk chunk = createChunk(true, CompactingMemStore.IndexType.ARRAY_MAP, chunkSize); 345 chunk.init(); 346 reclaimedChunks.add(chunk); 347 } 348 chunkCount.set(initialCount); 349 final String n = Thread.currentThread().getName(); 350 scheduleThreadPool = Executors.newScheduledThreadPool(1, new ThreadFactoryBuilder() 351 .setNameFormat(n + "-MemStoreChunkPool Statistics").setDaemon(true).build()); 352 this.scheduleThreadPool.scheduleAtFixedRate(new StatisticsThread(), statThreadPeriod, 353 statThreadPeriod, TimeUnit.SECONDS); 354 } 355 356 /** 357 * Poll a chunk from the pool, reset it if not null, else create a new chunk to return if we have 358 * not yet created max allowed chunks count. When we have already created max allowed chunks and 359 * no free chunks as of now, return null. It is the responsibility of the caller to make a chunk 360 * then. 361 * Note: Chunks returned by this pool must be put back to the pool after its use. 362 * @return a chunk 363 * @see #putbackChunks(Chunk) 364 */ 365 Chunk getChunk() { 366 return getChunk(CompactingMemStore.IndexType.ARRAY_MAP); 367 } 368 369 Chunk getChunk(CompactingMemStore.IndexType chunkIndexType) { 370 Chunk chunk = reclaimedChunks.poll(); 371 if (chunk != null) { 372 chunk.reset(); 373 reusedChunkCount.increment(); 374 } else { 375 // Make a chunk iff we have not yet created the maxCount chunks 376 while (true) { 377 long created = this.chunkCount.get(); 378 if (created < this.maxCount) { 379 if (this.chunkCount.compareAndSet(created, created + 1)) { 380 chunk = createChunkForPool(chunkIndexType, chunkSize); 381 break; 382 } 383 } else { 384 break; 385 } 386 } 387 } 388 return chunk; 389 } 390 391 int getChunkSize() { 392 return chunkSize; 393 } 394 395 /** 396 * Add the chunks to the pool, when the pool achieves the max size, it will skip the remaining 397 * chunks 398 * @param c 399 */ 400 private void putbackChunks(Chunk c) { 401 int toAdd = this.maxCount - reclaimedChunks.size(); 402 if (c.isFromPool() && c.size == chunkSize && toAdd > 0) { 403 reclaimedChunks.add(c); 404 } else { 405 // remove the chunk (that is not going to pool) 406 // though it is initially from the pool or not 407 ChunkCreator.this.removeChunk(c.getId()); 408 } 409 } 410 411 private class StatisticsThread extends Thread { 412 StatisticsThread() { 413 super("MemStoreChunkPool.StatisticsThread"); 414 setDaemon(true); 415 } 416 417 @Override 418 public void run() { 419 logStats(); 420 } 421 422 private void logStats() { 423 if (!LOG.isDebugEnabled()) return; 424 long created = chunkCount.get(); 425 long reused = reusedChunkCount.sum(); 426 long total = created + reused; 427 LOG.debug("{} stats (chunk size={}): current pool size={}, created chunk count={}, " + 428 "reused chunk count={}, reuseRatio={}", label, chunkSize, reclaimedChunks.size(), 429 created, reused, 430 (total == 0? "0": StringUtils.formatPercent((float)reused/(float)total,2))); 431 } 432 } 433 434 private int getMaxCount() { 435 return this.maxCount; 436 } 437 438 @Override 439 public void onHeapMemoryTune(long newMemstoreSize, long newBlockCacheSize) { 440 // don't do any tuning in case of offheap memstore 441 if (isOffheap()) { 442 LOG.warn("{} not tuning the chunk pool as it is offheap", label); 443 return; 444 } 445 int newMaxCount = 446 (int) (newMemstoreSize * poolSizePercentage / getChunkSize()); 447 if (newMaxCount != this.maxCount) { 448 // We need an adjustment in the chunks numbers 449 if (newMaxCount > this.maxCount) { 450 // Max chunks getting increased. Just change the variable. Later calls to getChunk() would 451 // create and add them to Q 452 LOG.info("{} max count for chunks increased from {} to {}", this.label, this.maxCount, 453 newMaxCount); 454 this.maxCount = newMaxCount; 455 } else { 456 // Max chunks getting decreased. We may need to clear off some of the pooled chunks now 457 // itself. If the extra chunks are serving already, do not pool those when we get them back 458 LOG.info("{} max count for chunks decreased from {} to {}", this.label, this.maxCount, 459 newMaxCount); 460 this.maxCount = newMaxCount; 461 if (this.reclaimedChunks.size() > newMaxCount) { 462 synchronized (this) { 463 while (this.reclaimedChunks.size() > newMaxCount) { 464 this.reclaimedChunks.poll(); 465 } 466 } 467 } 468 } 469 } 470 } 471 } 472 473 @VisibleForTesting 474 static void clearDisableFlag() { 475 chunkPoolDisabled = false; 476 } 477 478 private MemStoreChunkPool initializePool(String label, long globalMemStoreSize, 479 float poolSizePercentage, float initialCountPercentage, int chunkSize, 480 HeapMemoryManager heapMemoryManager) { 481 if (poolSizePercentage <= 0) { 482 LOG.info("{} poolSizePercentage is less than 0. So not using pool", label); 483 return null; 484 } 485 if (chunkPoolDisabled) { 486 return null; 487 } 488 if (poolSizePercentage > 1.0) { 489 throw new IllegalArgumentException( 490 MemStoreLAB.CHUNK_POOL_MAXSIZE_KEY + " must be between 0.0 and 1.0"); 491 } 492 int maxCount = (int) (globalMemStoreSize * poolSizePercentage / chunkSize); 493 if (initialCountPercentage > 1.0 || initialCountPercentage < 0) { 494 throw new IllegalArgumentException(label + " " + MemStoreLAB.CHUNK_POOL_INITIALSIZE_KEY + 495 " must be between 0.0 and 1.0"); 496 } 497 int initialCount = (int) (initialCountPercentage * maxCount); 498 LOG.info("Allocating {} MemStoreChunkPool with chunk size {}, max count {}, initial count {}", 499 label, StringUtils.byteDesc(chunkSize), maxCount, initialCount); 500 MemStoreChunkPool memStoreChunkPool = new MemStoreChunkPool(label, chunkSize, maxCount, 501 initialCount, poolSizePercentage); 502 if (heapMemoryManager != null && memStoreChunkPool != null) { 503 // Register with Heap Memory manager 504 heapMemoryManager.registerTuneObserver(memStoreChunkPool); 505 } 506 return memStoreChunkPool; 507 } 508 509 @VisibleForTesting 510 int getMaxCount() { 511 return getMaxCount(ChunkType.DATA_CHUNK); 512 } 513 514 @VisibleForTesting 515 int getMaxCount(ChunkType chunkType) { 516 switch (chunkType) { 517 case INDEX_CHUNK: 518 if (indexChunksPool != null) { 519 return indexChunksPool.getMaxCount(); 520 } 521 break; 522 case DATA_CHUNK: 523 if (dataChunksPool != null) { 524 return dataChunksPool.getMaxCount(); 525 } 526 break; 527 default: 528 throw new IllegalArgumentException( 529 "chunkType must either be INDEX_CHUNK or DATA_CHUNK"); 530 } 531 532 return 0; 533 } 534 535 @VisibleForTesting 536 int getPoolSize() { 537 return getPoolSize(ChunkType.DATA_CHUNK); 538 } 539 540 @VisibleForTesting 541 int getPoolSize(ChunkType chunkType) { 542 switch (chunkType) { 543 case INDEX_CHUNK: 544 if (indexChunksPool != null) { 545 return indexChunksPool.reclaimedChunks.size(); 546 } 547 break; 548 case DATA_CHUNK: 549 if (dataChunksPool != null) { 550 return dataChunksPool.reclaimedChunks.size(); 551 } 552 break; 553 default: 554 throw new IllegalArgumentException( 555 "chunkType must either be INDEX_CHUNK or DATA_CHUNK"); 556 } 557 return 0; 558 } 559 560 @VisibleForTesting 561 boolean isChunkInPool(int chunkId) { 562 Chunk c = getChunk(chunkId); 563 if (c==null) { 564 return false; 565 } 566 567 // chunks that are from pool will return true chunk reference not null 568 if (dataChunksPool != null && dataChunksPool.reclaimedChunks.contains(c)) { 569 return true; 570 } else if (indexChunksPool != null && indexChunksPool.reclaimedChunks.contains(c)) { 571 return true; 572 } 573 return false; 574 } 575 576 /* 577 * Only used in testing 578 */ 579 @VisibleForTesting 580 void clearChunksInPool() { 581 if (dataChunksPool != null) { 582 dataChunksPool.reclaimedChunks.clear(); 583 } 584 if (indexChunksPool != null) { 585 indexChunksPool.reclaimedChunks.clear(); 586 } 587 } 588 589 int getChunkSize() { 590 return getChunkSize(ChunkType.DATA_CHUNK); 591 } 592 593 int getChunkSize(ChunkType chunkType) { 594 switch (chunkType) { 595 case INDEX_CHUNK: 596 if (indexChunksPool != null) { 597 return indexChunksPool.getChunkSize(); 598 } 599 case DATA_CHUNK: 600 if (dataChunksPool != null) { 601 return dataChunksPool.getChunkSize(); 602 } else { // When pools are empty 603 return chunkSize; 604 } 605 default: 606 throw new IllegalArgumentException( 607 "chunkType must either be INDEX_CHUNK or DATA_CHUNK"); 608 } 609 } 610 611 synchronized void putbackChunks(Set<Integer> chunks) { 612 // if there is no pool just try to clear the chunkIdMap in case there is something 613 if (dataChunksPool == null && indexChunksPool == null) { 614 this.removeChunks(chunks); 615 return; 616 } 617 618 // if there is a pool, go over all chunk IDs that came back, the chunks may be from pool or not 619 for (int chunkID : chunks) { 620 // translate chunk ID to chunk, if chunk initially wasn't in pool 621 // this translation will (most likely) return null 622 Chunk chunk = ChunkCreator.this.getChunk(chunkID); 623 if (chunk != null) { 624 if (chunk.isFromPool() && chunk.isIndexChunk()) { 625 indexChunksPool.putbackChunks(chunk); 626 } else if (chunk.isFromPool() && chunk.size == dataChunksPool.getChunkSize()) { 627 dataChunksPool.putbackChunks(chunk); 628 } else { 629 // chunks which are not from one of the pools 630 // should be released without going to the pools. 631 // Removing them from chunkIdMap will cause their removal by the GC. 632 this.removeChunk(chunkID); 633 } 634 } 635 // if chunk is null, it was never covered by the chunkIdMap (and so wasn't in pool also), 636 // so we have nothing to do on its release 637 } 638 return; 639 } 640 641} 642