org.jruby.javasupport.util.ObjectProxyCache Maven / Gradle / Ivy
package org.jruby.javasupport.util;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.concurrent.locks.ReentrantLock;
/**
* 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 Bill Dortch
*
*/
public abstract class ObjectProxyCache {
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 ++_nextId;
}
private final ReferenceType referenceType;
private final Segment[] segments;
private final int segmentShift;
private final int segmentMask;
private Thread vulture;
private final int id;
public ObjectProxyCache() {
this(DEFAULT_SEGMENTS, DEFAULT_SEGMENT_SIZE, ReferenceType.WEAK);
}
public ObjectProxyCache(ReferenceType refType) {
this(DEFAULT_SEGMENTS, DEFAULT_SEGMENT_SIZE, refType);
}
public ObjectProxyCache(int numSegments, int initialSegCapacity, ReferenceType refType) {
if (numSegments <= 0 || initialSegCapacity <= 0 || refType == null) {
throw new IllegalArgumentException();
}
this.id = nextId();
this.referenceType = refType;
if (numSegments > MAX_SEGMENTS) numSegments = MAX_SEGMENTS;
// 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.segmentShift = 24 - sshift;
this.segmentMask = ssize - 1;
this.segments = Segment.newArray(ssize);
if (initialSegCapacity > MAX_CAPACITY) {
initialSegCapacity = MAX_CAPACITY;
}
int cap = 1;
while (cap < initialSegCapacity) cap <<= 1;
for (int i = ssize; --i >= 0; ) {
segments[i] = new Segment(cap, this);
}
// 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.vulture = new Thread("ObjectProxyCache "+id+" vulture") {
public void run() {
for ( ;; ) {
try {
sleep(VULTURE_RUN_FREQ_SECONDS * 1000);
} catch (InterruptedException e) {}
boolean dump = size() > 200;
if (dump) {
LOG.debug("***Vulture {} waking, stats:", id);
LOG.debug(stats());
}
for (int i = segments.length; --i >= 0; ) {
Segment seg = segments[i];
seg.lock();
try {
seg.expunge();
} finally {
seg.unlock();
}
yield();
}
if (dump) {
LOG.debug("***Vulture {} sleeping, stats:", id);
LOG.debug(stats());
}
}
}
};
vulture.setDaemon(true);
} catch (SecurityException e) {
this.vulture = 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 == null) return null;
int hash = hash(javaObject);
return segmentFor(hash).get(javaObject, hash);
}
public T getOrCreate(Object javaObject, A allocator) {
if (javaObject == null || allocator == null) return null;
int hash = hash(javaObject);
return segmentFor(hash).getOrCreate(javaObject, hash, allocator);
}
public void put(Object javaObject, T proxy) {
if (javaObject == null || proxy == null) return;
int hash = hash(javaObject);
segmentFor(hash).put(javaObject, hash, proxy);
}
private static int hash(Object javaObject) {
int h = System.identityHashCode(javaObject);
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
private Segment segmentFor(int hash) {
return segments[(hash >>> segmentShift) & segmentMask];
}
/**
* 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.
*
* @return
*/
public int size() {
int size = 0;
for (Segment seg : segments) {
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(segments.length).append("\n");
for (Segment seg : segments) {
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 get();
int hash();
}
private static final class WeakEntryRef extends WeakReference implements EntryRef {
final int hash;
WeakEntryRef(int hash, T rawObject, ReferenceQueue