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/v8/V8GCController.h" 33 34 #include "bindings/core/v8/V8MutationObserver.h" 35 #include "bindings/core/v8/V8Node.h" 36 #include "bindings/v8/RetainedDOMInfo.h" 37 #include "bindings/v8/V8AbstractEventListener.h" 38 #include "bindings/v8/V8Binding.h" 39 #include "bindings/v8/V8ScriptRunner.h" 40 #include "bindings/v8/WrapperTypeInfo.h" 41 #include "core/dom/Attr.h" 42 #include "core/dom/NodeTraversal.h" 43 #include "core/dom/TemplateContentDocumentFragment.h" 44 #include "core/dom/shadow/ElementShadow.h" 45 #include "core/dom/shadow/ShadowRoot.h" 46 #include "core/html/HTMLImageElement.h" 47 #include "core/html/HTMLTemplateElement.h" 48 #include "core/html/imports/HTMLImportsController.h" 49 #include "core/inspector/InspectorTraceEvents.h" 50 #include "core/svg/SVGElement.h" 51 #include "platform/Partitions.h" 52 #include "platform/TraceEvent.h" 53 #include <algorithm> 54 55 namespace WebCore { 56 57 // FIXME: This should use opaque GC roots. 58 static void addReferencesForNodeWithEventListeners(v8::Isolate* isolate, Node* node, const v8::Persistent<v8::Object>& wrapper) 59 { 60 ASSERT(node->hasEventListeners()); 61 62 EventListenerIterator iterator(node); 63 while (EventListener* listener = iterator.nextListener()) { 64 if (listener->type() != EventListener::JSEventListenerType) 65 continue; 66 V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListener*>(listener); 67 if (!v8listener->hasExistingListenerObject()) 68 continue; 69 70 isolate->SetReference(wrapper, v8::Persistent<v8::Value>::Cast(v8listener->existingListenerObjectPersistentHandle())); 71 } 72 } 73 74 Node* V8GCController::opaqueRootForGC(Node* node, v8::Isolate*) 75 { 76 ASSERT(node); 77 // FIXME: Remove the special handling for image elements. 78 // The same special handling is in V8GCController::gcTree(). 79 // Maybe should image elements be active DOM nodes? 80 // See https://code.google.com/p/chromium/issues/detail?id=164882 81 if (node->inDocument() || (isHTMLImageElement(*node) && toHTMLImageElement(*node).hasPendingActivity())) { 82 Document& document = node->document(); 83 if (HTMLImportsController* controller = document.importsController()) 84 return controller->master(); 85 return &document; 86 } 87 88 if (node->isAttributeNode()) { 89 Node* ownerElement = toAttr(node)->ownerElement(); 90 if (!ownerElement) 91 return node; 92 node = ownerElement; 93 } 94 95 while (Node* parent = node->parentOrShadowHostOrTemplateHostNode()) 96 node = parent; 97 98 return node; 99 } 100 101 // Regarding a minor GC algorithm for DOM nodes, see this document: 102 // https://docs.google.com/a/google.com/presentation/d/1uifwVYGNYTZDoGLyCb7sXa7g49mWNMW2gaWvMN5NLk8/edit#slide=id.p 103 class MinorGCWrapperVisitor : public v8::PersistentHandleVisitor { 104 public: 105 explicit MinorGCWrapperVisitor(v8::Isolate* isolate) 106 : m_isolate(isolate) 107 { } 108 109 virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE 110 { 111 // A minor DOM GC can collect only Nodes. 112 if (classId != v8DOMNodeClassId) 113 return; 114 115 // To make minor GC cycle time bounded, we limit the number of wrappers handled 116 // by each minor GC cycle to 10000. This value was selected so that the minor 117 // GC cycle time is bounded to 20 ms in a case where the new space size 118 // is 16 MB and it is full of wrappers (which is almost the worst case). 119 // Practically speaking, as far as I crawled real web applications, 120 // the number of wrappers handled by each minor GC cycle is at most 3000. 121 // So this limit is mainly for pathological micro benchmarks. 122 const unsigned wrappersHandledByEachMinorGC = 10000; 123 if (m_nodesInNewSpace.size() >= wrappersHandledByEachMinorGC) 124 return; 125 126 // Casting to a Handle is safe here, since the Persistent doesn't get GCd 127 // during the GC prologue. 128 ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject()); 129 v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value); 130 ASSERT(V8DOMWrapper::isDOMWrapper(*wrapper)); 131 ASSERT(V8Node::hasInstance(*wrapper, m_isolate)); 132 Node* node = V8Node::toNative(*wrapper); 133 // A minor DOM GC can handle only node wrappers in the main world. 134 // Note that node->wrapper().IsEmpty() returns true for nodes that 135 // do not have wrappers in the main world. 136 if (node->containsWrapper()) { 137 const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper); 138 ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper); 139 if (activeDOMObject && activeDOMObject->hasPendingActivity()) 140 return; 141 // FIXME: Remove the special handling for image elements. 142 // The same special handling is in V8GCController::opaqueRootForGC(). 143 // Maybe should image elements be active DOM nodes? 144 // See https://code.google.com/p/chromium/issues/detail?id=164882 145 if (isHTMLImageElement(*node) && toHTMLImageElement(*node).hasPendingActivity()) 146 return; 147 // FIXME: Remove the special handling for SVG context elements. 148 if (node->isSVGElement() && toSVGElement(node)->isContextElement()) 149 return; 150 151 m_nodesInNewSpace.append(node); 152 node->markV8CollectableDuringMinorGC(); 153 } 154 } 155 156 void notifyFinished() 157 { 158 Node** nodeIterator = m_nodesInNewSpace.begin(); 159 Node** nodeIteratorEnd = m_nodesInNewSpace.end(); 160 for (; nodeIterator < nodeIteratorEnd; ++nodeIterator) { 161 Node* node = *nodeIterator; 162 ASSERT(node->containsWrapper()); 163 if (node->isV8CollectableDuringMinorGC()) { // This branch is just for performance. 164 gcTree(m_isolate, node); 165 node->clearV8CollectableDuringMinorGC(); 166 } 167 } 168 } 169 170 private: 171 bool traverseTree(Node* rootNode, Vector<Node*, initialNodeVectorSize>* partiallyDependentNodes) 172 { 173 // To make each minor GC time bounded, we might need to give up 174 // traversing at some point for a large DOM tree. That being said, 175 // I could not observe the need even in pathological test cases. 176 for (Node* node = rootNode; node; node = NodeTraversal::next(*node)) { 177 if (node->containsWrapper()) { 178 if (!node->isV8CollectableDuringMinorGC()) { 179 // This node is not in the new space of V8. This indicates that 180 // the minor GC cannot anyway judge reachability of this DOM tree. 181 // Thus we give up traversing the DOM tree. 182 return false; 183 } 184 node->clearV8CollectableDuringMinorGC(); 185 partiallyDependentNodes->append(node); 186 } 187 if (ShadowRoot* shadowRoot = node->youngestShadowRoot()) { 188 if (!traverseTree(shadowRoot, partiallyDependentNodes)) 189 return false; 190 } else if (node->isShadowRoot()) { 191 if (ShadowRoot* shadowRoot = toShadowRoot(node)->olderShadowRoot()) { 192 if (!traverseTree(shadowRoot, partiallyDependentNodes)) 193 return false; 194 } 195 } 196 // <template> has a |content| property holding a DOM fragment which we must traverse, 197 // just like we do for the shadow trees above. 198 if (isHTMLTemplateElement(*node)) { 199 if (!traverseTree(toHTMLTemplateElement(*node).content(), partiallyDependentNodes)) 200 return false; 201 } 202 203 // Document maintains the list of imported documents through HTMLImportsController. 204 if (node->isDocumentNode()) { 205 Document* document = toDocument(node); 206 HTMLImportsController* controller = document->importsController(); 207 if (controller && document == controller->master()) { 208 for (unsigned i = 0; i < controller->loaderCount(); ++i) { 209 if (!traverseTree(controller->loaderDocumentAt(i), partiallyDependentNodes)) 210 return false; 211 } 212 } 213 } 214 } 215 return true; 216 } 217 218 void gcTree(v8::Isolate* isolate, Node* startNode) 219 { 220 Vector<Node*, initialNodeVectorSize> partiallyDependentNodes; 221 222 Node* node = startNode; 223 while (Node* parent = node->parentOrShadowHostOrTemplateHostNode()) 224 node = parent; 225 226 if (!traverseTree(node, &partiallyDependentNodes)) 227 return; 228 229 // We completed the DOM tree traversal. All wrappers in the DOM tree are 230 // stored in partiallyDependentNodes and are expected to exist in the new space of V8. 231 // We report those wrappers to V8 as an object group. 232 Node** nodeIterator = partiallyDependentNodes.begin(); 233 Node** const nodeIteratorEnd = partiallyDependentNodes.end(); 234 if (nodeIterator == nodeIteratorEnd) 235 return; 236 237 Node* groupRoot = *nodeIterator; 238 for (; nodeIterator != nodeIteratorEnd; ++nodeIterator) { 239 (*nodeIterator)->markAsDependentGroup(groupRoot, isolate); 240 } 241 } 242 243 Vector<Node*> m_nodesInNewSpace; 244 v8::Isolate* m_isolate; 245 }; 246 247 class MajorGCWrapperVisitor : public v8::PersistentHandleVisitor { 248 public: 249 explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedObjectInfos) 250 : m_isolate(isolate) 251 , m_liveRootGroupIdSet(false) 252 , m_constructRetainedObjectInfos(constructRetainedObjectInfos) 253 { 254 } 255 256 virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE 257 { 258 if (classId != v8DOMNodeClassId && classId != v8DOMObjectClassId) 259 return; 260 261 // Casting to a Handle is safe here, since the Persistent doesn't get GCd 262 // during the GC prologue. 263 ASSERT((*reinterpret_cast<v8::Handle<v8::Value>*>(value))->IsObject()); 264 v8::Handle<v8::Object>* wrapper = reinterpret_cast<v8::Handle<v8::Object>*>(value); 265 ASSERT(V8DOMWrapper::isDOMWrapper(*wrapper)); 266 267 if (value->IsIndependent()) 268 return; 269 270 const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper); 271 void* object = toNative(*wrapper); 272 273 ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper); 274 if (activeDOMObject && activeDOMObject->hasPendingActivity()) 275 m_isolate->SetObjectGroupId(*value, liveRootId()); 276 277 if (classId == v8DOMNodeClassId) { 278 ASSERT(V8Node::hasInstance(*wrapper, m_isolate)); 279 Node* node = static_cast<Node*>(object); 280 if (node->hasEventListeners()) 281 addReferencesForNodeWithEventListeners(m_isolate, node, v8::Persistent<v8::Object>::Cast(*value)); 282 Node* root = V8GCController::opaqueRootForGC(node, m_isolate); 283 m_isolate->SetObjectGroupId(*value, v8::UniqueId(reinterpret_cast<intptr_t>(root))); 284 if (m_constructRetainedObjectInfos) 285 m_groupsWhichNeedRetainerInfo.append(root); 286 } else if (classId == v8DOMObjectClassId) { 287 type->visitDOMWrapper(object, v8::Persistent<v8::Object>::Cast(*value), m_isolate); 288 } else { 289 ASSERT_NOT_REACHED(); 290 } 291 } 292 293 void notifyFinished() 294 { 295 if (!m_constructRetainedObjectInfos) 296 return; 297 std::sort(m_groupsWhichNeedRetainerInfo.begin(), m_groupsWhichNeedRetainerInfo.end()); 298 Node* alreadyAdded = 0; 299 v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler(); 300 for (size_t i = 0; i < m_groupsWhichNeedRetainerInfo.size(); ++i) { 301 Node* root = m_groupsWhichNeedRetainerInfo[i]; 302 if (root != alreadyAdded) { 303 profiler->SetRetainedObjectInfo(v8::UniqueId(reinterpret_cast<intptr_t>(root)), new RetainedDOMInfo(root)); 304 alreadyAdded = root; 305 } 306 } 307 } 308 309 private: 310 v8::UniqueId liveRootId() 311 { 312 const v8::Persistent<v8::Value>& liveRoot = V8PerIsolateData::from(m_isolate)->ensureLiveRoot(); 313 const intptr_t* idPointer = reinterpret_cast<const intptr_t*>(&liveRoot); 314 v8::UniqueId id(*idPointer); 315 if (!m_liveRootGroupIdSet) { 316 m_isolate->SetObjectGroupId(liveRoot, id); 317 m_liveRootGroupIdSet = true; 318 } 319 return id; 320 } 321 322 v8::Isolate* m_isolate; 323 Vector<Node*> m_groupsWhichNeedRetainerInfo; 324 bool m_liveRootGroupIdSet; 325 bool m_constructRetainedObjectInfos; 326 }; 327 328 static unsigned long long usedHeapSize(v8::Isolate* isolate) 329 { 330 v8::HeapStatistics heapStatistics; 331 isolate->GetHeapStatistics(&heapStatistics); 332 return heapStatistics.used_heap_size(); 333 } 334 335 void V8GCController::gcPrologue(v8::GCType type, v8::GCCallbackFlags flags) 336 { 337 // FIXME: It would be nice if the GC callbacks passed the Isolate directly.... 338 v8::Isolate* isolate = v8::Isolate::GetCurrent(); 339 TRACE_EVENT_BEGIN1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "GCEvent", "usedHeapSizeBefore", usedHeapSize(isolate)); 340 if (type == v8::kGCTypeScavenge) 341 minorGCPrologue(isolate); 342 else if (type == v8::kGCTypeMarkSweepCompact) 343 majorGCPrologue(flags & v8::kGCCallbackFlagConstructRetainedObjectInfos, isolate); 344 } 345 346 void V8GCController::minorGCPrologue(v8::Isolate* isolate) 347 { 348 TRACE_EVENT_BEGIN0("v8", "minorGC"); 349 if (isMainThread()) { 350 { 351 TRACE_EVENT_SCOPED_SAMPLING_STATE("Blink", "DOMMinorGC"); 352 v8::HandleScope scope(isolate); 353 MinorGCWrapperVisitor visitor(isolate); 354 v8::V8::VisitHandlesForPartialDependence(isolate, &visitor); 355 visitor.notifyFinished(); 356 } 357 V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE()); 358 TRACE_EVENT_SET_SAMPLING_STATE("V8", "V8MinorGC"); 359 } 360 } 361 362 // Create object groups for DOM tree nodes. 363 void V8GCController::majorGCPrologue(bool constructRetainedObjectInfos, v8::Isolate* isolate) 364 { 365 v8::HandleScope scope(isolate); 366 TRACE_EVENT_BEGIN0("v8", "majorGC"); 367 if (isMainThread()) { 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); 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 } 422 423 void V8GCController::majorGCEpilogue(v8::Isolate* isolate) 424 { 425 v8::HandleScope scope(isolate); 426 427 TRACE_EVENT_END0("v8", "majorGC"); 428 if (isMainThread()) 429 TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState()); 430 } 431 432 void V8GCController::collectGarbage(v8::Isolate* isolate) 433 { 434 v8::HandleScope handleScope(isolate); 435 RefPtr<ScriptState> scriptState = ScriptState::create(v8::Context::New(isolate), DOMWrapperWorld::create()); 436 ScriptState::Scope scope(scriptState.get()); 437 V8ScriptRunner::compileAndRunInternalScript(v8String(isolate, "if (gc) gc();"), isolate); 438 scriptState->disposePerContextData(); 439 } 440 441 void V8GCController::reportDOMMemoryUsageToV8(v8::Isolate* isolate) 442 { 443 if (!isMainThread()) 444 return; 445 446 static size_t lastUsageReportedToV8 = 0; 447 448 size_t currentUsage = Partitions::currentDOMMemoryUsage(); 449 int64_t diff = static_cast<int64_t>(currentUsage) - static_cast<int64_t>(lastUsageReportedToV8); 450 isolate->AdjustAmountOfExternalAllocatedMemory(diff); 451 452 lastUsageReportedToV8 = currentUsage; 453 } 454 455 } // namespace WebCore 456