ObjectSpace.java

  1. /***** BEGIN LICENSE BLOCK *****
  2.  * Version: EPL 1.0/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Eclipse Public
  5.  * License Version 1.0 (the "License"); you may not use this file
  6.  * except in compliance with the License. You may obtain a copy of
  7.  * the License at http://www.eclipse.org/legal/epl-v10.html
  8.  *
  9.  * Software distributed under the License is distributed on an "AS
  10.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11.  * implied. See the License for the specific language governing
  12.  * rights and limitations under the License.
  13.  *
  14.  * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
  15.  * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
  16.  * Copyright (C) 2004-2006 Charles O Nutter <headius@headius.com>
  17.  * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
  18.  *
  19.  * Alternatively, the contents of this file may be used under the terms of
  20.  * either of the GNU General Public License Version 2 or later (the "GPL"),
  21.  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  22.  * in which case the provisions of the GPL or the LGPL are applicable instead
  23.  * of those above. If you wish to allow use of your version of this file only
  24.  * under the terms of either the GPL or the LGPL, and not to allow others to
  25.  * use your version of this file under the terms of the EPL, indicate your
  26.  * decision by deleting the provisions above and replace them with the notice
  27.  * and other provisions required by the GPL or the LGPL. If you do not delete
  28.  * the provisions above, a recipient may use your version of this file under
  29.  * the terms of any one of the EPL, the GPL or the LGPL.
  30.  ***** END LICENSE BLOCK *****/
  31. package org.jruby.runtime;

  32. import java.lang.ref.Reference;
  33. import java.lang.ref.ReferenceQueue;
  34. import java.lang.ref.SoftReference;
  35. import java.lang.ref.WeakReference;
  36. import java.util.*;
  37. import java.util.concurrent.atomic.AtomicLong;
  38. import java.util.concurrent.atomic.AtomicReferenceArray;

  39. import org.jruby.RubyModule;
  40. import org.jruby.java.proxies.JavaProxy;
  41. import org.jruby.runtime.builtin.IRubyObject;
  42. import org.jruby.util.WeakIdentityHashMap;

  43. /**
  44.  * FIXME: This version is faster than the previous, but both suffer from a
  45.  * crucial flaw: It is impossible to create an ObjectSpace with an iterator
  46.  * that doesn't either: a. hold on to objects that might otherwise be collected
  47.  * or b. have no way to guarantee that a call to hasNext() will be correct or
  48.  * that a subsequent call to next() will produce an object. For our purposes,
  49.  * for now, this may be acceptable.
  50.  */
  51. public class ObjectSpace {
  52.     private final ReferenceQueue<Object> deadReferences = new ReferenceQueue<Object>();
  53.     private final ReferenceQueue<ObjectGroup> objectGroupReferenceQueue = new ReferenceQueue<ObjectGroup>();
  54.     private WeakReferenceListNode top;

  55.     private ReferenceQueue deadIdentityReferences = new ReferenceQueue();
  56.     private final Map identities = new HashMap();
  57.     private final Map identitiesByObject = new WeakIdentityHashMap();
  58.     private static final AtomicLong maxId = new AtomicLong(1000);
  59.     private final ThreadLocal<Reference<ObjectGroup>> currentObjectGroup = new ThreadLocal<Reference<ObjectGroup>>();
  60.     private Reference<GroupSweeper> groupSweeperReference;

  61.     public void registerObjectId(long id, IRubyObject object) {
  62.         synchronized (identities) {
  63.             cleanIdentities();
  64.             identities.put(id, new IdReference(object, id, deadIdentityReferences));
  65.             identitiesByObject.put(object, id);
  66.         }
  67.     }

  68.     public static long calculateObjectId(Object object) {
  69.         // Fixnums get all the odd IDs, so we use identityHashCode * 2
  70.         return maxId.getAndIncrement() * 2;
  71.     }
  72.    
  73.     public long createAndRegisterObjectId(IRubyObject rubyObject) {
  74.         synchronized (identities) {
  75.             Long longId = (Long) identitiesByObject.get(rubyObject);
  76.             if (longId == null) {
  77.                 longId = createId(rubyObject);
  78.             }
  79.             return longId.longValue();
  80.         }
  81.     }

  82.     private long createId(IRubyObject object) {
  83.         long id = calculateObjectId(object);
  84.         registerObjectId(id, object);
  85.         return id;
  86.     }

  87.     public IRubyObject id2ref(long id) {
  88.         synchronized (identities) {
  89.             cleanIdentities();
  90.             IdReference reference = (IdReference) identities.get(Long.valueOf(id));
  91.             if (reference == null) {
  92.                 return null;
  93.             }
  94.             return (IRubyObject) reference.get();
  95.         }
  96.     }

  97.     private void cleanIdentities() {
  98.         IdReference ref;
  99.         while ((ref = (IdReference) deadIdentityReferences.poll()) != null)
  100.             identities.remove(Long.valueOf(ref.id()));
  101.     }

  102.     @Deprecated
  103.     public long idOf(IRubyObject rubyObject) {
  104.         return createAndRegisterObjectId(rubyObject);
  105.     }
  106.    
  107.     public void addFinalizer(IRubyObject object, IRubyObject proc) {
  108.         object.addFinalizer(proc);
  109.     }
  110.    
  111.     public void removeFinalizers(long id) {
  112.         IRubyObject object = id2ref(id);
  113.         if (object != null) {
  114.             object.removeFinalizers();
  115.         }
  116.     }
  117.    
  118.     public void add(IRubyObject object) {
  119.         if (true && object.getMetaClass() != null && !(object instanceof JavaProxy)) {
  120.             // If the object is already frozen when we encounter it, it's pre-frozen.
  121.             // Since this only (currently) applies to objects created outside the
  122.             // normal routes of construction, we don't show it in ObjectSpace.
  123.             if (object.isFrozen()) return;

  124.             getObjectGroup().add(object);
  125.         } else {
  126.             addIndividualWeakReference(object);
  127.         }
  128.     }

  129.     private synchronized void addIndividualWeakReference(IRubyObject object) {
  130.         cleanup(deadReferences);
  131.         top = new WeakReferenceListNode<Object>(object, deadReferences, top);
  132.     }

  133.     private synchronized void registerGroupSweeper() {
  134.         if (groupSweeperReference == null || groupSweeperReference.get() == null) {
  135.             groupSweeperReference = new WeakReference<GroupSweeper>(new GroupSweeper());
  136.         }
  137.     }

  138.     private synchronized void splitObjectGroups() {
  139.         cleanup(objectGroupReferenceQueue);

  140.         // Split apart each group into individual weak references
  141.         WeakReferenceListNode node = top;
  142.         while (node != null) {
  143.             Object obj = node.get();
  144.             if (obj instanceof ObjectGroup) {
  145.                 ObjectGroup objectGroup = (ObjectGroup) obj;
  146.                 for (int i = 0; i < objectGroup.size(); i++) {
  147.                     IRubyObject rubyObject = objectGroup.set(i, null);
  148.                     if (rubyObject != null) {
  149.                         top = new WeakReferenceListNode<Object>(rubyObject, deadReferences, top);
  150.                     }
  151.                 }
  152.             }
  153.             node = node.nextNode;
  154.         }
  155.     }

  156.     public synchronized Iterator iterator(RubyModule rubyClass) {
  157.         final List<Reference<Object>> objList = new ArrayList<Reference<Object>>();
  158.         WeakReferenceListNode current = top;
  159.         while (current != null) {
  160.             Object obj = current.get();
  161.             if (obj instanceof IRubyObject) {
  162.                 IRubyObject rubyObject = (IRubyObject)obj;
  163.                 if (rubyObject != null && rubyClass.isInstance(rubyObject)) {
  164.                     objList.add(current);
  165.                 }

  166.             } else if (obj instanceof ObjectGroup) {
  167.                 for (IRubyObject rubyObject : (ObjectGroup) obj) {
  168.                     if (rubyObject != null && rubyClass.isInstance(rubyObject)) {
  169.                         objList.add(new WeakReference<Object>(rubyObject));
  170.                     }
  171.                 }
  172.             }

  173.             current = current.nextNode;
  174.         }

  175.         return new Iterator() {
  176.             private Iterator<Reference<Object>> iter = objList.iterator();

  177.             public boolean hasNext() {
  178.                 throw new UnsupportedOperationException();
  179.             }

  180.             public Object next() {
  181.                 Object obj = null;
  182.                 while (iter.hasNext()) {
  183.                     obj = iter.next().get();

  184.                     if (obj != null) break;
  185.                 }
  186.                 return obj;
  187.             }

  188.             public void remove() {
  189.                 throw new UnsupportedOperationException();
  190.             }
  191.         };
  192.     }

  193.     private void cleanup(ReferenceQueue<?> referenceQueue) {
  194.         WeakReferenceListNode reference;
  195.         while ((reference = (WeakReferenceListNode)referenceQueue.poll()) != null) {
  196.             reference.remove();
  197.         }
  198.     }

  199.     private class WeakReferenceListNode<T> extends WeakReference<T> {
  200.         private WeakReferenceListNode prevNode;
  201.         private WeakReferenceListNode nextNode;

  202.         public WeakReferenceListNode(T referent, ReferenceQueue<T> queue, WeakReferenceListNode<?> next) {
  203.             super(referent, queue);

  204.             this.nextNode = next;
  205.             if (next != null) {
  206.                 next.prevNode = this;
  207.             }
  208.         }

  209.         private void remove() {
  210.             if (prevNode != null) {
  211.                 prevNode.nextNode = nextNode;
  212.             } else {
  213.                 top = nextNode;
  214.             }
  215.             if (nextNode != null) {
  216.                 nextNode.prevNode = prevNode;
  217.             }
  218.         }
  219.     }

  220.     private static class IdReference extends WeakReference {
  221.         private final long id;

  222.         public IdReference(IRubyObject object, long id, ReferenceQueue queue) {
  223.             super(object, queue);
  224.             this.id = id;
  225.         }

  226.         public long id() {
  227.             return id;
  228.         }
  229.     }

  230.     private ObjectGroup getObjectGroup() {
  231.         Reference<ObjectGroup> ref = currentObjectGroup.get();
  232.         ObjectGroup objectGroup = ref != null ? ref.get() : null;
  233.         return objectGroup != null && !objectGroup.isFull() ? objectGroup : addObjectGroup();
  234.     }


  235.     private synchronized ObjectGroup addObjectGroup() {
  236.         cleanup(objectGroupReferenceQueue);
  237.         ObjectGroup objectGroup;
  238.         WeakReferenceListNode<ObjectGroup> ref = new WeakReferenceListNode<ObjectGroup>(objectGroup = new ObjectGroup(),
  239.                 objectGroupReferenceQueue, top);
  240.         currentObjectGroup.set(ref);
  241.         top = ref;
  242.         if (groupSweeperReference == null) registerGroupSweeper();

  243.         return objectGroup;
  244.     }

  245.     private static final class ObjectGroup extends AbstractList<IRubyObject> {
  246.         private static final int MAX_OBJECTS_PER_GROUP = 64;
  247.         private final AtomicReferenceArray<IRubyObject> objects = new AtomicReferenceArray<IRubyObject>(MAX_OBJECTS_PER_GROUP);
  248.         private int nextIndex = 0;

  249.         public boolean add(IRubyObject obj) {
  250.             obj.getMetaClass().getObjectGroupAccessorForWrite().set(obj, this);
  251.             objects.set(nextIndex, obj);
  252.             ++nextIndex;
  253.             return true;
  254.         }

  255.         @Override
  256.         public IRubyObject get(int index) {
  257.             return objects.get(index);
  258.         }

  259.         @Override
  260.         public IRubyObject set(int index, IRubyObject element) {
  261.             return objects.getAndSet(index, element);
  262.         }

  263.         private boolean isFull() {
  264.             return nextIndex >= objects.length();
  265.         }

  266.         public int size() {
  267.             return objects.length();
  268.         }
  269.     }

  270.     // This class is used as a low-memory cleaner.  When it is finalized, it will cause all groups to be split into
  271.     // individual weak refs, so each object can be collected.
  272.     private final class GroupSweeper {

  273.         @Override
  274.         protected void finalize() throws Throwable {
  275.             try {
  276.                 splitObjectGroups();
  277.             } finally {
  278.                 registerGroupSweeper();
  279.                 super.finalize();
  280.             }
  281.         }
  282.     }
  283. }