2 * Copyright (C) 2009 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
32 #include "bindings/core/v8/V8GCController.h"
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"
59 // FIXME: This should use opaque GC roots.
60 static void addReferencesForNodeWithEventListeners(v8::Isolate* isolate, Node* node, const v8::Persistent<v8::Object>& wrapper)
62 ASSERT(node->hasEventListeners());
64 EventListenerIterator iterator(node);
65 while (EventListener* listener = iterator.nextListener()) {
66 if (listener->type() != EventListener::JSEventListenerType)
68 V8AbstractEventListener* v8listener = static_cast<V8AbstractEventListener*>(listener);
69 if (!v8listener->hasExistingListenerObject())
72 isolate->SetReference(wrapper, v8::Persistent<v8::Value>::Cast(v8listener->existingListenerObjectPersistentHandle()));
76 Node* V8GCController::opaqueRootForGC(Node* node, v8::Isolate*)
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();
90 if (node->isAttributeNode()) {
91 Node* ownerElement = toAttr(node)->ownerElement();
97 while (Node* parent = node->parentOrShadowHostOrTemplateHostNode())
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 {
107 explicit MinorGCWrapperVisitor(v8::Isolate* isolate)
111 virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
113 // A minor DOM GC can collect only Nodes.
114 if (classId != WrapperTypeInfo::NodeClassId)
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)
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())
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())
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())
155 m_nodesInNewSpace.append(node);
156 node->markV8CollectableDuringMinorGC();
160 void notifyFinished()
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();
173 bool traverseTree(Node* rootNode, WillBeHeapVector<RawPtrWillBeMember<Node>, initialNodeVectorSize>* partiallyDependentNodes)
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.
186 node->clearV8CollectableDuringMinorGC();
187 partiallyDependentNodes->append(node);
189 if (ShadowRoot* shadowRoot = node->youngestShadowRoot()) {
190 if (!traverseTree(shadowRoot, partiallyDependentNodes))
192 } else if (node->isShadowRoot()) {
193 if (ShadowRoot* shadowRoot = toShadowRoot(node)->olderShadowRoot()) {
194 if (!traverseTree(shadowRoot, partiallyDependentNodes))
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))
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))
220 void gcTree(v8::Isolate* isolate, Node* startNode)
222 WillBeHeapVector<RawPtrWillBeMember<Node>, initialNodeVectorSize> partiallyDependentNodes;
224 Node* node = startNode;
225 while (Node* parent = node->parentOrShadowHostOrTemplateHostNode())
228 if (!traverseTree(node, &partiallyDependentNodes))
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())
236 Node* groupRoot = partiallyDependentNodes[0];
237 for (size_t i = 0; i < partiallyDependentNodes.size(); i++) {
238 partiallyDependentNodes[i]->markAsDependentGroup(groupRoot, isolate);
242 WillBePersistentHeapVector<RawPtrWillBeMember<Node> > m_nodesInNewSpace;
243 v8::Isolate* m_isolate;
246 class MajorGCWrapperVisitor : public v8::PersistentHandleVisitor {
248 explicit MajorGCWrapperVisitor(v8::Isolate* isolate, bool constructRetainedObjectInfos)
250 , m_liveRootGroupIdSet(false)
251 , m_constructRetainedObjectInfos(constructRetainedObjectInfos)
255 virtual void VisitPersistentHandle(v8::Persistent<v8::Value>* value, uint16_t classId) OVERRIDE
257 if (classId != WrapperTypeInfo::NodeClassId && classId != WrapperTypeInfo::ObjectClassId)
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));
266 if (value->IsIndependent())
269 const WrapperTypeInfo* type = toWrapperTypeInfo(*wrapper);
271 ActiveDOMObject* activeDOMObject = type->toActiveDOMObject(*wrapper);
272 if (activeDOMObject && activeDOMObject->hasPendingActivity())
273 m_isolate->SetObjectGroupId(*value, liveRootId());
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);
287 ASSERT_NOT_REACHED();
291 void notifyFinished()
293 if (!m_constructRetainedObjectInfos)
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));
308 v8::UniqueId liveRootId()
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;
320 v8::Isolate* m_isolate;
321 WillBePersistentHeapVector<RawPtrWillBeMember<Node> > m_groupsWhichNeedRetainerInfo;
322 bool m_liveRootGroupIdSet;
323 bool m_constructRetainedObjectInfos;
326 static unsigned long long usedHeapSize(v8::Isolate* isolate)
328 v8::HeapStatistics heapStatistics;
329 isolate->GetHeapStatistics(&heapStatistics);
330 return heapStatistics.used_heap_size();
333 void V8GCController::gcPrologue(v8::GCType type, v8::GCCallbackFlags flags)
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);
344 void V8GCController::minorGCPrologue(v8::Isolate* isolate)
346 TRACE_EVENT_BEGIN0("v8", "minorGC");
347 if (isMainThread()) {
348 ScriptForbiddenScope::enter();
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();
356 V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
357 TRACE_EVENT_SET_SAMPLING_STATE("v8", "V8MinorGC");
361 // Create object groups for DOM tree nodes.
362 void V8GCController::majorGCPrologue(bool constructRetainedObjectInfos, v8::Isolate* isolate)
364 v8::HandleScope scope(isolate);
365 TRACE_EVENT_BEGIN0("v8", "majorGC");
366 if (isMainThread()) {
367 ScriptForbiddenScope::enter();
369 TRACE_EVENT_SCOPED_SAMPLING_STATE("blink", "DOMMajorGC");
370 MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
371 v8::V8::VisitHandlesWithClassIds(&visitor);
372 visitor.notifyFinished();
374 V8PerIsolateData::from(isolate)->setPreviousSamplingState(TRACE_EVENT_GET_SAMPLING_STATE());
375 TRACE_EVENT_SET_SAMPLING_STATE("v8", "V8MajorGC");
377 MajorGCWrapperVisitor visitor(isolate, constructRetainedObjectInfos);
378 v8::V8::VisitHandlesWithClassIds(&visitor);
379 visitor.notifyFinished();
383 void V8GCController::gcEpilogue(v8::GCType type, v8::GCCallbackFlags flags)
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);
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.
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);
408 // Forces a precise GC at the end of the current event loop.
409 Heap::setForcePreciseGCForTesting();
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());
416 void V8GCController::minorGCEpilogue(v8::Isolate* isolate)
418 TRACE_EVENT_END0("v8", "minorGC");
419 if (isMainThread()) {
420 TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
421 ScriptForbiddenScope::exit();
425 void V8GCController::majorGCEpilogue(v8::Isolate* isolate)
427 v8::HandleScope scope(isolate);
429 TRACE_EVENT_END0("v8", "majorGC");
430 if (isMainThread()) {
431 TRACE_EVENT_SET_NONCONST_SAMPLING_STATE(V8PerIsolateData::from(isolate)->previousSamplingState());
432 ScriptForbiddenScope::exit();
436 void V8GCController::collectGarbage(v8::Isolate* isolate)
438 v8::HandleScope handleScope(isolate);
439 RefPtr<ScriptState> scriptState = ScriptState::create(v8::Context::New(isolate), DOMWrapperWorld::create());
440 ScriptState::Scope scope(scriptState.get());
441 V8ScriptRunner::compileAndRunInternalScript(v8String(isolate, "if (gc) gc();"), isolate);
442 scriptState->disposePerContextData();
445 void V8GCController::reportDOMMemoryUsageToV8(v8::Isolate* isolate)
450 static size_t lastUsageReportedToV8 = 0;
452 size_t currentUsage = Partitions::currentDOMMemoryUsage();
453 int64_t diff = static_cast<int64_t>(currentUsage) - static_cast<int64_t>(lastUsageReportedToV8);
454 isolate->AdjustAmountOfExternalAllocatedMemory(diff);
456 lastUsageReportedToV8 = currentUsage;