Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  package org.jruby.javasupport.util;
  
  
  
Maps Java objects to their proxies. Combines elements of WeakHashMap and ConcurrentHashMap to permit unsynchronized reads. May be configured to use either Weak (the default) or Soft references.

Note that both Java objects and their proxies are held by weak/soft references; because proxies (currently) keep strong references to their Java objects, if we kept strong references to them the Java objects would never be gc'ed. This presents a problem in the case where a user passes a Rubified Java object out to Java but keeps no reference in Ruby to the proxy; if the object is returned to Ruby after its proxy has been gc'ed, a new (and possibly very wrong, in the case of JRuby-defined subclasses) proxy will be created. Use of soft references may help reduce the likelihood of this occurring; users may be advised to keep Ruby-side references to prevent it occurring altogether.

Author(s):
Bill Dortch
 
 public abstract class ObjectProxyCache<T,A> {
 
     private static final Logger LOG = LoggerFactory.getLogger("ObjectProxyCache");
     
     public static enum ReferenceType { WEAK, SOFT }
     
     private static final int DEFAULT_SEGMENTS = 16; // must be power of 2
     private static final int DEFAULT_SEGMENT_SIZE = 8; // must be power of 2
     private static final float DEFAULT_LOAD_FACTOR = 0.75f;
     private static final int MAX_CAPACITY = 1 << 30;
     private static final int MAX_SEGMENTS = 1 << 16;
     private static final int VULTURE_RUN_FREQ_SECONDS = 5;
     
     private static int _nextId = 0;
     
     private static synchronized int nextId() {
         return ++;
     }
 
     
     private final ReferenceType referenceType;
     private final Segment<T,A>[] segments;
     private final int segmentShift;
     private final int segmentMask;
     private Thread vulture;
     private final int id;
     
     public ObjectProxyCache() {
     }
     
     public ObjectProxyCache(ReferenceType refType) {
         this(refType);
     }
     
     
     public ObjectProxyCache(int numSegmentsint initialSegCapacityReferenceType refType) {
         if (numSegments <= 0 || initialSegCapacity <= 0 || refType == null) {
             throw new IllegalArgumentException();
         }
         this. = nextId();
         this. = refType;
         if (numSegments > numSegments = ;
     
         // Find power-of-two sizes best matching arguments
         int sshift = 0;
         int ssize = 1;
         while (ssize < numSegments) {
             ++sshift;
             ssize <<= 1;
         }
         // note segmentShift differs from ConcurrentHashMap's calculation due to
         // issues with System.identityHashCode (upper n bits always 0, at least 
         // under Java 1.6 / WinXP)
         this. = 24 - sshift;
         this. = ssize - 1;
         this. = Segment.newArray(ssize);
     
         if (initialSegCapacity > ) {
             initialSegCapacity = ;
         }
         int cap = 1;
         while (cap < initialSegCapacitycap <<= 1;
     
         for (int i = ssize; --i >= 0; ) {
             [i] = new Segment<T,A>(capthis);
         }
         // vulture thread will periodically expunge dead
        // entries.  entries are also expunged during 'put'
        // operations; this is designed to cover the case where
        // many objects are created initially, followed by limited
        // put activity.
        //
        // FIXME: DISABLED (below) pending resolution of finalization issue
        //
        try {
            this. = new Thread("ObjectProxyCache "++" vulture") {
                    public void run() {
                        for ( ;; ) {
                            try {
                                sleep( * 1000);
                            } catch (InterruptedException e) {}
                            boolean dump = size() > 200;
                            if (dump) {
                                .debug("***Vulture {} waking, stats:");
                                .debug(stats());
                            }
                            for (int i = .; --i >= 0; ) {
                                Segment<T,A> seg = [i];
                                seg.lock();
                                try {
                                    seg.expunge();
                                } finally {
                                    seg.unlock();
                                }
                                yield();
                            }
                            if (dump) {
                                .debug("***Vulture {} sleeping, stats:");
                                .debug(stats());
                            }
                        }
                    }
                };
            .setDaemon(true);
        } catch (SecurityException e) {
            this. = null;
        }
        
        // FIXME: vulture daemon thread prevents finalization,
        // find alternative approach.
        // vulture.start();
//      System.err.println("***ObjectProxyCache " + id + " started at "+ new java.util.Date());
    }
    
//    protected void finalize() throws Throwable {
//        System.err.println("***ObjectProxyCache " + id + " finalized at "+ new java.util.Date());
//    }
    
    public abstract T allocateProxy(Object javaObject, A allocator);
    
    public T get(Object javaObject) {
        if (javaObject == nullreturn null;
        int hash = hash(javaObject);
        return segmentFor(hash).get(javaObjecthash);
    }
    
    public T getOrCreate(Object javaObject, A allocator) {
        if (javaObject == null || allocator == nullreturn null;
        int hash = hash(javaObject);
        return segmentFor(hash).getOrCreate(javaObjecthashallocator);
    }
    
    public void put(Object javaObject, T proxy) {
        if (javaObject == null || proxy == nullreturn;
        int hash = hash(javaObject);
        segmentFor(hash).put(javaObjecthashproxy);
    }
    
    private static int hash(Object javaObject) {
        int h = System.identityHashCode(javaObject);
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    private Segment<T,A> segmentFor(int hash) {
        return [(hash >>> ) & ];
    }
    
    
Returns the approximate size (elements in use) of the cache. The sizes of the segments are summed. No effort is made to synchronize across segments, so the value returned may differ from the actual size at any point in time.

Returns:
    public int size() {
       int size = 0;
       for (Segment<T,A> seg : ) {
           size += seg.tableSize;
       }
       return size;
    }
    
    public String stats() {
        StringBuilder b = new StringBuilder();
        int n = 0;
        int size = 0;
        int alloc = 0;
        b.append("Segments: ").append(.).append("\n");
        for (Segment<T,A> seg : ) {
            int ssize = 0;
            int salloc = 0;
            seg.lock();
            try {
                ssize = seg.count();
                salloc = seg.entryTable.length;
            } finally {
                seg.unlock();
            }
            size += ssize;
            alloc += salloc;
            b.append("seg[").append(n++).append("]:  size: ").append(ssize)
                .append("  alloc: ").append(salloc).append("\n");
        }
        b.append("Total: size: ").append(size)
            .append("  alloc: ").append(alloc).append("\n");
        return b.toString();
    }
    
    // EntryRefs include hash with key to facilitate lookup by Segment#expunge
    // after ref is removed from ReferenceQueue
    private static interface EntryRef<T> {
        T get();
        int hash();
    }
    private static final class WeakEntryRef<T> extends WeakReference<T> implements EntryRef<T> {
        final int hash;
        WeakEntryRef(int hash, T rawObjectReferenceQueue<Objectqueue) {
            super(rawObjectqueue);
            this. = hash;
        }
        public int hash() {
            return ;
        }
    }
    private static final class SoftEntryRef<T> extends SoftReference<T> implements EntryRef<T> {
        final int hash;
        SoftEntryRef(int hash, T rawObjectReferenceQueue<Objectqueue) {
            super(rawObjectqueue);
            this. = hash;
        }
        public int hash() {
            return ;
        }
    }
    // Unlike WeakHashMap, our Entry does not subclass WeakReference, but rather
    // makes it a final field.  The theory is that doing so should force a happens-before
    // relationship WRT the WeakReference constructor, guaranteeing that the key will be
    // visibile to other threads (unless it's been GC'ed).  See JLS 17.5 (final fields) and
    // 17.4.5 (Happens-before order) to confirm or refute my reasoning here.
    static class Entry<T> {
        final EntryRef<ObjectobjectRef;
        final int hash;
        final EntryRef<T> proxyRef;
        final Entry<T> next;
        
        Entry(Object objectint hash, T proxyReferenceType typeEntry<T> nextReferenceQueue<Objectqueue) {
            this. = hash;
            this. = next;
            // references to the Java object and its proxy will either both be
            // weak or both be soft, since the proxy contains a strong reference
            // to the object, so it wouldn't make sense for the reference types
            // to differ.
            if (type == .) {
                this. = new WeakEntryRef<Object>(hashobjectqueue);
                this. = new WeakEntryRef<T>(hashproxyqueue);
            } else {
                this. = new SoftEntryRef<Object>(hashobjectqueue);
                this. = new SoftEntryRef<T>(hashproxyqueue);
            }
        }
        
        // ctor used by remove/rehash
        Entry(EntryRef<ObjectobjectRefint hashEntryRef<T> proxyRefEntry<T> next) {
            this. = objectRef;
            this. = hash;
            this. = proxyRef;
            this. = next;
        }
        
        @SuppressWarnings("unchecked")
        static final <T> Entry<T>[] newArray(int size) {
            return new Entry[size];
        }
     }
    
    // lame generics issues: making Segment class static and manually
    // inserting cache reference to work around various problems generically
    // referencing methods/vars across classes.
    static class Segment<T,A> extends ReentrantLock {
        final ObjectProxyCache<T,A> cache;
        final ReferenceQueue<ObjectreferenceQueue = new ReferenceQueue<Object>();
        volatile Entry<T>[] entryTable;
        int tableSize;
        int threshold;
        Segment(int capacityObjectProxyCache<T,A> cache) {
             = (int)(capacity * );
             = Entry.newArray(capacity);
            this. = cache;
        }
        
        // must be called under lock
        private void expunge() {
            Entry<T>[] table = ;
            ReferenceQueue<Objectqueue = ;
            EntryRef ref;
            // note that we'll potentially see the refs for both the java object and
            // proxy -- whichever we see first will cause the entry to be removed;
            // the other will not match an entry and will be ignored.
            while ((ref = (EntryRef)queue.poll()) != null) {
                int hash;
                for (Entry<T> e = table[(hash = ref.hash()) & (table.length - 1)]; e != nulle = e.next) {
                    if (hash == e.hash && (ref == e.objectRef || ref == e.proxyRef)) {
                        remove(tablehashe);
                        break;
                    }
                }
            }
        }
        
        // must be called under lock
        private void remove(Entry<T>[] tableint hashEntry<T> e) {
            int index = hash & (table.length - 1);
            Entry<T> first = table[index];
            for (Entry<T> n = firstn != nulln = n.next) {
                if (n == e) {
                    Entry<T> newFirst = n.next;
                    for (Entry<T> p = firstp != np = p.next) {
                        newFirst = new Entry<T>(p.objectRefp.hashp.proxyRefnewFirst);
                    }
                    table[index] = newFirst;
                    --;
                     = table// write-volatile
                    return;
                }
            }
        }
        // temp method to verify tableSize value
        // must be called under lock
        private int count() {
            int count = 0;
            for (Entry<T> e : ) {
                while (e != null) {
                    count++;
                    e = e.next;
                }
            }
            return count;
        }
        // must be called under lock
        private Entry<T>[] rehash() {
            assert  == count() : "tableSize "++" != count() "+count();
            Entry<T>[] oldTable = // read-volatile
            int oldCapacity;
            if ((oldCapacity = oldTable.length) >= ) {
                return oldTable;
            }
            int newCapacity = oldCapacity << 1;
            int sizeMask = newCapacity - 1;
             = (int)(newCapacity * );
            Entry<T>[] newTable = Entry.newArray(newCapacity);
            Entry<T> e;
            for (int i = oldCapacity; --i >= 0; ) {
                if ((e = oldTable[i]) != null) {
                    int idx = e.hash & sizeMask;
                    Entry<T> next;
                    if ((next = e.next) == null) {
                        // Single node in list
                        newTable[idx] = e;
                    } else {
                        // Reuse trailing consecutive sequence at same slot
                        int lastIdx = idx;
                        Entry<T> lastRun = e;
                        for (Entry<T> last = nextlast != nulllast = last.next) {
                            int k;
                            if ((k = last.hash & sizeMask) != lastIdx) {
                                lastIdx = k;
                                lastRun = last;
                            }
                        }
                        newTable[lastIdx] = lastRun;
                        // Clone all remaining nodes
                        for (Entry<T> p = ep != lastRunp = p.next) {
                            int k = p.hash & sizeMask;
                            Entry<T> m = new Entry<T>(p.objectRefp.hashp.proxyRefnewTable[k]);
                            newTable[k] = m;
                        }
                    }
                }
            }
             = newTable// write-volatile
            return newTable;
        }
        void put(Object objectint hash, T proxy) {
            lock();
            try {
                expunge();
                Entry<T>[] table;
                int potentialNewSize;
                if ((potentialNewSize =  + 1) > ) {
                    table = rehash(); // indirect read-/write- volatile
                } else {
                    table = // read-volatile
                }
                int index;
                Entry<T> e;
                for (e = table[index = hash & (table.length - 1)]; e != nulle = e.next) {
                    if (hash == e.hash && object == e.objectRef.get()) {
                        if (proxy == e.proxyRef.get()) return;
                        // entry exists, proxy doesn't match. replace.
                        // this could happen if old proxy was gc'ed
                        // TODO: raise exception if stored proxy is non-null? (not gc'ed)
                        remove(tablehashe);
                        potentialNewSize--;
                        break;
                    }
                }
                e = new Entry<T>(objecthashproxy.table[index], );
                table[index] = e;
                 = potentialNewSize;
                 = table// write-volatile
            } finally {
                unlock();
            }
        }
        T getOrCreate(Object objectint hash, A allocator) {
            Entry<T>[] table;
            T proxy;
            for (Entry<T> e = (table = )[hash & table.length - 1]; e != nulle = e.next) {
                if (hash == e.hash && object == e.objectRef.get()) {
                    if ((proxy = e.proxyRef.get()) != nullreturn proxy;
                    break;
                }
            }
            lock();
            try {
                expunge();
                int potentialNewSize;
                if ((potentialNewSize =  + 1) > ) {
                    table = rehash(); // indirect read-/write- volatile
                } else {
                    table = // read-volatile
                }
                int index;
                Entry<T> e;
                for (e = table[index = hash & (table.length - 1)]; e != nulle = e.next) {
                    if (hash == e.hash && object == e.objectRef.get()) {
                        if ((proxy = e.proxyRef.get()) != nullreturn proxy;
                        // entry exists, proxy has been gc'ed. replace entry.
                        remove(tablehashe);
                        potentialNewSize--;
                        break;
                    }
                }
                proxy = .allocateProxy(objectallocator);
                e = new Entry<T>(objecthashproxy.table[index], );
                table[index] = e;
                 = potentialNewSize;
                 = table// write-volatile
                return proxy;
            } finally {
                unlock();
            }
        }
        
        T get(Object objectint hash) {
            Entry<T>[] table;
            for (Entry<T> e = (table = )[hash & table.length - 1]; e != nulle = e.next) {
                if (hash == e.hash && object == e.objectRef.get()) {
                    return e.proxyRef.get();
                }
            }
            return null;
        }
        @SuppressWarnings("unchecked")
        static final <T,A> Segment<T,A>[] newArray(int size) {
            return new Segment[size];
        }
    }
New to GrepCode? Check out our FAQ X