Home | History | Annotate | Download | only in v8
      1 /*
      2  * Copyright (C) 2009 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "bindings/core/v8/V8GCController.h"
     33 
     34 #include "bindings/core/v8/RetainedDOMInfo.h"
     35 #include "bindings/core/v8/V8AbstractEventListener.h"
     36 #include "bindings/core/v8/V8Binding.h"
     37 #include "bindings/core/v8/V8MutationObserver.h"
     38 #include "bindings/core/v8/V8Node.h"
     39 #include "bindings/core/v8/V8ScriptRunner.h"
     40 #include "bindings/core/v8/WrapperTypeInfo.h"
     41 #include "core/dom/Attr.h"
     42 #include "core/dom/Document.h"
     43 #include "core/dom/NodeTraversal.h"
     44 #include "core/dom/TemplateContentDocumentFragment.h"
     45 #include "core/dom/shadow/ElementShadow.h"
     46 #include "core/dom/shadow/ShadowRoot.h"
     47 #include "core/html/HTMLImageElement.h"
     48 #include "core/html/HTMLTemplateElement.h"
     49 #include "core/html/imports/HTMLImportsController.h"
     50 #include "core/inspector/InspectorTraceEvents.h"
     51 #include "core/svg/SVGElement.h"
     52 #include "platform/Partitions.h"
     53 #include "platform/TraceEvent.h"
     54 #include "wtf/Vector.h"
     55 #include <algorithm>
     56 
     57 namespace blink {
     58 
     59 // FIXME: This should use opaque GC roots.
     60 static void addReferencesForNodeWithEventListeners(v8::Isolate* isolate, Node* node, const v8::Persistent<v8::Object>& wrapper)
     61 {
     62     ASSERT(node->hasEventListeners());
     63 
     64     EventListenerIterator iterator(node);
     65     while (EventListener* listener = iterator.nextListener()) {
     66         if (listener->type() != EventListener::JSEventListenerType)
     67             continue;
     68         V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListener*>(listener);
     69         if (!v8listener->hasExistingListenerObject())
     70             continue;
     71 
     72         isolate->SetReference(wrapper, v8::Persistent<v8::Value>::Cast(v8listener->existingListenerObjectPersistentHandle()));
     73     }
     74 }
     75 
     76 Node* V8GCController::opaqueRootForGC(Node* node, v8::Isolate*)
     77 {
     78     ASSERT(node);
     79     // FIXME: Remove the special handling for image elements.
     80     // The same special handling is in V8GCController::gcTree().
     81     // Maybe should image elements be active DOM nodes?
     82     // See https://code.google.com/p/chromium/issues/detail?id=164882
     83     if (node->inDocument() || (isHTMLImageElement(*node) && toHTMLImageElement(*node).hasPendingActivity())) {
     84         Document& document = node->document();
     85         if (HTMLImportsController* controller = document.importsController())
     86             return controller->master();
     87         return &document;
     88     }
     89 
     90     if (node->isAttributeNode()) {
     91         Node* ownerElement = toAttr(node)->ownerElement();
     92         if (!ownerElement)
     93             return node;
     94         node = ownerElement;
     95     }
     96 
     97     while (Node* parent = node->parentOrShadowHostOrTemplateHostNode())
     98         node = parent;
     99 
    100     return node;
    101 }
    102 
    103 // Regarding a minor GC algorithm for DOM nodes, see this document:
    104 // https://docs.google.com/a/google.com/presentation/d/1uifwVYGNYTZDoGLyCb7sXa7g49mWNMW2gaWvMN5NLk8/edit#slide=id.p
    105 class MinorGCWrapperVisitor : public v8::PersistentHandleVisitor {
    106 public:
    107     explicit MinorGCWrapperVisitor(v8::Isolate* isolate)
    108         : m_isolate(isolate)
    109     { }
    110 
    111     virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
    112     {
    113         // A minor DOM GC can collect only Nodes.
    114         if (classId != WrapperTypeInfo::NodeClassId)
    115             return;
    116 
    117         // To make minor GC cycle time bounded, we limit the number of wrappers handled
    118         // by each minor GC cycle to 10000. This value was selected so that the minor
    119         // GC cycle time is bounded to 20 ms in a case where the new space size
    120         // is 16 MB and it is full of wrappers (which is almost the worst case).
    121         // Practically speaking, as far as I crawled real web applications,
    122         // the number of wrappers handled by each minor GC cycle is at most 3000.
    123         // So this limit is mainly for pathological micro benchmarks.
    124         const unsigned wrappersHandledByEachMinorGC = 10000;
    125         if (m_nodesInNewSpace.size() >= wrappersHandledByEachMinorGC)
    126             return;
    127 
    128         // Casting to a Handle is safe here, since the Persistent doesn't get GCd
    129         // during the GC prologue.
    130         ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
    131         v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
    132         ASSERT(V8DOMWrapper::isDOMWrapper(*wrapper));
    133         ASSERT(V8Node::hasInstance(*wrapper, m_isolate));
    134         Node* node = V8Node::toImpl(*wrapper);
    135         // A minor DOM GC can handle only node wrappers in the main world.
    136         // Note that node->wrapper().IsEmpty() returns true for nodes that
    137         // do not have wrappers in the main world.
    138         if (node->containsWrapper()) {
    139             const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
    140             ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
    141             if (activeDOMObject && activeDOMObject->hasPendingActivity())
    142                 return;
    143             // FIXME: Remove the special handling for image elements.
    144             // The same special handling is in V8GCController::opaqueRootForGC().
    145             // Maybe should image elements be active DOM nodes?
    146             // See https://code.google.com/p/chromium/issues/detail?id=164882
    147             if (isHTMLImageElement(*node) && toHTMLImageElement(*node).hasPendingActivity())
    148                 return;
    149             // FIXME: Remove the special handling for SVG elements.
    150             // We currently can't collect SVG Elements from minor gc, as we have
    151             // strong references from SVG property tear-offs keeping context SVG element alive.
    152             if (node->isSVGElement())
    153                 return;
    154 
    155             m_nodesInNewSpace.append(node);
    156             node->markV8CollectableDuringMinorGC();
    157         }
    158     }
    159 
    160     void notifyFinished()
    161     {
    162         for (size_t i = 0; i < m_nodesInNewSpace.size(); i++) {
    163             Node* node = m_nodesInNewSpace[i];
    164             ASSERT(node->containsWrapper());
    165             if (node->isV8CollectableDuringMinorGC()) { // This branch is just for performance.
    166                 gcTree(m_isolate, node);
    167                 node->clearV8CollectableDuringMinorGC();
    168             }
    169         }
    170     }
    171 
    172 private:
    173     bool traverseTree(Node* rootNode, WillBeHeapVector<RawPtrWillBeMember<Node>, initialNodeVectorSize>* partiallyDependentNodes)
    174     {
    175         // To make each minor GC time bounded, we might need to give up
    176         // traversing at some point for a large DOM tree. That being said,
    177         // I could not observe the need even in pathological test cases.
    178         for (Node* node = rootNode; node; node = NodeTraversal::next(*node)) {
    179             if (node->containsWrapper()) {
    180                 if (!node->isV8CollectableDuringMinorGC()) {
    181                     // This node is not in the new space of V8. This indicates that
    182                     // the minor GC cannot anyway judge reachability of this DOM tree.
    183                     // Thus we give up traversing the DOM tree.
    184                     return false;
    185                 }
    186                 node->clearV8CollectableDuringMinorGC();
    187                 partiallyDependentNodes->append(node);
    188             }
    189             if (ShadowRoot* shadowRoot = node->youngestShadowRoot()) {
    190                 if (!traverseTree(shadowRoot, partiallyDependentNodes))
    191                     return false;
    192             } else if (node->isShadowRoot()) {
    193                 if (ShadowRoot* shadowRoot = toShadowRoot(node)->olderShadowRoot()) {
    194                     if (!traverseTree(shadowRoot, partiallyDependentNodes))
    195                         return false;
    196                 }
    197             }
    198             // <template> has a |content| property holding a DOM fragment which we must traverse,
    199             // just like we do for the shadow trees above.
    200             if (isHTMLTemplateElement(*node)) {
    201                 if (!traverseTree(toHTMLTemplateElement(*node).content(), partiallyDependentNodes))
    202                     return false;
    203             }
    204 
    205             // Document maintains the list of imported documents through HTMLImportsController.
    206             if (node->isDocumentNode()) {
    207                 Document* document = toDocument(node);
    208                 HTMLImportsController* controller = document->importsController();
    209                 if (controller && document == controller->master()) {
    210                     for (unsigned i = 0; i < controller->loaderCount(); ++i) {
    211                         if (!traverseTree(controller->loaderDocumentAt(i), partiallyDependentNodes))
    212                             return false;
    213                     }
    214                 }
    215             }
    216         }
    217         return true;
    218     }
    219 
    220     void gcTree(v8::Isolate* isolate, Node* startNode)
    221     {
    222         WillBeHeapVector<RawPtrWillBeMember<Node>, initialNodeVectorSize> partiallyDependentNodes;
    223 
    224         Node* node = startNode;
    225         while (Node* parent = node->parentOrShadowHostOrTemplateHostNode())
    226             node = parent;
    227 
    228         if (!traverseTree(node, &partiallyDependentNodes))
    229             return;
    230 
    231         // We completed the DOM tree traversal. All wrappers in the DOM tree are
    232         // stored in partiallyDependentNodes and are expected to exist in the new space of V8.
    233         // We report those wrappers to V8 as an object group.
    234         if (!partiallyDependentNodes.size())
    235             return;
    236         Node* groupRoot = partiallyDependentNodes[0];
    237         for (size_t i = 0; i < partiallyDependentNodes.size(); i++) {
    238             partiallyDependentNodes[i]->markAsDependentGroup(groupRoot, isolate);
    239         }
    240     }
    241 
    242     WillBePersistentHeapVector<RawPtrWillBeMember<Node> > m_nodesInNewSpace;
    243     v8::Isolate* m_isolate;
    244 };
    245 
    246 class MajorGCWrapperVisitor : public v8::PersistentHandleVisitor {
    247 public:
    248     explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedObjectInfos)
    249         : m_isolate(isolate)
    250         , m_liveRootGroupIdSet(false)
    251         , m_constructRetainedObjectInfos(constructRetainedObjectInfos)
    252     {
    253     }
    254 
    255     virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
    256     {
    257         if (classId != WrapperTypeInfo::NodeClassId && classId != WrapperTypeInfo::ObjectClassId)
    258             return;
    259 
    260         // Casting to a Handle is safe here, since the Persistent doesn't get GCd
    261         // during the GC prologue.
    262         ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject());
    263         v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value);
    264         ASSERT(V8DOMWrapper::isDOMWrapper(*wrapper));
    265 
    266         if (value->IsIndependent())
    267             return;
    268 
    269         const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
    270 
    271         ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
    272         if (activeDOMObject && activeDOMObject->hasPendingActivity())
    273             m_isolate->SetObjectGroupId(*value, liveRootId());
    274 
    275         if (classId == WrapperTypeInfo::NodeClassId) {
    276             ASSERT(V8Node::hasInstance(*wrapper, m_isolate));
    277             Node* node = V8Node::toImpl(*wrapper);
    278             if (node->hasEventListeners())
    279                 addReferencesForNodeWithEventListeners(m_isolate, node, v8::Persistent<v8::Object>::Cast(*value));
    280             Node* root = V8GCController::opaqueRootForGC(node, m_isolate);
    281             m_isolate->SetObjectGroupId(*value, v8::UniqueId(reinterpret_cast<intptr_t>(root)));
    282             if (m_constructRetainedObjectInfos)
    283                 m_groupsWhichNeedRetainerInfo.append(root);
    284         } else if (classId == WrapperTypeInfo::ObjectClassId) {
    285             type->visitDOMWrapper(toScriptWrappableBase(*wrapper), v8::Persistent<v8::Object>::Cast(*value), m_isolate);
    286         } else {
    287             ASSERT_NOT_REACHED();
    288         }
    289     }
    290 
    291     void notifyFinished()
    292     {
    293         if (!m_constructRetainedObjectInfos)
    294             return;
    295         std::sort(m_groupsWhichNeedRetainerInfo.begin(), m_groupsWhichNeedRetainerInfo.end());
    296         Node* alreadyAdded = 0;
    297         v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler();
    298         for (size_t i = 0; i < m_groupsWhichNeedRetainerInfo.size(); ++i) {
    299             Node* root = m_groupsWhichNeedRetainerInfo[i];
    300             if (root != alreadyAdded) {
    301                 profiler->SetRetainedObjectInfo(v8::UniqueId(reinterpret_cast<intptr_t>(root)), new RetainedDOMInfo(root));
    302                 alreadyAdded = root;
    303             }
    304         }
    305     }
    306 
    307 private:
    308     v8::UniqueId liveRootId()
    309     {
    310         const v8::Persistent<v8::Value>& liveRoot = V8PerIsolateData::from(m_isolate)->ensureLiveRoot();
    311         const intptr_t* idPointer = reinterpret_cast<const intptr_t*>(&liveRoot);
    312         v8::UniqueId id(*idPointer);
    313         if (!m_liveRootGroupIdSet) {
    314             m_isolate->SetObjectGroupId(liveRoot, id);
    315             m_liveRootGroupIdSet = true;
    316         }
    317         return id;
    318     }
    319 
    320     v8::Isolate* m_isolate;
    321     WillBePersistentHeapVector<RawPtrWillBeMember<Node> > m_groupsWhichNeedRetainerInfo;
    322     bool m_liveRootGroupIdSet;
    323     bool m_constructRetainedObjectInfos;
    324 };
    325 
    326 static unsigned long long usedHeapSize(v8::Isolate* isolate)
    327 {
    328     v8::HeapStatistics heapStatistics;
    329     isolate->GetHeapStatistics(&heapStatistics);
    330     return heapStatistics.used_heap_size();
    331 }
    332 
    333 void V8GCController::gcPrologue(v8::GCType type, v8::GCCallbackFlags flags)
    334 {
    335     // FIXME: It would be nice if the GC callbacks passed the Isolate directly....
    336     v8::Isolate* isolate = v8::Isolate::GetCurrent();
    337     TRACE_EVENT_BEGIN1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "GCEvent", "usedHeapSizeBefore", usedHeapSize(isolate));
    338     if (type == v8::kGCTypeScavenge)
    339         minorGCPrologue(isolate);
    340     else if (type == v8::kGCTypeMarkSweepCompact)
    341         majorGCPrologue(flags & v8::kGCCallbackFlagConstructRetainedObjectInfos, isolate);
    342 }
    343 
    344 void V8GCController::minorGCPrologue(v8::Isolate* isolate)
    345 {
    346     TRACE_EVENT_BEGIN0("v8", "minorGC");
    347     if (isMainThread()) {
    348         ScriptForbiddenScope::enter();
    349         {
    350             TRACE_EVENT_SCOPED_SAMPLING_STATE("blink", "DOMMinorGC");
    351             v8::HandleScope scope(isolate);
    352             MinorGCWrapperVisitor visitor(isolate);
    353             v8::V8::VisitHandlesForPartialDependence(isolate, &visitor);
    354             visitor.notifyFinished();
    355         }
    356         V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
    357         TRACE_EVENT_SET_SAMPLING_STATE("v8", "V8MinorGC");
    358     }
    359 }
    360 
    361 // Create object groups for DOM tree nodes.
    362 void V8GCController::majorGCPrologue(bool constructRetainedObjectInfos, v8::Isolate* isolate)
    363 {
    364     v8::HandleScope scope(isolate);
    365     TRACE_EVENT_BEGIN0("v8", "majorGC");
    366     if (isMainThread()) {
    367         ScriptForbiddenScope::enter();
    368         {
    369             TRACE_EVENT_SCOPED_SAMPLING_STATE("blink", "DOMMajorGC");
    370             MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
    371             v8::V8::VisitHandlesWithClassIds(&visitor);
    372             visitor.notifyFinished();
    373         }
    374         V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
    375         TRACE_EVENT_SET_SAMPLING_STATE("v8", "V8MajorGC");
    376     } else {
    377         MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
    378         v8::V8::VisitHandlesWithClassIds(&visitor);
    379         visitor.notifyFinished();
    380     }
    381 }
    382 
    383 void V8GCController::gcEpilogue(v8::GCType type, v8::GCCallbackFlags flags)
    384 {
    385     // FIXME: It would be nice if the GC callbacks passed the Isolate directly....
    386     v8::Isolate* isolate = v8::Isolate::GetCurrent();
    387     if (type == v8::kGCTypeScavenge)
    388         minorGCEpilogue(isolate);
    389     else if (type == v8::kGCTypeMarkSweepCompact)
    390         majorGCEpilogue(isolate);
    391 
    392     // Forces a Blink heap garbage collection when a garbage collection
    393     // was forced from V8. This is used for tests that force GCs from
    394     // JavaScript to verify that objects die when expected.
    395     if (flags & v8::kGCCallbackFlagForced) {
    396         // This single GC is not enough for two reasons:
    397         //   (1) The GC is not precise because the GC scans on-stack pointers conservatively.
    398         //   (2) One GC is not enough to break a chain of persistent handles. It's possible that
    399         //       some heap allocated objects own objects that contain persistent handles
    400         //       pointing to other heap allocated objects. To break the chain, we need multiple GCs.
    401         //
    402         // Regarding (1), we force a precise GC at the end of the current event loop. So if you want
    403         // to collect all garbage, you need to wait until the next event loop.
    404         // Regarding (2), it would be OK in practice to trigger only one GC per gcEpilogue, because
    405         // GCController.collectAll() forces 7 V8's GC.
    406         Heap::collectGarbage(ThreadState::HeapPointersOnStack, ThreadState::ForcedGC);
    407 
    408         // Forces a precise GC at the end of the current event loop.
    409         Heap::setForcePreciseGCForTesting();
    410     }
    411 
    412     TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "GCEvent", "usedHeapSizeAfter", usedHeapSize(isolate));
    413     TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "UpdateCounters", "data", InspectorUpdateCountersEvent::data());
    414 }
    415 
    416 void V8GCController::minorGCEpilogue(v8::Isolate* isolate)
    417 {
    418     TRACE_EVENT_END0("v8", "minorGC");
    419     if (isMainThread()) {
    420         TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
    421         ScriptForbiddenScope::exit();
    422     }
    423 }
    424 
    425 void V8GCController::majorGCEpilogue(v8::Isolate* isolate)
    426 {
    427     TRACE_EVENT_END0("v8", "majorGC");
    428     if (isMainThread()) {
    429         TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
    430         ScriptForbiddenScope::exit();
    431 
    432         // Schedule a precise GC to avoid the following scenario:
    433         // (1) A DOM object X holds a v8::Persistent to a V8 object.
    434         //     Assume that X is small but the V8 object is huge.
    435         //     The v8::Persistent is released when X is destructed.
    436         // (2) X's DOM wrapper is created.
    437         // (3) The DOM wrapper becomes unreachable.
    438         // (4) V8 triggers a GC. The V8's GC collects the DOM wrapper.
    439         //     However, X is not collected until a next Oilpan's GC is
    440         //     triggered.
    441         // (5) If a lot of such DOM objects are created, we end up with
    442         //     a situation where V8's GC collects the DOM wrappers but
    443         //     the DOM objects are not collected forever. (Note that
    444         //     Oilpan's GC is not triggered unless Oilpan's heap gets full.)
    445         // (6) V8 hits OOM.
    446         ThreadState::current()->setGCRequested();
    447     }
    448 }
    449 
    450 void V8GCController::collectGarbage(v8::Isolate* isolate)
    451 {
    452     v8::HandleScope handleScope(isolate);
    453     RefPtr<ScriptState> scriptState = ScriptState::create(v8::Context::New(isolate), DOMWrapperWorld::create());
    454     ScriptState::Scope scope(scriptState.get());
    455     V8ScriptRunner::compileAndRunInternalScript(v8String(isolate, "if (gc) gc();"), isolate);
    456     scriptState->disposePerContextData();
    457 }
    458 
    459 void V8GCController::reportDOMMemoryUsageToV8(v8::Isolate* isolate)
    460 {
    461     if (!isMainThread())
    462         return;
    463 
    464     static size_t lastUsageReportedToV8 = 0;
    465 
    466     size_t currentUsage = Partitions::currentDOMMemoryUsage();
    467     int64_t diff = static_cast<int64_t>(currentUsage) - static_cast<int64_t>(lastUsageReportedToV8);
    468     isolate->AdjustAmountOfExternalAllocatedMemory(diff);
    469 
    470     lastUsageReportedToV8 = currentUsage;
    471 }
    472 
    473 } // namespace blink
    474