FileDocCategorySizeDatePackage
DirectByteBufferPool.javaAPI DocAzureus 3.0.3.427039Thu Jan 04 14:27:04 GMT 2007org.gudy.azureus2.core3.util

DirectByteBufferPool

public class DirectByteBufferPool extends Object
This class handles allocation of direct ByteBuffers. It always tries to find a free buffer in the buffer pool before creating a new one.

Fields Summary
protected static final boolean
DEBUG_TRACK_HANDEDOUT
protected static final boolean
DEBUG_PRINT_MEM
protected static final int
DEBUG_PRINT_TIME
protected static final boolean
DEBUG_HANDOUT_SIZES
private static final int
START_POWER
private static final int
END_POWER
private static final int[]
EXTRA_BUCKETS
public static final int
MAX_SIZE
private static final DirectByteBufferPool
pool
private final Map
buffersMap
private final Object
poolsLock
private static final int
SLICE_END_SIZE
private static final int
SLICE_ALLOC_CHUNK_SIZE
private static final short[]
SLICE_ENTRY_SIZES
private static final short[]
SLICE_ALLOC_MAXS
private static final short[]
SLICE_ENTRY_ALLOC_SIZES
private static final List[]
slice_entries
private static final boolean[]
slice_allocs
private static final boolean[]
slice_alloc_fails
private static final long[]
slice_use_count
private final Map
handed_out
private final Map
size_counts
private static final long
COMPACTION_CHECK_PERIOD
private static final long
MAX_FREE_BYTES
private long
bytesIn
private long
bytesOut
Constructors Summary
private DirectByteBufferPool()

  
  
	 
	 
	
	  	
	    //create the buffer pool for each buffer size
	  	
	  	ArrayList	list = new ArrayList();
	  	
	    for (int p=START_POWER; p <= END_POWER; p++) {
	    	
	    	list.add( new Integer(BigInteger.valueOf(2).pow(p).intValue()));
	    }
	    
	    for (int i=0;i<EXTRA_BUCKETS.length;i++){
	    	       
	        list.add( new Integer(EXTRA_BUCKETS[i]));
	    }
	    
	    Integer[]	sizes = new Integer[ list.size() ];
	    list.toArray( sizes );
	    Arrays.sort( sizes);
	    
	    for (int i=0;i<sizes.length;i++){
	    	
	    	ArrayList bufferPool = new ArrayList();
	    	
	    	buffersMap.put(sizes[i], bufferPool);
	    }
	    
	    //initiate periodic timer to check free memory usage
	    SimpleTimer.addPeriodicEvent(
	    	"DirectBB:compact",
	        COMPACTION_CHECK_PERIOD,
	        new TimerEventPerformer() {
	          public void perform( TimerEvent ev ) {
	       
	            compactBuffers();
	          }
	        }
	     );
	    
	    if( DEBUG_PRINT_MEM ) {
	      Timer printer = new Timer("printer");
	      printer.addPeriodicEvent(
	          DEBUG_PRINT_TIME,
	          new TimerEventPerformer() {
	            public void perform( TimerEvent ev ) {
	              System.out.print("DIRECT: given=" +bytesOut/1024/1024+ "MB, returned=" +bytesIn/1024/1024+ "MB, ");
	              
	              long in_use = bytesOut - bytesIn;
	              if( in_use < 1024*1024 ) System.out.print( "in use=" +in_use+ "B, " );
	              else System.out.print( "in use=" +in_use/1024/1024+ "MB, " );
	              
	              long free = bytesFree();
	              if( free < 1024*1024 ) System.out.print( "free=" +free+ "B" );
	              else System.out.print( "free=" +free/1024/1024+ "MB" );
	
	              System.out.println();
	              
	              printInUse( false );
	              
	              long free_mem = Runtime.getRuntime().freeMemory() /1024/1024;
	              long max_mem = Runtime.getRuntime().maxMemory() /1024/1024;
	              long total_mem = Runtime.getRuntime().totalMemory() /1024/1024;
	              System.out.println("HEAP: max=" +max_mem+ "MB, total=" +total_mem+ "MB, free=" +free_mem+ "MB");
	              System.out.println();
	            }
	          }
	      );
	    }
	
Methods Summary
private java.nio.ByteBufferallocateNewBuffer(int _size)
Allocate and return a new direct ByteBuffer.

    try {
      return ByteBuffer.allocateDirect(_size);
    }
    catch (OutOfMemoryError e) {
       //Debug.out("Running garbage collector...");
       
       clearBufferPools();
       
       runGarbageCollection();

       try {
       		return ByteBuffer.allocateDirect(_size);
       	
       }catch (OutOfMemoryError ex) {
       	
         String msg = "Memory allocation failed: Out of direct memory space.\n"
                    + "To fix: Use the -XX:MaxDirectMemorySize=512m command line option,\n"
                    + "or upgrade your Java JRE to version 1.4.2_05 or 1.5 series or newer.";
       	 Debug.out( msg );
       	 
       	 Logger.log(new LogAlert(LogAlert.UNREPEATABLE, LogAlert.AT_ERROR, msg));
         
         printInUse( true );
         
         throw( ex );
       }
    }
  
private longbytesFree()

    long bytesUsed = 0;
    synchronized( poolsLock ) {
      //count up total bytes used by free buffers
      Iterator it = buffersMap.keySet().iterator();
      while (it.hasNext()) {
        Integer keyVal = (Integer)it.next();
        ArrayList bufferPool = (ArrayList)buffersMap.get(keyVal);
      
        bytesUsed += keyVal.intValue() * bufferPool.size();
      }
    }
    return bytesUsed;
  
private voidclearBufferPools()
Clears the free buffer pools so that currently unused buffers can be garbage collected.

    Iterator it = buffersMap.values().iterator();
    while (it.hasNext()) {
        ArrayList bufferPool = (ArrayList)it.next();
        bufferPool.clear();
    }
  
private voidcompactBuffers()
Checks memory usage of free buffers in buffer pools, and calls the compaction method if necessary.

    long bytesUsed = 0;
    
    synchronized( poolsLock ) {
      
      //count up total bytes used by free buffers
      Iterator it = buffersMap.keySet().iterator();
      while (it.hasNext()) {
        Integer keyVal = (Integer)it.next();
        ArrayList bufferPool = (ArrayList)buffersMap.get(keyVal);
      
        bytesUsed += keyVal.intValue() * bufferPool.size();
      }
      
      //compact buffer pools if they use too much memory
      if (bytesUsed > MAX_FREE_BYTES) {
        compactFreeBuffers(bytesUsed);
      }
      
    }
	
	compactSlices();
  
private voidcompactFreeBuffers(long bytes_used)
Fairly removes free buffers from the pools to limit memory usage.

    final int numPools = buffersMap.size();
    long bytesToFree = 0;
    int maxPoolSize = 0;
    
    int[] buffSizes = new int[numPools];
    int[] poolSizes = new int[numPools];
    int[] numToFree = new int[numPools];

    
    //fill size arrays
    int pos = 0;
    Iterator it = buffersMap.keySet().iterator();
    while (it.hasNext()) {
      Integer keyVal = (Integer)it.next();
      ArrayList bufferPool = (ArrayList)buffersMap.get(keyVal);
      
      buffSizes[pos] = keyVal.intValue();
      poolSizes[pos] = bufferPool.size();
      numToFree[pos] = 0;
      
      //find initial max value
      if (poolSizes[pos] > maxPoolSize) maxPoolSize = poolSizes[pos];
      
      pos++;
    }
    
    //calculate the number of buffers to free from each pool
    while( bytesToFree < (bytes_used - MAX_FREE_BYTES) ) {
      for (int i=0; i < numPools; i++) {
        //if the pool size is as large as the current max size
        if (poolSizes[i] == maxPoolSize) {
          //update counts
          numToFree[i]++;
          poolSizes[i]--;
          bytesToFree += buffSizes[i];
        }
      }
      //reduce max size for next round
      maxPoolSize--;
    }
    
    //free buffers from the pools
    pos = 0;
    it = buffersMap.values().iterator();
    while (it.hasNext()) {
      //for each pool
      ArrayList bufferPool = (ArrayList)it.next();
      synchronized( bufferPool ) {
        int size = bufferPool.size();
        //remove the buffers from the end
        for (int i=(size - 1); i >= (size - numToFree[pos]); i--) {
          bufferPool.remove(i);
        }
      }
      
      pos++;
    }
    
    runGarbageCollection();
  
protected voidcompactSlices()

			// we don't maintain the buffers in sorted order as this is too costly. however, we
			// always allocate and free from the start of the free list, so unused buffer space
			// will be at the end of the list. we periodically sort this list into allocate block
			// order so that if an entire block isn't used for one compaction cycle all of
			// its elements will end up together, if you see what I mean :P
		
			// when we find an entire block is unused then we just drop them from the list to
			// permit them (and the underlying block) to be garbage collected
		
		for (int i=0;i<slice_entries.length;i++){
			
			int			entries_per_alloc 	= SLICE_ENTRY_ALLOC_SIZES[i];
	
			List	l = slice_entries[i];
	
				// no point in trying gc if not enough entries
			
			if ( l.size() >= entries_per_alloc ){
			
				synchronized( l ){
					
					Collections.sort( l,
						new Comparator()
						{
							public int
							compare(
								Object	o1,
								Object	o2 )
							{
								sliceBuffer	sb1 = (sliceBuffer)o1;
								sliceBuffer	sb2 = (sliceBuffer)o2;
								
								int	res = sb1.getAllocID() - sb2.getAllocID();
								
								if ( res == 0 ){
									
									res = sb1.getSliceID() - sb2.getSliceID();
								}
								
								return( res );
							}
						});
			
					boolean[]	allocs				= slice_allocs[i];
			
					Iterator	it = l.iterator();
					
					int	current_alloc 	= -1;
					int entry_count		= 0;
			
					boolean	freed_one	= false;
					
					while( it.hasNext()){
						
						sliceBuffer	sb = (sliceBuffer)it.next();
						
						int	aid = sb.getAllocID();
						
						if ( aid != current_alloc ){
							
							if ( entry_count == entries_per_alloc ){
								
								// System.out.println( "CompactSlices[" + SLICE_ENTRY_SIZES[i]+"] freeing " + aid );
								
								freed_one	= true;
								
								allocs[aid]	= false;					
							}
							
							current_alloc	= aid;
							
							entry_count		= 1;
							
						}else{
							
							entry_count++;
						}
					}
					
					if ( entry_count == entries_per_alloc ){
						
						// System.out.println( "CompactSlices[" + SLICE_ENTRY_SIZES[i]+"] freeing " + current_alloc );
						
						freed_one	= true;
						
						allocs[current_alloc]	= false;					
					}
					
					if ( freed_one ){
						
						it = l.iterator();
						
						while( it.hasNext()){
							
							sliceBuffer	sb = (sliceBuffer)it.next();
							
							if ( !allocs[ sb.getAllocID()]){
								
								it.remove();
							}
						}
					}
				}
			}
		}
	
protected voidfreeSliceBuffer(DirectByteBuffer ddb)

		
		if ( ddb instanceof sliceDBB ){
			
			int	slice_index = getSliceIndex( ddb.getBufferInternal().capacity());

			List		my_slice_entries 	= slice_entries[slice_index];

			synchronized( my_slice_entries ){
			
				my_slice_entries.add( 0, ((sliceDBB)ddb).getSliceBuffer());
			}
		}
	
public static DirectByteBuffergetBuffer(byte _allocator, int _length)
Retrieve a buffer from the buffer pool of size at least length, and no larger than DirectByteBufferPool.MAX_SIZE

    if (_length < 1) {
        Debug.out("requested length [" +_length+ "] < 1");
        return null;
    }

    if (_length > MAX_SIZE) {
        Debug.out("requested length [" +_length+ "] > MAX_SIZE [" +MAX_SIZE+ "]");
        return null;
    }

    return pool.getBufferHelper(_allocator,_length);
  
private DirectByteBuffergetBufferHelper(byte _allocator, int _length)
Retrieve an appropriate buffer from the free pool, or create a new one if the pool is empty.

		DirectByteBuffer	res;
		
		if ( _length <= SLICE_END_SIZE ){

			res = getSliceBuffer( _allocator, _length );
			
		}else{
				
			ByteBuffer	buff = null;
			
			Integer reqVal = new Integer(_length);
	    
				//loop through the buffer pools to find a buffer big enough
	    
			Iterator it = buffersMap.keySet().iterator();
	    
			while (it.hasNext()) {
	    	
				Integer keyVal = (Integer)it.next();
	
					//	check if the buffers in this pool are big enough
	      
				if (reqVal.compareTo(keyVal) <= 0) {
	      	
	   
					ArrayList bufferPool = (ArrayList)buffersMap.get(keyVal);
	            
					synchronized ( poolsLock ) { 
	        
						//	make sure we don't remove a buffer when running compaction
						//if there are no free buffers in the pool, create a new one.
						//otherwise use one from the pool
	        	
						if (bufferPool.isEmpty()) {
	          	
							buff = allocateNewBuffer(keyVal.intValue());
	            
						}else{
	          	
							synchronized ( bufferPool ) {
	            	
								buff = (ByteBuffer)bufferPool.remove(bufferPool.size() - 1);
							}
						}
					}
			
					break;
				}
			}
		
			if ( buff == null ){
						      
			    Debug.out("Unable to find an appropriate buffer pool");
			    
			    throw( new RuntimeException( "Unable to find an appropriate buffer pool" ));
			}
			
			res = new DirectByteBuffer( _allocator, buff, this );		   
		}
			
        	// clear doesn't actually zero the data, it just sets pos to 0 etc.
        
		ByteBuffer buff = res.getBufferInternal();
		
        buff.clear();   //scrub the buffer
        
		buff.limit( _length );
		
        bytesOut += buff.capacity();
                                
        if ( DEBUG_PRINT_MEM || DEBUG_TRACK_HANDEDOUT ){
        	
        	synchronized( handed_out ){
        	        	
				if ( DEBUG_HANDOUT_SIZES ){
					
					int	trim_size;
					
					if ( _length < 32 ){
						
						trim_size = 4;
					}else{
						
						trim_size = 16;
					}
					
					int	trim = ((_length+trim_size-1)/trim_size)*trim_size;
					
					Long count = (Long)size_counts.get(new Integer(trim));
					
					if ( count == null ){
						
						size_counts.put( new Integer( trim ), new Long(1));
						
					}else{
						
						size_counts.put( new Integer( trim), new Long( count.longValue() + 1 ));
					}
				}
				
        		if ( handed_out.put( buff, res ) != null ){
          		
        			Debug.out( "buffer handed out twice!!!!");
          		
        			throw( new RuntimeException( "Buffer handed out twice" ));
        		}
        	
				//System.out.println( "[" + handed_out.size() + "] -> " + buff + ", bytesIn = " + bytesIn + ", bytesOut = " + bytesOut );
          	}
        }
        
        // addInUse( dbb.capacity() );
        
        return( res );
    
protected DirectByteBuffergetSliceBuffer(byte _allocator, int _length)

		int	slice_index = getSliceIndex( _length );
		
		List		my_slice_entries 	= slice_entries[slice_index];

		synchronized( my_slice_entries ){
	
			boolean[]	my_allocs			= slice_allocs[slice_index];
			
			sliceBuffer	sb = null;
			
			if ( my_slice_entries.size() > 0 ){
				
				sb = (sliceBuffer)my_slice_entries.remove(0);
				
				slice_use_count[slice_index]++;
				
			}else{
				
					// find a free slot
				
				short	slot = -1;
				
				for (short i=0;i<my_allocs.length;i++){
					
					if( !my_allocs[i]){
						
						slot	= i;
			
						break;
					}
				}
				
				if ( slot != -1 ){
					
					short	slice_entry_size 	= SLICE_ENTRY_SIZES[slice_index];
					short	slice_entry_count	= SLICE_ENTRY_ALLOC_SIZES[slice_index];
					
					ByteBuffer	chunk = ByteBuffer.allocateDirect(  slice_entry_size*slice_entry_count  );
					
					my_allocs[slot] = true;
					
					for (short i=0;i<slice_entry_count;i++){
						
						chunk.limit((i+1)*slice_entry_size);
						chunk.position(i*slice_entry_size);
						
						ByteBuffer	slice = chunk.slice();
						
						sliceBuffer new_buffer = new sliceBuffer( slice, slot, i );
						
						if ( i == 0 ){
							
							sb = new_buffer;
							
							slice_use_count[slice_index]++;
							
						}else{
							
							my_slice_entries.add( new_buffer );
						}
					}
				}else{
					
					if ( !slice_alloc_fails[slice_index] ){
						
						slice_alloc_fails[slice_index]	= true;
						
						Debug.out( "Run out of slice space for '" + SLICE_ENTRY_SIZES[slice_index] + ", reverting to normal allocation" );
					}
					
					ByteBuffer buff = ByteBuffer.allocate( _length );
					
				    return( new DirectByteBuffer( _allocator, buff, this ));

				}
			}
			
			sliceDBB dbb = new sliceDBB( _allocator, sb );

			return( dbb );
		}
	
protected intgetSliceIndex(int _length)

		for (int i=0;i<SLICE_ENTRY_SIZES.length;i++){
			
			if ( _length <= SLICE_ENTRY_SIZES[i] ){
				
				return( i );
			}
		}
		
		Debug.out( "eh?");
		
		return( 0 );
	
private voidprintInUse(boolean verbose)

  		if ( DEBUG_PRINT_MEM ){
	  		CacheFileManager cm	= null;
	  		
			try{
	 			cm = CacheFileManagerFactory.getSingleton();
	 			
			}catch( Throwable e ){
					
				Debug.printStackTrace( e );
			}
	 
	  		synchronized( handed_out ){
	    	
	  			Iterator	it = handed_out.values().iterator();
	  			
	  			Map	cap_map		= new TreeMap();
	  			Map	alloc_map	= new TreeMap();
	  			
	  			while( it.hasNext()){
	  				
	  				DirectByteBuffer	db = (DirectByteBuffer)it.next();
	  				
	  				if ( verbose ){
		  				String	trace = db.getTraceString();
		  				
		  				if ( trace != null ){
		  					
		  					System.out.println( trace );
		  				}
	  				}
	  				
	  				Integer cap 	= new Integer( db.getBufferInternal().capacity());
	  				Byte	alloc 	= new Byte( db.getAllocator());
	  				
	  				myInteger	c = (myInteger)cap_map.get(cap);
	  				
	  				if ( c == null ){
	  					
	  					c	= new myInteger();
	  					
	  					cap_map.put( cap, c );
	  				}
	  				
	  				c.value++;
	  				
					myInteger	a = (myInteger)alloc_map.get(alloc);
	  				
	  				if ( a == null ){
	  					
	  					a	= new myInteger();
	  					
	  					alloc_map.put( alloc, a );
	  				}
	  				
	  				a.value++;				
	  			}
	  			
	  			it = cap_map.keySet().iterator();
	  			
	  			while( it.hasNext()){
	  				
	  				Integer		key 	= (Integer)it.next();
	  				myInteger	count 	= (myInteger)cap_map.get( key );
	  				
	  		        if( key.intValue() < 1024 ){
	  		        	
	  		        	System.out.print("[" +key.intValue()+ " x " +count.value+ "] ");
	  		        	
	  		        }else{  
	  		        	
	  		        	System.out.print("[" +key.intValue()/1024+ "K x " +count.value+ "] ");
	  		        }
	  			}
	  			
	  			System.out.println();
	  			
				it = alloc_map.keySet().iterator();
	  			
	  			while( it.hasNext()){
	  				
	  				Byte		key 	= (Byte)it.next();
	  				myInteger	count 	= (myInteger)alloc_map.get( key );
	  				
	  	        	System.out.print("[" + DirectByteBuffer.AL_DESCS[key.intValue()]+ " x " +count.value+ "] ");
	  			}
	  			
	  			if ( cm != null ){
	  				
	  				CacheFileManagerStats stats = cm.getStats();
	  				
	  				System.out.print( " - Cache: " );
	  	  			
	  				
					System.out.print( "sz=" + stats.getSize());
					System.out.print( ",us=" + stats.getUsedSize());
					System.out.print( ",cw=" + stats.getBytesWrittenToCache());
					System.out.print( ",cr=" + stats.getBytesReadFromCache());
					System.out.print( ",fw=" + stats.getBytesWrittenToFile());
					System.out.print( ",fr=" + stats.getBytesReadFromFile());
					
	  			}
	  			
	  			System.out.println();
				
				if ( DEBUG_HANDOUT_SIZES ){
					it = size_counts.entrySet().iterator();
					
					String	str = "";
						
					while( it.hasNext()){
						
						Map.Entry	entry = (Map.Entry)it.next();
						
						str += (str.length()==0?"":",") + entry.getKey() + "=" + entry.getValue();
					}
					
					System.out.println( str );
				}
				
				String str = "";
				
				for (int i=0;i<slice_entries.length;i++){
				
					boolean[]	allocs = slice_allocs[i];
					int	alloc_count = 0;
					for (int j=0;j<allocs.length;j++){
						if( allocs[j]){
							alloc_count++;
						}
					}
					str += (i==0?"":",") + "["+SLICE_ENTRY_SIZES[i]+"]f=" +slice_entries[i].size()+",a=" + (alloc_count*SLICE_ENTRY_ALLOC_SIZES[i]) + ",u=" +slice_use_count[i];
				}
				
				System.out.println( "slices: " + str );

	  		}
  		}
  	
protected voidreturnBuffer(DirectByteBuffer ddb)
Return the given buffer to the appropriate pool.

		ByteBuffer	buff = ddb.getBufferInternal();
		
		int	capacity = buff.capacity();

		bytesIn += capacity;
		    
	  	if ( DEBUG_TRACK_HANDEDOUT ){
	  		
	  		synchronized( handed_out ){

	  			if ( handed_out.remove( buff ) == null ){
	  				
	  				Debug.out( "buffer not handed out" );
	  				
	  				throw( new RuntimeException( "Buffer not handed out" ));
	  			}
	  			
	       		// System.out.println( "[" + handed_out.size() + "] <- " + buffer + ", bytesIn = " + bytesIn + ", bytesOut = " + bytesOut );
	  		}
	  	}
	  	
	    // remInUse( buffer.capacity() );
		
		if ( capacity <= SLICE_END_SIZE ){
			
			freeSliceBuffer( ddb );
			
		}else{
		    Integer buffSize = new Integer(capacity);
		    
		    ArrayList bufferPool = (ArrayList)buffersMap.get(buffSize);
		    
		    if (bufferPool != null) {
				
				//no need to sync around 'poolsLock', as adding during compaction is ok
				
		      synchronized ( bufferPool ){
				  
		        bufferPool.add(buff);
		      }
		    }else{
				
		      Debug.out("Invalid buffer given; could not find proper buffer pool");
		    }
		}
	
private voidrunGarbageCollection()
Force system garbage collection.

    if( DEBUG_PRINT_MEM ) {
      System.out.println( "runGarbageCollection()" );
    }
    System.runFinalization();
    System.gc();