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 57namespace blink { 58 59// FIXME: This should use opaque GC roots. 60static 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 76Node* 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 105class MinorGCWrapperVisitor : public v8::PersistentHandleVisitor { 106public: 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 172private: 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 246class MajorGCWrapperVisitor : public v8::PersistentHandleVisitor { 247public: 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 307private: 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 326static unsigned long long usedHeapSize(v8::Isolate* isolate) 327{ 328 v8::HeapStatistics heapStatistics; 329 isolate->GetHeapStatistics(&heapStatistics); 330 return heapStatistics.used_heap_size(); 331} 332 333void 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 344void 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. 362void 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 383void 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 416void 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 425void 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 450void 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 459void 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