void recursivelyReachable(GCShared* p, GCObjectSet& o) { if(o.find(p) == o.end()) { assert(p); o.insert(p); GCCountMap tmp; p->__gcReachable(tmp); for(GCCountMap::const_iterator i = tmp.begin(); i != tmp.end(); ++i) { recursivelyReachable(i->first, o); } } }
bool GCObject::collect(IceUtilInternal::MutexPtrLock<IceUtil::Mutex>& lock) { GCCountMap counts; // // Go through the object graph and decrease reference counts for // all the objects from the graph. Cycles which can be collected // should lead to objects with a zero reference count. // DecreaseRefCounts(counts).visit(this); assert(counts.find(this) != counts.end()); if(counts[this] > 0) { return false; // Object is still reachable, we're done. } // // Go the graph again and check for objects which are still // reachable. If there are any, we restore the reference counts // for the sub-graph of the reachable object and we remove the // reachable objects from the counts map. At the end, if the // counts map is empty, it indicates that this object graph isn't // collectable yet. // RestoreRefCountsIfReachable(counts).visit(this); if(counts.empty()) { return false; } assert(counts.find(this) != counts.end()); // This object must be collectable. // // At this point, we can release the lock. The objects from counts // are un-reachable from the user code and clearing the members of // the collectable objects requires acquiring the mutex to // decrement their reference counts. // lock.release(); // // Break all the cyclic reference counts of objects which are // remaining in the counts map by clearing members. // // We first go through the list to mark all the objects as // non-deletable and we also disable collection for all those // objects since we already know they are collectable. // // After clearing members, we delete all the collectable // objects. We can't just delete the objects since those objects // likely point to each other. // for(GCCountMap::const_iterator p = counts.begin(); p != counts.end(); ++p) { p->first->__setFlag(NoDelete); p->first->__clearFlag(CycleMember); // Disable cycle collection. } for(GCCountMap::const_iterator p = counts.begin(); p != counts.end(); ++p) { p->first->_iceGcVisitMembers(clearMembers); } for(GCCountMap::const_iterator p = counts.begin(); p != counts.end(); ++p) { delete p->first; } return true; }
void IceInternal::GC::collectGarbage() { // // Do nothing if the collector is running already. // { Monitor<Mutex>::Lock sync(*this); if(_collecting) { return; } _collecting = true; } RecMutex::Lock sync(gcRecMutex); // Prevent any further class reference count activity. Time t; GCStats stats; if(_statsCallback) { t = Time::now(); stats.examined = static_cast<int>(gcObjects.size()); } GCCountMap counts; { // // gcObjects contains the set of class instances that have at least one member of class type, // that is, gcObjects contains all those instances that can point at other instances. // // Create a map that, for each object in gcObjects, contains an <object, refcount> pair. // In addition, for each object in gcObjects, add the objects that are immediately (not // recursively) reachable from that object to a reachable map that counts how many times // the object is pointed at. // GCCountMap reachable; { for(GCObjectSet::const_iterator i = gcObjects.begin(); i != gcObjects.end(); ++i) { counts.insert(GCCountMap::value_type(*i, (*i)->__getRefUnsafe())); (*i)->__gcReachable(reachable); } } // // Decrement the reference count for each object in the counts map by the count in the reachable // map. This drops the reference count of each object in the counts map by the number of times that // the object is pointed at by other objects in the counts map. // { for(GCCountMap::const_iterator i = reachable.begin(); i != reachable.end(); ++i) { GCCountMap::iterator pos = counts.find(i->first); assert(pos != counts.end()); pos->second -= i->second; } } } { // // Any instances in the counts map with a ref count > 0 are referenced from outside the objects in // gcObjects (and are therefore reachable from the program, for example, via Ptr variable on the stack). // The set of live objects therefore are all the objects with a reference count > 0, as well as all // objects that are (recursively) reachable from these objects. // GCObjectSet liveObjects; { for(GCCountMap::const_iterator i = counts.begin(); i != counts.end(); ++i) { if(i->second > 0) { recursivelyReachable(i->first, liveObjects); } } } // // Remove all live objects from the counts map. // { for(GCObjectSet::const_iterator i = liveObjects.begin(); i != liveObjects.end(); ++i) { #ifndef NDEBUG size_t erased = #endif counts.erase(*i); assert(erased != 0); } } } // // What is left in the counts map can be garbage collected. // { GCCountMap::const_iterator i; for(i = counts.begin(); i != counts.end(); ++i) { // // For classes with members that point at potentially-cyclic instances, __gcClear() // decrements the reference count of the pointed-at instances as many times as they are // pointed at and clears the corresponding Ptr members in the pointing class. // For classes that cannot be part of a cycle (because they do not contain class members) // and are therefore true leaves, __gcClear() assigns 0 to the corresponding class member, // which either decrements the ref count or, if it reaches zero, deletes the instance as usual. // i->first->__gcClear(); } for(i = counts.begin(); i != counts.end(); ++i) { gcObjects.erase(i->first); // Remove this object from candidate set. delete i->first; // Delete this object. } } if(_statsCallback) { stats.time = Time::now() - t; stats.collected = static_cast<int>(counts.size()); _statsCallback(stats); } // // We clear explicitly under protection of the lock, instead of waiting for the // counts destructor. This avoids lots of lock contention later because, otherwise, // the destructor of each object in the counts set would acquire and release // gcRecMutex._m. // counts.clear(); { Monitor<Mutex>::Lock sync(*this); _collecting = false; } }