View Javadoc

1   /**
2    * Copyright 2009 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.io.hfile;
21  
22  import java.lang.ref.WeakReference;
23  import java.nio.ByteBuffer;
24  import java.util.LinkedList;
25  import java.util.PriorityQueue;
26  import java.util.concurrent.atomic.AtomicLong;
27  import java.util.concurrent.locks.ReentrantLock;
28  import java.util.concurrent.ConcurrentHashMap;
29  import java.util.concurrent.ScheduledExecutorService;
30  import java.util.concurrent.Executors;
31  import java.util.concurrent.TimeUnit;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.apache.hadoop.hbase.io.HeapSize;
36  import org.apache.hadoop.hbase.util.Bytes;
37  import org.apache.hadoop.hbase.util.ClassSize;
38  import org.apache.hadoop.util.StringUtils;
39  
40  import com.google.common.util.concurrent.ThreadFactoryBuilder;
41  
42  /**
43   * A block cache implementation that is memory-aware using {@link HeapSize},
44   * memory-bound using an LRU eviction algorithm, and concurrent: backed by a
45   * {@link ConcurrentHashMap} and with a non-blocking eviction thread giving
46   * constant-time {@link #cacheBlock} and {@link #getBlock} operations.<p>
47   *
48   * Contains three levels of block priority to allow for
49   * scan-resistance and in-memory families.  A block is added with an inMemory
50   * flag if necessary, otherwise a block becomes a single access priority.  Once
51   * a blocked is accessed again, it changes to multiple access.  This is used
52   * to prevent scans from thrashing the cache, adding a least-frequently-used
53   * element to the eviction algorithm.<p>
54   *
55   * Each priority is given its own chunk of the total cache to ensure
56   * fairness during eviction.  Each priority will retain close to its maximum
57   * size, however, if any priority is not using its entire chunk the others
58   * are able to grow beyond their chunk size.<p>
59   *
60   * Instantiated at a minimum with the total size and average block size.
61   * All sizes are in bytes.  The block size is not especially important as this
62   * cache is fully dynamic in its sizing of blocks.  It is only used for
63   * pre-allocating data structures and in initial heap estimation of the map.<p>
64   *
65   * The detailed constructor defines the sizes for the three priorities (they
66   * should total to the maximum size defined).  It also sets the levels that
67   * trigger and control the eviction thread.<p>
68   *
69   * The acceptable size is the cache size level which triggers the eviction
70   * process to start.  It evicts enough blocks to get the size below the
71   * minimum size specified.<p>
72   *
73   * Eviction happens in a separate thread and involves a single full-scan
74   * of the map.  It determines how many bytes must be freed to reach the minimum
75   * size, and then while scanning determines the fewest least-recently-used
76   * blocks necessary from each of the three priorities (would be 3 times bytes
77   * to free).  It then uses the priority chunk sizes to evict fairly according
78   * to the relative sizes and usage.
79   */
80  public class LruBlockCache implements BlockCache, HeapSize {
81  
82    static final Log LOG = LogFactory.getLog(LruBlockCache.class);
83  
84    /** Default Configuration Parameters*/
85  
86    /** Backing Concurrent Map Configuration */
87    static final float DEFAULT_LOAD_FACTOR = 0.75f;
88    static final int DEFAULT_CONCURRENCY_LEVEL = 16;
89  
90    /** Eviction thresholds */
91    static final float DEFAULT_MIN_FACTOR = 0.75f;
92    static final float DEFAULT_ACCEPTABLE_FACTOR = 0.85f;
93  
94    /** Priority buckets */
95    static final float DEFAULT_SINGLE_FACTOR = 0.25f;
96    static final float DEFAULT_MULTI_FACTOR = 0.50f;
97    static final float DEFAULT_MEMORY_FACTOR = 0.25f;
98  
99    /** Statistics thread */
100   static final int statThreadPeriod = 60 * 5;
101 
102   /** Concurrent map (the cache) */
103   private final ConcurrentHashMap<String,CachedBlock> map;
104 
105   /** Eviction lock (locked when eviction in process) */
106   private final ReentrantLock evictionLock = new ReentrantLock(true);
107 
108   /** Volatile boolean to track if we are in an eviction process or not */
109   private volatile boolean evictionInProgress = false;
110 
111   /** Eviction thread */
112   private final EvictionThread evictionThread;
113 
114   /** Statistics thread schedule pool (for heavy debugging, could remove) */
115   private final ScheduledExecutorService scheduleThreadPool =
116     Executors.newScheduledThreadPool(1,
117       new ThreadFactoryBuilder()
118         .setNameFormat("LRU Statistics #%d")
119         .build());
120 
121   /** Current size of cache */
122   private final AtomicLong size;
123 
124   /** Current number of cached elements */
125   private final AtomicLong elements;
126 
127   /** Cache access count (sequential ID) */
128   private final AtomicLong count;
129 
130   /** Cache statistics */
131   private final CacheStats stats;
132 
133   /** Maximum allowable size of cache (block put if size > max, evict) */
134   private long maxSize;
135 
136   /** Approximate block size */
137   private long blockSize;
138 
139   /** Acceptable size of cache (no evictions if size < acceptable) */
140   private float acceptableFactor;
141 
142   /** Minimum threshold of cache (when evicting, evict until size < min) */
143   private float minFactor;
144 
145   /** Single access bucket size */
146   private float singleFactor;
147 
148   /** Multiple access bucket size */
149   private float multiFactor;
150 
151   /** In-memory bucket size */
152   private float memoryFactor;
153 
154   /** Overhead of the structure itself */
155   private long overhead;
156 
157   /**
158    * Default constructor.  Specify maximum size and expected average block
159    * size (approximation is fine).
160    *
161    * <p>All other factors will be calculated based on defaults specified in
162    * this class.
163    * @param maxSize maximum size of cache, in bytes
164    * @param blockSize approximate size of each block, in bytes
165    */
166   public LruBlockCache(long maxSize, long blockSize) {
167     this(maxSize, blockSize, true);
168   }
169 
170   /**
171    * Constructor used for testing.  Allows disabling of the eviction thread.
172    */
173   public LruBlockCache(long maxSize, long blockSize, boolean evictionThread) {
174     this(maxSize, blockSize, evictionThread,
175         (int)Math.ceil(1.2*maxSize/blockSize),
176         DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL,
177         DEFAULT_MIN_FACTOR, DEFAULT_ACCEPTABLE_FACTOR,
178         DEFAULT_SINGLE_FACTOR, DEFAULT_MULTI_FACTOR,
179         DEFAULT_MEMORY_FACTOR);
180   }
181 
182   /**
183    * Configurable constructor.  Use this constructor if not using defaults.
184    * @param maxSize maximum size of this cache, in bytes
185    * @param blockSize expected average size of blocks, in bytes
186    * @param evictionThread whether to run evictions in a bg thread or not
187    * @param mapInitialSize initial size of backing ConcurrentHashMap
188    * @param mapLoadFactor initial load factor of backing ConcurrentHashMap
189    * @param mapConcurrencyLevel initial concurrency factor for backing CHM
190    * @param minFactor percentage of total size that eviction will evict until
191    * @param acceptableFactor percentage of total size that triggers eviction
192    * @param singleFactor percentage of total size for single-access blocks
193    * @param multiFactor percentage of total size for multiple-access blocks
194    * @param memoryFactor percentage of total size for in-memory blocks
195    */
196   public LruBlockCache(long maxSize, long blockSize, boolean evictionThread,
197       int mapInitialSize, float mapLoadFactor, int mapConcurrencyLevel,
198       float minFactor, float acceptableFactor,
199       float singleFactor, float multiFactor, float memoryFactor) {
200     if(singleFactor + multiFactor + memoryFactor != 1) {
201       throw new IllegalArgumentException("Single, multi, and memory factors " +
202           " should total 1.0");
203     }
204     if(minFactor >= acceptableFactor) {
205       throw new IllegalArgumentException("minFactor must be smaller than acceptableFactor");
206     }
207     if(minFactor >= 1.0f || acceptableFactor >= 1.0f) {
208       throw new IllegalArgumentException("all factors must be < 1");
209     }
210     this.maxSize = maxSize;
211     this.blockSize = blockSize;
212     map = new ConcurrentHashMap<String,CachedBlock>(mapInitialSize,
213         mapLoadFactor, mapConcurrencyLevel);
214     this.minFactor = minFactor;
215     this.acceptableFactor = acceptableFactor;
216     this.singleFactor = singleFactor;
217     this.multiFactor = multiFactor;
218     this.memoryFactor = memoryFactor;
219     this.stats = new CacheStats();
220     this.count = new AtomicLong(0);
221     this.elements = new AtomicLong(0);
222     this.overhead = calculateOverhead(maxSize, blockSize, mapConcurrencyLevel);
223     this.size = new AtomicLong(this.overhead);
224     if(evictionThread) {
225       this.evictionThread = new EvictionThread(this);
226       this.evictionThread.start(); // FindBugs SC_START_IN_CTOR
227     } else {
228       this.evictionThread = null;
229     }
230     this.scheduleThreadPool.scheduleAtFixedRate(new StatisticsThread(this),
231         statThreadPeriod, statThreadPeriod, TimeUnit.SECONDS);
232   }
233 
234   public void setMaxSize(long maxSize) {
235     this.maxSize = maxSize;
236     if(this.size.get() > acceptableSize() && !evictionInProgress) {
237       runEviction();
238     }
239   }
240 
241   // BlockCache implementation
242 
243   /**
244    * Cache the block with the specified name and buffer.
245    * <p>
246    * It is assumed this will NEVER be called on an already cached block.  If
247    * that is done, it is assumed that you are reinserting the same exact
248    * block due to a race condition and will update the buffer but not modify
249    * the size of the cache.
250    * @param blockName block name
251    * @param buf block buffer
252    * @param inMemory if block is in-memory
253    */
254   public void cacheBlock(String blockName, ByteBuffer buf, boolean inMemory) {
255     CachedBlock cb = map.get(blockName);
256     if(cb != null) {
257       throw new RuntimeException("Cached an already cached block");
258     }
259     cb = new CachedBlock(blockName, buf, count.incrementAndGet(), inMemory);
260     long newSize = size.addAndGet(cb.heapSize());
261     map.put(blockName, cb);
262     elements.incrementAndGet();
263     if(newSize > acceptableSize() && !evictionInProgress) {
264       runEviction();
265     }
266   }
267 
268   /**
269    * Cache the block with the specified name and buffer.
270    * <p>
271    * It is assumed this will NEVER be called on an already cached block.  If
272    * that is done, it is assumed that you are reinserting the same exact
273    * block due to a race condition and will update the buffer but not modify
274    * the size of the cache.
275    * @param blockName block name
276    * @param buf block buffer
277    */
278   public void cacheBlock(String blockName, ByteBuffer buf) {
279     cacheBlock(blockName, buf, false);
280   }
281 
282   /**
283    * Get the buffer of the block with the specified name.
284    * @param blockName block name
285    * @return buffer of specified block name, or null if not in cache
286    */
287   public ByteBuffer getBlock(String blockName, boolean caching) {
288     CachedBlock cb = map.get(blockName);
289     if(cb == null) {
290       stats.miss(caching);
291       return null;
292     }
293     stats.hit(caching);
294     cb.access(count.incrementAndGet());
295     return cb.getBuffer();
296   }
297 
298   protected long evictBlock(CachedBlock block) {
299     map.remove(block.getName());
300     size.addAndGet(-1 * block.heapSize());
301     elements.decrementAndGet();
302     stats.evicted();
303     return block.heapSize();
304   }
305 
306   /**
307    * Multi-threaded call to run the eviction process.
308    */
309   private void runEviction() {
310     if(evictionThread == null) {
311       evict();
312     } else {
313       evictionThread.evict();
314     }
315   }
316 
317   /**
318    * Eviction method.
319    */
320   void evict() {
321 
322     // Ensure only one eviction at a time
323     if(!evictionLock.tryLock()) return;
324 
325     try {
326       evictionInProgress = true;
327       long currentSize = this.size.get();
328       long bytesToFree = currentSize - minSize();
329 
330       if (LOG.isDebugEnabled()) {
331         LOG.debug("Block cache LRU eviction started; Attempting to free " +
332           StringUtils.byteDesc(bytesToFree) + " of total=" +
333           StringUtils.byteDesc(currentSize));
334       }
335 
336       if(bytesToFree <= 0) return;
337 
338       // Instantiate priority buckets
339       BlockBucket bucketSingle = new BlockBucket(bytesToFree, blockSize,
340           singleSize());
341       BlockBucket bucketMulti = new BlockBucket(bytesToFree, blockSize,
342           multiSize());
343       BlockBucket bucketMemory = new BlockBucket(bytesToFree, blockSize,
344           memorySize());
345 
346       // Scan entire map putting into appropriate buckets
347       for(CachedBlock cachedBlock : map.values()) {
348         switch(cachedBlock.getPriority()) {
349           case SINGLE: {
350             bucketSingle.add(cachedBlock);
351             break;
352           }
353           case MULTI: {
354             bucketMulti.add(cachedBlock);
355             break;
356           }
357           case MEMORY: {
358             bucketMemory.add(cachedBlock);
359             break;
360           }
361         }
362       }
363 
364       PriorityQueue<BlockBucket> bucketQueue =
365         new PriorityQueue<BlockBucket>(3);
366 
367       bucketQueue.add(bucketSingle);
368       bucketQueue.add(bucketMulti);
369       bucketQueue.add(bucketMemory);
370 
371       int remainingBuckets = 3;
372       long bytesFreed = 0;
373 
374       BlockBucket bucket;
375       while((bucket = bucketQueue.poll()) != null) {
376         long overflow = bucket.overflow();
377         if(overflow > 0) {
378           long bucketBytesToFree = Math.min(overflow,
379             (bytesToFree - bytesFreed) / remainingBuckets);
380           bytesFreed += bucket.free(bucketBytesToFree);
381         }
382         remainingBuckets--;
383       }
384 
385       if (LOG.isDebugEnabled()) {
386         long single = bucketSingle.totalSize();
387         long multi = bucketMulti.totalSize();
388         long memory = bucketMemory.totalSize();
389         LOG.debug("Block cache LRU eviction completed; " +
390           "freed=" + StringUtils.byteDesc(bytesFreed) + ", " +
391           "total=" + StringUtils.byteDesc(this.size.get()) + ", " +
392           "single=" + StringUtils.byteDesc(single) + ", " +
393           "multi=" + StringUtils.byteDesc(multi) + ", " +
394           "memory=" + StringUtils.byteDesc(memory));
395       }
396     } finally {
397       stats.evict();
398       evictionInProgress = false;
399       evictionLock.unlock();
400     }
401   }
402 
403   /**
404    * Used to group blocks into priority buckets.  There will be a BlockBucket
405    * for each priority (single, multi, memory).  Once bucketed, the eviction
406    * algorithm takes the appropriate number of elements out of each according
407    * to configuration parameters and their relatives sizes.
408    */
409   private class BlockBucket implements Comparable<BlockBucket> {
410 
411     private CachedBlockQueue queue;
412     private long totalSize = 0;
413     private long bucketSize;
414 
415     public BlockBucket(long bytesToFree, long blockSize, long bucketSize) {
416       this.bucketSize = bucketSize;
417       queue = new CachedBlockQueue(bytesToFree, blockSize);
418       totalSize = 0;
419     }
420 
421     public void add(CachedBlock block) {
422       totalSize += block.heapSize();
423       queue.add(block);
424     }
425 
426     public long free(long toFree) {
427       LinkedList<CachedBlock> blocks = queue.get();
428       long freedBytes = 0;
429       for(CachedBlock cb: blocks) {
430         freedBytes += evictBlock(cb);
431         if(freedBytes >= toFree) {
432           return freedBytes;
433         }
434       }
435       return freedBytes;
436     }
437 
438     public long overflow() {
439       return totalSize - bucketSize;
440     }
441 
442     public long totalSize() {
443       return totalSize;
444     }
445 
446     public int compareTo(BlockBucket that) {
447       if(this.overflow() == that.overflow()) return 0;
448       return this.overflow() > that.overflow() ? 1 : -1;
449     }
450   }
451 
452   /**
453    * Get the maximum size of this cache.
454    * @return max size in bytes
455    */
456   public long getMaxSize() {
457     return this.maxSize;
458   }
459 
460   /**
461    * Get the current size of this cache.
462    * @return current size in bytes
463    */
464   public long getCurrentSize() {
465     return this.size.get();
466   }
467 
468   /**
469    * Get the current size of this cache.
470    * @return current size in bytes
471    */
472   public long getFreeSize() {
473     return getMaxSize() - getCurrentSize();
474   }
475 
476   /**
477    * Get the size of this cache (number of cached blocks)
478    * @return number of cached blocks
479    */
480   public long size() {
481     return this.elements.get();
482   }
483 
484   /**
485    * Get the number of eviction runs that have occurred
486    */
487   public long getEvictionCount() {
488     return this.stats.getEvictionCount();
489   }
490 
491   /**
492    * Get the number of blocks that have been evicted during the lifetime
493    * of this cache.
494    */
495   public long getEvictedCount() {
496     return this.stats.getEvictedCount();
497   }
498 
499   /*
500    * Eviction thread.  Sits in waiting state until an eviction is triggered
501    * when the cache size grows above the acceptable level.<p>
502    *
503    * Thread is triggered into action by {@link LruBlockCache#runEviction()}
504    */
505   private static class EvictionThread extends Thread {
506     private WeakReference<LruBlockCache> cache;
507 
508     public EvictionThread(LruBlockCache cache) {
509       super("LruBlockCache.EvictionThread");
510       setDaemon(true);
511       this.cache = new WeakReference<LruBlockCache>(cache);
512     }
513 
514     @Override
515     public void run() {
516       while(true) {
517         synchronized(this) {
518           try {
519             this.wait();
520           } catch(InterruptedException e) {}
521         }
522         LruBlockCache cache = this.cache.get();
523         if(cache == null) break;
524         cache.evict();
525       }
526     }
527     public void evict() {
528       synchronized(this) {
529         this.notify(); // FindBugs NN_NAKED_NOTIFY
530       }
531     }
532   }
533 
534   /*
535    * Statistics thread.  Periodically prints the cache statistics to the log.
536    */
537   static class StatisticsThread extends Thread {
538     LruBlockCache lru;
539 
540     public StatisticsThread(LruBlockCache lru) {
541       super("LruBlockCache.StatisticsThread");
542       setDaemon(true);
543       this.lru = lru;
544     }
545     @Override
546     public void run() {
547       lru.logStats();
548     }
549   }
550 
551   public void logStats() {
552     if (!LOG.isDebugEnabled()) return;
553     // Log size
554     long totalSize = heapSize();
555     long freeSize = maxSize - totalSize;
556     LruBlockCache.LOG.debug("LRU Stats: " +
557         "total=" + StringUtils.byteDesc(totalSize) + ", " +
558         "free=" + StringUtils.byteDesc(freeSize) + ", " +
559         "max=" + StringUtils.byteDesc(this.maxSize) + ", " +
560         "blocks=" + size() +", " +
561         "accesses=" + stats.getRequestCount() + ", " +
562         "hits=" + stats.getHitCount() + ", " +
563         "hitRatio=" + StringUtils.formatPercent(stats.getHitRatio(), 2) + "%, "+
564         "cachingAccesses=" + stats.getRequestCachingCount() + ", " +
565         "cachingHits=" + stats.getHitCachingCount() + ", " +
566         "cachingHitsRatio=" +
567           StringUtils.formatPercent(stats.getHitCachingRatio(), 2) + "%, " +
568         "evictions=" + stats.getEvictionCount() + ", " +
569         "evicted=" + stats.getEvictedCount() + ", " +
570         "evictedPerRun=" + stats.evictedPerEviction());
571   }
572 
573   /**
574    * Get counter statistics for this cache.
575    *
576    * <p>Includes: total accesses, hits, misses, evicted blocks, and runs
577    * of the eviction processes.
578    */
579   public CacheStats getStats() {
580     return this.stats;
581   }
582 
583   public static class CacheStats {
584     /** The number of getBlock requests that were cache hits */
585     private final AtomicLong hitCount = new AtomicLong(0);
586     /**
587      * The number of getBlock requests that were cache hits, but only from
588      * requests that were set to use the block cache.  This is because all reads
589      * attempt to read from the block cache even if they will not put new blocks
590      * into the block cache.  See HBASE-2253 for more information.
591      */
592     private final AtomicLong hitCachingCount = new AtomicLong(0);
593     /** The number of getBlock requests that were cache misses */
594     private final AtomicLong missCount = new AtomicLong(0);
595     /**
596      * The number of getBlock requests that were cache misses, but only from
597      * requests that were set to use the block cache.
598      */
599     private final AtomicLong missCachingCount = new AtomicLong(0);
600     /** The number of times an eviction has occurred */
601     private final AtomicLong evictionCount = new AtomicLong(0);
602     /** The total number of blocks that have been evicted */
603     private final AtomicLong evictedCount = new AtomicLong(0);
604 
605     public void miss(boolean caching) {
606       missCount.incrementAndGet();
607       if (caching) missCachingCount.incrementAndGet();
608     }
609 
610     public void hit(boolean caching) {
611       hitCount.incrementAndGet();
612       if (caching) hitCachingCount.incrementAndGet();
613     }
614 
615     public void evict() {
616       evictionCount.incrementAndGet();
617     }
618 
619     public void evicted() {
620       evictedCount.incrementAndGet();
621     }
622 
623     public long getRequestCount() {
624       return getHitCount() + getMissCount();
625     }
626 
627     public long getRequestCachingCount() {
628       return getHitCachingCount() + getMissCachingCount();
629     }
630 
631     public long getMissCount() {
632       return missCount.get();
633     }
634 
635     public long getMissCachingCount() {
636       return missCachingCount.get();
637     }
638 
639     public long getHitCount() {
640       return hitCount.get();
641     }
642 
643     public long getHitCachingCount() {
644       return hitCachingCount.get();
645     }
646 
647     public long getEvictionCount() {
648       return evictionCount.get();
649     }
650 
651     public long getEvictedCount() {
652       return evictedCount.get();
653     }
654 
655     public double getHitRatio() {
656       return ((float)getHitCount()/(float)getRequestCount());
657     }
658 
659     public double getHitCachingRatio() {
660       return ((float)getHitCachingCount()/(float)getRequestCachingCount());
661     }
662 
663     public double getMissRatio() {
664       return ((float)getMissCount()/(float)getRequestCount());
665     }
666 
667     public double getMissCachingRatio() {
668       return ((float)getMissCachingCount()/(float)getRequestCachingCount());
669     }
670 
671     public double evictedPerEviction() {
672       return ((float)getEvictedCount()/(float)getEvictionCount());
673     }
674   }
675 
676   public final static long CACHE_FIXED_OVERHEAD = ClassSize.align(
677       (3 * Bytes.SIZEOF_LONG) + (8 * ClassSize.REFERENCE) +
678       (5 * Bytes.SIZEOF_FLOAT) + Bytes.SIZEOF_BOOLEAN
679       + ClassSize.OBJECT);
680 
681   // HeapSize implementation
682   public long heapSize() {
683     return getCurrentSize();
684   }
685 
686   public static long calculateOverhead(long maxSize, long blockSize, int concurrency){
687     // FindBugs ICAST_INTEGER_MULTIPLY_CAST_TO_LONG
688     return CACHE_FIXED_OVERHEAD + ClassSize.CONCURRENT_HASHMAP +
689         ((long)Math.ceil(maxSize*1.2/blockSize)
690             * ClassSize.CONCURRENT_HASHMAP_ENTRY) +
691         (concurrency * ClassSize.CONCURRENT_HASHMAP_SEGMENT);
692   }
693 
694   // Simple calculators of sizes given factors and maxSize
695 
696   private long acceptableSize() {
697     return (long)Math.floor(this.maxSize * this.acceptableFactor);
698   }
699   private long minSize() {
700     return (long)Math.floor(this.maxSize * this.minFactor);
701   }
702   private long singleSize() {
703     return (long)Math.floor(this.maxSize * this.singleFactor * this.minFactor);
704   }
705   private long multiSize() {
706     return (long)Math.floor(this.maxSize * this.multiFactor * this.minFactor);
707   }
708   private long memorySize() {
709     return (long)Math.floor(this.maxSize * this.memoryFactor * this.minFactor);
710   }
711 
712   public void shutdown() {
713     this.scheduleThreadPool.shutdown();
714   }
715 }