PageRenderTime 125ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/projects/jruby-1.7.3/src/org/jruby/javasupport/util/ObjectProxyCache.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 495 lines | 370 code | 49 blank | 76 comment | 95 complexity | 0614231101e1d5be68c0788c6fef7135 MD5 | raw file
  1. package org.jruby.javasupport.util;
  2. import org.jruby.util.log.Logger;
  3. import org.jruby.util.log.LoggerFactory;
  4. import java.lang.ref.ReferenceQueue;
  5. import java.lang.ref.SoftReference;
  6. import java.lang.ref.WeakReference;
  7. import java.util.concurrent.locks.ReentrantLock;
  8. /**
  9. * Maps Java objects to their proxies. Combines elements of WeakHashMap and
  10. * ConcurrentHashMap to permit unsynchronized reads. May be configured to
  11. * use either Weak (the default) or Soft references.<p>
  12. *
  13. * Note that both Java objects and their proxies are held by weak/soft
  14. * references; because proxies (currently) keep strong references to their
  15. * Java objects, if we kept strong references to them the Java objects would
  16. * never be gc'ed. This presents a problem in the case where a user passes
  17. * a Rubified Java object out to Java but keeps no reference in Ruby to the
  18. * proxy; if the object is returned to Ruby after its proxy has been gc'ed,
  19. * a new (and possibly very wrong, in the case of JRuby-defined subclasses)
  20. * proxy will be created. Use of soft references may help reduce the
  21. * likelihood of this occurring; users may be advised to keep Ruby-side
  22. * references to prevent it occurring altogether.
  23. *
  24. * @author <a href="mailto:bill.dortch@gmail.com">Bill Dortch</a>
  25. *
  26. */
  27. public abstract class ObjectProxyCache<T,A> {
  28. private static final Logger LOG = LoggerFactory.getLogger("ObjectProxyCache");
  29. public static enum ReferenceType { WEAK, SOFT }
  30. private static final int DEFAULT_SEGMENTS = 16; // must be power of 2
  31. private static final int DEFAULT_SEGMENT_SIZE = 8; // must be power of 2
  32. private static final float DEFAULT_LOAD_FACTOR = 0.75f;
  33. private static final int MAX_CAPACITY = 1 << 30;
  34. private static final int MAX_SEGMENTS = 1 << 16;
  35. private static final int VULTURE_RUN_FREQ_SECONDS = 5;
  36. private static int _nextId = 0;
  37. private static synchronized int nextId() {
  38. return ++_nextId;
  39. }
  40. private final ReferenceType referenceType;
  41. private final Segment<T,A>[] segments;
  42. private final int segmentShift;
  43. private final int segmentMask;
  44. private Thread vulture;
  45. private final int id;
  46. public ObjectProxyCache() {
  47. this(DEFAULT_SEGMENTS, DEFAULT_SEGMENT_SIZE, ReferenceType.WEAK);
  48. }
  49. public ObjectProxyCache(ReferenceType refType) {
  50. this(DEFAULT_SEGMENTS, DEFAULT_SEGMENT_SIZE, refType);
  51. }
  52. public ObjectProxyCache(int numSegments, int initialSegCapacity, ReferenceType refType) {
  53. if (numSegments <= 0 || initialSegCapacity <= 0 || refType == null) {
  54. throw new IllegalArgumentException();
  55. }
  56. this.id = nextId();
  57. this.referenceType = refType;
  58. if (numSegments > MAX_SEGMENTS) numSegments = MAX_SEGMENTS;
  59. // Find power-of-two sizes best matching arguments
  60. int sshift = 0;
  61. int ssize = 1;
  62. while (ssize < numSegments) {
  63. ++sshift;
  64. ssize <<= 1;
  65. }
  66. // note segmentShift differs from ConcurrentHashMap's calculation due to
  67. // issues with System.identityHashCode (upper n bits always 0, at least
  68. // under Java 1.6 / WinXP)
  69. this.segmentShift = 24 - sshift;
  70. this.segmentMask = ssize - 1;
  71. this.segments = Segment.newArray(ssize);
  72. if (initialSegCapacity > MAX_CAPACITY) {
  73. initialSegCapacity = MAX_CAPACITY;
  74. }
  75. int cap = 1;
  76. while (cap < initialSegCapacity) cap <<= 1;
  77. for (int i = ssize; --i >= 0; ) {
  78. segments[i] = new Segment<T,A>(cap, this);
  79. }
  80. // vulture thread will periodically expunge dead
  81. // entries. entries are also expunged during 'put'
  82. // operations; this is designed to cover the case where
  83. // many objects are created initially, followed by limited
  84. // put activity.
  85. //
  86. // FIXME: DISABLED (below) pending resolution of finalization issue
  87. //
  88. try {
  89. this.vulture = new Thread("ObjectProxyCache "+id+" vulture") {
  90. public void run() {
  91. for ( ;; ) {
  92. try {
  93. sleep(VULTURE_RUN_FREQ_SECONDS * 1000);
  94. } catch (InterruptedException e) {}
  95. boolean dump = size() > 200;
  96. if (dump) {
  97. LOG.debug("***Vulture {} waking, stats:", id);
  98. LOG.debug(stats());
  99. }
  100. for (int i = segments.length; --i >= 0; ) {
  101. Segment<T,A> seg = segments[i];
  102. seg.lock();
  103. try {
  104. seg.expunge();
  105. } finally {
  106. seg.unlock();
  107. }
  108. yield();
  109. }
  110. if (dump) {
  111. LOG.debug("***Vulture {} sleeping, stats:", id);
  112. LOG.debug(stats());
  113. }
  114. }
  115. }
  116. };
  117. vulture.setDaemon(true);
  118. } catch (SecurityException e) {
  119. this.vulture = null;
  120. }
  121. // FIXME: vulture daemon thread prevents finalization,
  122. // find alternative approach.
  123. // vulture.start();
  124. // System.err.println("***ObjectProxyCache " + id + " started at "+ new java.util.Date());
  125. }
  126. // protected void finalize() throws Throwable {
  127. // System.err.println("***ObjectProxyCache " + id + " finalized at "+ new java.util.Date());
  128. // }
  129. public abstract T allocateProxy(Object javaObject, A allocator);
  130. public T get(Object javaObject) {
  131. if (javaObject == null) return null;
  132. int hash = hash(javaObject);
  133. return segmentFor(hash).get(javaObject, hash);
  134. }
  135. public T getOrCreate(Object javaObject, A allocator) {
  136. if (javaObject == null || allocator == null) return null;
  137. int hash = hash(javaObject);
  138. return segmentFor(hash).getOrCreate(javaObject, hash, allocator);
  139. }
  140. public void put(Object javaObject, T proxy) {
  141. if (javaObject == null || proxy == null) return;
  142. int hash = hash(javaObject);
  143. segmentFor(hash).put(javaObject, hash, proxy);
  144. }
  145. private static int hash(Object javaObject) {
  146. int h = System.identityHashCode(javaObject);
  147. h ^= (h >>> 20) ^ (h >>> 12);
  148. return h ^ (h >>> 7) ^ (h >>> 4);
  149. }
  150. private Segment<T,A> segmentFor(int hash) {
  151. return segments[(hash >>> segmentShift) & segmentMask];
  152. }
  153. /**
  154. * Returns the approximate size (elements in use) of the cache. The
  155. * sizes of the segments are summed. No effort is made to synchronize
  156. * across segments, so the value returned may differ from the actual
  157. * size at any point in time.
  158. *
  159. * @return
  160. */
  161. public int size() {
  162. int size = 0;
  163. for (Segment<T,A> seg : segments) {
  164. size += seg.tableSize;
  165. }
  166. return size;
  167. }
  168. public String stats() {
  169. StringBuilder b = new StringBuilder();
  170. int n = 0;
  171. int size = 0;
  172. int alloc = 0;
  173. b.append("Segments: ").append(segments.length).append("\n");
  174. for (Segment<T,A> seg : segments) {
  175. int ssize = 0;
  176. int salloc = 0;
  177. seg.lock();
  178. try {
  179. ssize = seg.count();
  180. salloc = seg.entryTable.length;
  181. } finally {
  182. seg.unlock();
  183. }
  184. size += ssize;
  185. alloc += salloc;
  186. b.append("seg[").append(n++).append("]: size: ").append(ssize)
  187. .append(" alloc: ").append(salloc).append("\n");
  188. }
  189. b.append("Total: size: ").append(size)
  190. .append(" alloc: ").append(alloc).append("\n");
  191. return b.toString();
  192. }
  193. // EntryRefs include hash with key to facilitate lookup by Segment#expunge
  194. // after ref is removed from ReferenceQueue
  195. private static interface EntryRef<T> {
  196. T get();
  197. int hash();
  198. }
  199. private static final class WeakEntryRef<T> extends WeakReference<T> implements EntryRef<T> {
  200. final int hash;
  201. WeakEntryRef(int hash, T rawObject, ReferenceQueue<Object> queue) {
  202. super(rawObject, queue);
  203. this.hash = hash;
  204. }
  205. public int hash() {
  206. return hash;
  207. }
  208. }
  209. private static final class SoftEntryRef<T> extends SoftReference<T> implements EntryRef<T> {
  210. final int hash;
  211. SoftEntryRef(int hash, T rawObject, ReferenceQueue<Object> queue) {
  212. super(rawObject, queue);
  213. this.hash = hash;
  214. }
  215. public int hash() {
  216. return hash;
  217. }
  218. }
  219. // Unlike WeakHashMap, our Entry does not subclass WeakReference, but rather
  220. // makes it a final field. The theory is that doing so should force a happens-before
  221. // relationship WRT the WeakReference constructor, guaranteeing that the key will be
  222. // visibile to other threads (unless it's been GC'ed). See JLS 17.5 (final fields) and
  223. // 17.4.5 (Happens-before order) to confirm or refute my reasoning here.
  224. static class Entry<T> {
  225. final EntryRef<Object> objectRef;
  226. final int hash;
  227. final EntryRef<T> proxyRef;
  228. final Entry<T> next;
  229. Entry(Object object, int hash, T proxy, ReferenceType type, Entry<T> next, ReferenceQueue<Object> queue) {
  230. this.hash = hash;
  231. this.next = next;
  232. // references to the Java object and its proxy will either both be
  233. // weak or both be soft, since the proxy contains a strong reference
  234. // to the object, so it wouldn't make sense for the reference types
  235. // to differ.
  236. if (type == ReferenceType.WEAK) {
  237. this.objectRef = new WeakEntryRef<Object>(hash, object, queue);
  238. this.proxyRef = new WeakEntryRef<T>(hash, proxy, queue);
  239. } else {
  240. this.objectRef = new SoftEntryRef<Object>(hash, object, queue);
  241. this.proxyRef = new SoftEntryRef<T>(hash, proxy, queue);
  242. }
  243. }
  244. // ctor used by remove/rehash
  245. Entry(EntryRef<Object> objectRef, int hash, EntryRef<T> proxyRef, Entry<T> next) {
  246. this.objectRef = objectRef;
  247. this.hash = hash;
  248. this.proxyRef = proxyRef;
  249. this.next = next;
  250. }
  251. @SuppressWarnings("unchecked")
  252. static final <T> Entry<T>[] newArray(int size) {
  253. return new Entry[size];
  254. }
  255. }
  256. // lame generics issues: making Segment class static and manually
  257. // inserting cache reference to work around various problems generically
  258. // referencing methods/vars across classes.
  259. static class Segment<T,A> extends ReentrantLock {
  260. final ObjectProxyCache<T,A> cache;
  261. final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
  262. volatile Entry<T>[] entryTable;
  263. int tableSize;
  264. int threshold;
  265. Segment(int capacity, ObjectProxyCache<T,A> cache) {
  266. threshold = (int)(capacity * DEFAULT_LOAD_FACTOR);
  267. entryTable = Entry.newArray(capacity);
  268. this.cache = cache;
  269. }
  270. // must be called under lock
  271. private void expunge() {
  272. Entry<T>[] table = entryTable;
  273. ReferenceQueue<Object> queue = referenceQueue;
  274. EntryRef ref;
  275. // note that we'll potentially see the refs for both the java object and
  276. // proxy -- whichever we see first will cause the entry to be removed;
  277. // the other will not match an entry and will be ignored.
  278. while ((ref = (EntryRef)queue.poll()) != null) {
  279. int hash;
  280. for (Entry<T> e = table[(hash = ref.hash()) & (table.length - 1)]; e != null; e = e.next) {
  281. if (hash == e.hash && (ref == e.objectRef || ref == e.proxyRef)) {
  282. remove(table, hash, e);
  283. break;
  284. }
  285. }
  286. }
  287. }
  288. // must be called under lock
  289. private void remove(Entry<T>[] table, int hash, Entry<T> e) {
  290. int index = hash & (table.length - 1);
  291. Entry<T> first = table[index];
  292. for (Entry<T> n = first; n != null; n = n.next) {
  293. if (n == e) {
  294. Entry<T> newFirst = n.next;
  295. for (Entry<T> p = first; p != n; p = p.next) {
  296. newFirst = new Entry<T>(p.objectRef, p.hash, p.proxyRef, newFirst);
  297. }
  298. table[index] = newFirst;
  299. tableSize--;
  300. entryTable = table; // write-volatile
  301. return;
  302. }
  303. }
  304. }
  305. // temp method to verify tableSize value
  306. // must be called under lock
  307. private int count() {
  308. int count = 0;
  309. for (Entry<T> e : entryTable) {
  310. while (e != null) {
  311. count++;
  312. e = e.next;
  313. }
  314. }
  315. return count;
  316. }
  317. // must be called under lock
  318. private Entry<T>[] rehash() {
  319. assert tableSize == count() : "tableSize "+tableSize+" != count() "+count();
  320. Entry<T>[] oldTable = entryTable; // read-volatile
  321. int oldCapacity;
  322. if ((oldCapacity = oldTable.length) >= MAX_CAPACITY) {
  323. return oldTable;
  324. }
  325. int newCapacity = oldCapacity << 1;
  326. int sizeMask = newCapacity - 1;
  327. threshold = (int)(newCapacity * DEFAULT_LOAD_FACTOR);
  328. Entry<T>[] newTable = Entry.newArray(newCapacity);
  329. Entry<T> e;
  330. for (int i = oldCapacity; --i >= 0; ) {
  331. if ((e = oldTable[i]) != null) {
  332. int idx = e.hash & sizeMask;
  333. Entry<T> next;
  334. if ((next = e.next) == null) {
  335. // Single node in list
  336. newTable[idx] = e;
  337. } else {
  338. // Reuse trailing consecutive sequence at same slot
  339. int lastIdx = idx;
  340. Entry<T> lastRun = e;
  341. for (Entry<T> last = next; last != null; last = last.next) {
  342. int k;
  343. if ((k = last.hash & sizeMask) != lastIdx) {
  344. lastIdx = k;
  345. lastRun = last;
  346. }
  347. }
  348. newTable[lastIdx] = lastRun;
  349. // Clone all remaining nodes
  350. for (Entry<T> p = e; p != lastRun; p = p.next) {
  351. int k = p.hash & sizeMask;
  352. Entry<T> m = new Entry<T>(p.objectRef, p.hash, p.proxyRef, newTable[k]);
  353. newTable[k] = m;
  354. }
  355. }
  356. }
  357. }
  358. entryTable = newTable; // write-volatile
  359. return newTable;
  360. }
  361. void put(Object object, int hash, T proxy) {
  362. lock();
  363. try {
  364. expunge();
  365. Entry<T>[] table;
  366. int potentialNewSize;
  367. if ((potentialNewSize = tableSize + 1) > threshold) {
  368. table = rehash(); // indirect read-/write- volatile
  369. } else {
  370. table = entryTable; // read-volatile
  371. }
  372. int index;
  373. Entry<T> e;
  374. for (e = table[index = hash & (table.length - 1)]; e != null; e = e.next) {
  375. if (hash == e.hash && object == e.objectRef.get()) {
  376. if (proxy == e.proxyRef.get()) return;
  377. // entry exists, proxy doesn't match. replace.
  378. // this could happen if old proxy was gc'ed
  379. // TODO: raise exception if stored proxy is non-null? (not gc'ed)
  380. remove(table, hash, e);
  381. potentialNewSize--;
  382. break;
  383. }
  384. }
  385. e = new Entry<T>(object, hash, proxy, cache.referenceType, table[index], referenceQueue);
  386. table[index] = e;
  387. tableSize = potentialNewSize;
  388. entryTable = table; // write-volatile
  389. } finally {
  390. unlock();
  391. }
  392. }
  393. T getOrCreate(Object object, int hash, A allocator) {
  394. Entry<T>[] table;
  395. T proxy;
  396. for (Entry<T> e = (table = entryTable)[hash & table.length - 1]; e != null; e = e.next) {
  397. if (hash == e.hash && object == e.objectRef.get()) {
  398. if ((proxy = e.proxyRef.get()) != null) return proxy;
  399. break;
  400. }
  401. }
  402. lock();
  403. try {
  404. expunge();
  405. int potentialNewSize;
  406. if ((potentialNewSize = tableSize + 1) > threshold) {
  407. table = rehash(); // indirect read-/write- volatile
  408. } else {
  409. table = entryTable; // read-volatile
  410. }
  411. int index;
  412. Entry<T> e;
  413. for (e = table[index = hash & (table.length - 1)]; e != null; e = e.next) {
  414. if (hash == e.hash && object == e.objectRef.get()) {
  415. if ((proxy = e.proxyRef.get()) != null) return proxy;
  416. // entry exists, proxy has been gc'ed. replace entry.
  417. remove(table, hash, e);
  418. potentialNewSize--;
  419. break;
  420. }
  421. }
  422. proxy = cache.allocateProxy(object, allocator);
  423. e = new Entry<T>(object, hash, proxy, cache.referenceType, table[index], referenceQueue);
  424. table[index] = e;
  425. tableSize = potentialNewSize;
  426. entryTable = table; // write-volatile
  427. return proxy;
  428. } finally {
  429. unlock();
  430. }
  431. }
  432. T get(Object object, int hash) {
  433. Entry<T>[] table;
  434. for (Entry<T> e = (table = entryTable)[hash & table.length - 1]; e != null; e = e.next) {
  435. if (hash == e.hash && object == e.objectRef.get()) {
  436. return e.proxyRef.get();
  437. }
  438. }
  439. return null;
  440. }
  441. @SuppressWarnings("unchecked")
  442. static final <T,A> Segment<T,A>[] newArray(int size) {
  443. return new Segment[size];
  444. }
  445. }
  446. }