package org.jacorb.orb; /* * JacORB - a free Java ORB * * Copyright (C) 1997-2014 Gerald Brose / The JacORB Team. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.jacorb.config.Configurable; import org.jacorb.config.Configuration; import org.jacorb.config.ConfigurationException; import org.jacorb.orb.buffermanager.BufferManagerExpansionPolicy; import org.jacorb.orb.buffermanager.DefaultExpansionPolicy; import org.omg.CORBA.INTERNAL; import org.omg.CORBA.NO_MEMORY; import org.slf4j.Logger; /** * A BufferManager is used to share a pool of buffers and to implement * a buffer allocation policy. This reduces the number of memory * allocations and deallocations and the overall memory footprint. * Buffers are generally created on demand. * * @author Gerald Brose */ public class BufferManager extends AbstractBufferManager { /** * Smallest size of arrays that will be cached for reuse. This equates to * 1023 (calculated by (1 << ( 9 + 1 ) ) - 1 = 1023 ) */ private static final int MIN_CACHE = 9; /** * Precompute Log2 */ private static final double LOG2 = Math.log (2.0); /** * The buffer pool */ protected final Collection[] bufferPool; /** * The 'extra-large' buffer cache. */ private byte[] bufferMax = null; /** * The maximal buffer size managed since the buffer pool is ordered by buffer * size in log2 steps. * * The default size of 22 means that it will manage 2^(22) = 4Mb. */ private final int maxManagedBufferSize; /** * Max number of buffers of the same size held in pool. * * Default value is 20. */ private final int threshold; /** * Purge thread for QoS purging of the bufferMax cache. */ private Reaper reaper; /** * <code>time</code> denotes whether the maxCache will be active: * -1: Not active * 0 : Active, never flushed * >0: Active with reaper flush thread. */ private final int time; /** * <code>expansionPolicy</code> defines how buffer sizes * will be calculated */ private BufferManagerExpansionPolicy expansionPolicy; private final Logger logger; /** * used to create the singleton ORB buffermanager * @param configuration */ public BufferManager(Configuration configuration) { logger = configuration.getLogger("org.jacorb.orb.buffermanager"); try { this.time = configuration.getAttributeAsInteger("jacorb.bufferManagerMaxFlush", 0); this.maxManagedBufferSize = configuration.getAttributeAsInteger("jacorb.maxManagedBufSize", 22); this.threshold = configuration.getAttributeAsInteger("jacorb.bufferManagerThreshold", 20); } catch (ConfigurationException ex) { logger.error ("Error configuring the BufferManager", ex); throw new INTERNAL ("Unable to configure the BufferManager"); } try { expansionPolicy = (BufferManagerExpansionPolicy) configuration.getAttributeAsObject ("jacorb.buffermanager.expansionpolicy", DefaultExpansionPolicy.class.getName ()); if (expansionPolicy instanceof Configurable) { ((Configurable)expansionPolicy).configure (configuration); } } catch (ConfigurationException e) { this.expansionPolicy = null; } bufferPool = initBufferPool(configuration, maxManagedBufferSize); // Partly prefill the cache with some buffers. int sizes [] = new int [] {1023, 2047}; for (int i = 0; i < sizes.length; i++) { for( int min = 0; min < 10; min++ ) { int position = calcLog(sizes[i]) - MIN_CACHE ; storeBuffer(position, new byte[sizes[i]]); } } if ( time > 0) { if (reaper != null) { // this is the case when // the BufferManager is re-configured reaper.dispose(); } // create new reaper reaper = new Reaper(time); reaper.setName ("BufferManager MaxCache Reaper"); reaper.setDaemon (true); reaper.start(); } } protected void storeBuffer(final int position, final byte[] buffer) { bufferPool[ position ].add(buffer); } protected Collection[] initBufferPool(Configuration configuration, int maxSize) { final List[] bufferPool = new List[maxSize]; for( int i = 0; i < bufferPool.length; i++) { bufferPool[ i ] = (List) new ArrayList (threshold); } return bufferPool; } /** * Calculate log2 of given value. * * Incorporates shortcut for known memory buffer size and non-cached values. * * @param value * @return */ private static final int calcLog (int value) { // Shortcut for uncached_data_length if (value <= 1023 ) { return MIN_CACHE; } else { return (int)(Math.floor (Math.log (value) / LOG2)); } } public byte[] getExpandedBuffer( int size ) { if (size < 0) { throw new INTERNAL ("Unable to cache and create buffer of negative size. Possible overflow issue."); } // Use the expansion policy if available if (expansionPolicy != null) { size = expansionPolicy.getExpandedSize (size); } return getBuffer (size); } /** * <code>getBuffer</code> returns a new buffer. * * @param size an <code>int</code> value * @return a <code>byte[]</code> value */ public byte[] getBuffer( int size ) { byte [] result = null; if (size < 0) { throw new INTERNAL ("Unable to cache and create buffer of negative size. Possible overflow issue."); } final int log = calcLog(size); if (log > maxManagedBufferSize) { try { if (time < 0) { // Defaults to returning asked for size result = new byte[size]; } else { synchronized(this) { // Using cache so do below determination if (bufferMax == null || bufferMax.length < size) { // Autocache really large values for speed bufferMax = new byte[size]; } // Else return the cached buffer result = bufferMax; bufferMax = null; } } } catch (OutOfMemoryError e) { throw new NO_MEMORY(e.toString()); } } else { int index = (log > MIN_CACHE ? log - MIN_CACHE : 0); final Collection s = bufferPool[index]; result = doFetchBuffer(s); if (result == null) { // .. = 1 << MIN_CACHE + 1 // 64 = 1 << 5 + 1 // 128 = 1 << 6 + 1 // 255 = 1 << 7 + 1 // 512 = 1 << 8 + 1 // 1024 = 1 << 9 + 1 // 2048 = 1 << 10 + 1 result = new byte[ ( log > MIN_CACHE ? 1 << log + 1 : 1024 ) - 1]; } } return result; } protected byte[] doFetchBuffer(Collection list) { synchronized(list) { final int size = list.size(); if (size > 0) { // pop least recently added buffer from the list return (byte[])((AbstractList)list).remove(size-1); } } return null; } /** * Describe <code>returnBuffer</code> method here. * * @param current a <code>byte[]</code> value * @param cdrStr a <code>boolean</code> value value to denote if CDROuputStream is * caller (may use cache in this situation) */ public void returnBuffer(byte[] current, boolean cdrStr) { if (current != null) { int log_curr = calcLog(current.length); if( log_curr >= MIN_CACHE) { if( log_curr > maxManagedBufferSize ) { synchronized(this) { // Only cache if CDROutputStream is called, cache is enabled & // the new value is > than the cached value. if (cdrStr && (time >= 0 && (bufferMax == null || bufferMax.length < current.length))) { bufferMax = current; } return; } } int expected = (1 << log_curr + 1) - 1; if (current.length != expected) { if (logger.isWarnEnabled()) { logger.warn ("BufferManager.returnBuffer, log = " + log_curr + " got length " + current.length + " expected length " + expected); } } else { final Collection s = bufferPool[ log_curr-MIN_CACHE ]; doReturnBuffer(s, current, threshold); } } } } protected void doReturnBuffer(Collection list, byte[] buffer, final int threshold) { synchronized(list) { if( list.size() < threshold ) { list.add( buffer ); } } } public void release() { for( int i= 0; i < bufferPool.length; ++i) { bufferPool[i].clear(); } if (reaper != null) { reaper.dispose(); reaper = null; } } private final class Reaper extends Thread { private boolean done = false; private int sleepInterval = 0; public Reaper (int sleepInterval) { super("BufferManagerReaper"); // Convert from seconds to milliseconds this.sleepInterval = (sleepInterval * 1000); } public void run() { long time; while (true) { // Sleep (note time check on wake to catch premature awakening bug) time = sleepInterval + System.currentTimeMillis(); synchronized(this) { while(!done && System.currentTimeMillis() <= time) { try { wait(sleepInterval); } catch (InterruptedException ex) { // ignored } } } // Check not shutting down synchronized(this) { if(done) { break; } } synchronized(BufferManager.this) { bufferMax = null; } } } public synchronized void dispose() { done = true; interrupt(); // Only one thread waiting so safe to use notify rather than notifyAll. notify(); } } }