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 "V8GCController.h"
34 #include "ActiveDOMObject.h"
36 #include "DOMDataStore.h"
38 #include "HTMLImageElement.h"
39 #include "HTMLNames.h"
40 #include "MessagePort.h"
41 #include "PlatformBridge.h"
42 #include "SVGElement.h"
43 #include "V8Binding.h"
45 #include "V8MessagePort.h"
47 #include "WrapperTypeInfo.h"
52 #include <wtf/HashMap.h>
53 #include <wtf/StdLibExtras.h>
54 #include <wtf/UnusedParam.h>
59 // Keeps track of global handles created (not JS wrappers
60 // of DOM objects). Often these global handles are source
63 // If you want to let a C++ object hold a persistent handle
64 // to a JS object, you should register the handle here to
65 // keep track of leaks.
67 // When creating a persistent handle, call:
70 // V8GCController::registerGlobalHandle(type, host, handle);
73 // When releasing the handle, call:
76 // V8GCController::unregisterGlobalHandle(type, host, handle);
79 typedef HashMap<v8::Value*, GlobalHandleInfo*> GlobalHandleMap;
81 static GlobalHandleMap& globalHandleMap()
83 DEFINE_STATIC_LOCAL(GlobalHandleMap, staticGlobalHandleMap, ());
84 return staticGlobalHandleMap;
87 // The function is the place to set the break point to inspect
88 // live global handles. Leaks are often come from leaked global handles.
89 static void enumerateGlobalHandles()
91 for (GlobalHandleMap::iterator it = globalHandleMap().begin(), end = globalHandleMap().end(); it != end; ++it) {
92 GlobalHandleInfo* info = it->second;
94 v8::Value* handle = it->first;
99 void V8GCController::registerGlobalHandle(GlobalHandleType type, void* host, v8::Persistent<v8::Value> handle)
101 ASSERT(!globalHandleMap().contains(*handle));
102 globalHandleMap().set(*handle, new GlobalHandleInfo(host, type));
105 void V8GCController::unregisterGlobalHandle(void* host, v8::Persistent<v8::Value> handle)
107 ASSERT(globalHandleMap().contains(*handle));
108 GlobalHandleInfo* info = globalHandleMap().take(*handle);
109 ASSERT(info->m_host == host);
112 #endif // ifndef NDEBUG
114 typedef HashMap<Node*, v8::Object*> DOMNodeMap;
115 typedef HashMap<void*, v8::Object*> DOMObjectMap;
119 static void enumerateDOMObjectMap(DOMObjectMap& wrapperMap)
121 for (DOMObjectMap::iterator it = wrapperMap.begin(), end = wrapperMap.end(); it != end; ++it) {
122 v8::Persistent<v8::Object> wrapper(it->second);
123 WrapperTypeInfo* type = V8DOMWrapper::domWrapperType(wrapper);
124 void* object = it->first;
126 UNUSED_PARAM(object);
130 class DOMObjectVisitor : public DOMWrapperMap<void>::Visitor {
132 void visitDOMWrapper(void* object, v8::Persistent<v8::Object> wrapper)
134 WrapperTypeInfo* type = V8DOMWrapper::domWrapperType(wrapper);
136 UNUSED_PARAM(object);
140 class EnsureWeakDOMNodeVisitor : public DOMWrapperMap<Node>::Visitor {
142 void visitDOMWrapper(Node* object, v8::Persistent<v8::Object> wrapper)
144 UNUSED_PARAM(object);
145 ASSERT(wrapper.IsWeak());
151 // A map from a DOM node to its JS wrapper, the wrapper
152 // is kept as a strong reference to survive GCs.
153 static DOMObjectMap& gcProtectedMap()
155 DEFINE_STATIC_LOCAL(DOMObjectMap, staticGcProtectedMap, ());
156 return staticGcProtectedMap;
159 void V8GCController::gcProtect(void* domObject)
163 if (gcProtectedMap().contains(domObject))
165 if (!getDOMObjectMap().contains(domObject))
168 // Create a new (strong) persistent handle for the object.
169 v8::Persistent<v8::Object> wrapper = getDOMObjectMap().get(domObject);
170 if (wrapper.IsEmpty())
173 gcProtectedMap().set(domObject, *v8::Persistent<v8::Object>::New(wrapper));
176 void V8GCController::gcUnprotect(void* domObject)
180 if (!gcProtectedMap().contains(domObject))
183 // Dispose the strong reference.
184 v8::Persistent<v8::Object> wrapper(gcProtectedMap().take(domObject));
188 class GCPrologueVisitor : public DOMWrapperMap<void>::Visitor {
190 void visitDOMWrapper(void* object, v8::Persistent<v8::Object> wrapper)
192 WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
194 // Additional handling of message port ensuring that entangled ports also
195 // have their wrappers entangled. This should ideally be handled when the
196 // ports are actually entangled in MessagePort::entangle, but to avoid
197 // forking MessagePort.* this is postponed to GC time. Having this postponed
198 // has the drawback that the wrappers are "entangled/unentangled" for each
199 // GC even though their entaglement most likely is still the same.
200 if (V8MessagePort::info.equals(typeInfo)) {
201 // Mark each port as in-use if it's entangled. For simplicity's sake, we assume all ports are remotely entangled,
202 // since the Chromium port implementation can't tell the difference.
203 MessagePort* port1 = static_cast<MessagePort*>(object);
204 if (port1->isEntangled() || port1->hasPendingActivity())
207 ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
208 if (activeDOMObject && activeDOMObject->hasPendingActivity())
216 GrouperItem(uintptr_t groupId, Node* node, v8::Persistent<v8::Object> wrapper)
223 uintptr_t groupId() const { return m_groupId; }
224 Node* node() const { return m_node; }
225 v8::Persistent<v8::Object> wrapper() const { return m_wrapper; }
230 v8::Persistent<v8::Object> m_wrapper;
233 bool operator<(const GrouperItem& a, const GrouperItem& b)
235 return a.groupId() < b.groupId();
238 typedef Vector<GrouperItem> GrouperList;
240 class ObjectGrouperVisitor : public DOMWrapperMap<Node>::Visitor {
242 ObjectGrouperVisitor()
244 // FIXME: grouper_.reserveCapacity(node_map.size()); ?
247 void visitDOMWrapper(Node* node, v8::Persistent<v8::Object> wrapper)
250 // If the node is in document, put it in the ownerDocument's object group.
252 // If an image element was created by JavaScript "new Image",
253 // it is not in a document. However, if the load event has not
254 // been fired (still onloading), it is treated as in the document.
256 // Otherwise, the node is put in an object group identified by the root
257 // element of the tree to which it belongs.
259 if (node->inDocument() || (node->hasTagName(HTMLNames::imgTag) && !static_cast<HTMLImageElement*>(node)->haveFiredLoadEvent()))
260 groupId = reinterpret_cast<uintptr_t>(node->document());
263 if (node->isAttributeNode()) {
264 root = static_cast<Attr*>(node)->ownerElement();
265 // If the attribute has no element, no need to put it in the group,
266 // because it'll always be a group of 1.
270 while (root->parentNode())
271 root = root->parentNode();
273 // If the node is alone in its DOM tree (doesn't have a parent or any
274 // children) then the group will be filtered out later anyway.
275 if (root == node && !node->hasChildNodes() && !node->hasAttributes())
278 groupId = reinterpret_cast<uintptr_t>(root);
280 m_grouper.append(GrouperItem(groupId, node, wrapper));
285 // Group by sorting by the group id.
286 std::sort(m_grouper.begin(), m_grouper.end());
288 // FIXME Should probably work in iterators here, but indexes were easier for my simple mind.
289 for (size_t i = 0; i < m_grouper.size(); ) {
290 // Seek to the next key (or the end of the list).
291 size_t nextKeyIndex = m_grouper.size();
292 for (size_t j = i; j < m_grouper.size(); ++j) {
293 if (m_grouper[i].groupId() != m_grouper[j].groupId()) {
299 ASSERT(nextKeyIndex > i);
301 // We only care about a group if it has more than one object. If it only
302 // has one object, it has nothing else that needs to be kept alive.
303 if (nextKeyIndex - i <= 1) {
308 Vector<v8::Persistent<v8::Value> > group;
309 group.reserveCapacity(nextKeyIndex - i);
310 for (; i < nextKeyIndex; ++i) {
311 v8::Persistent<v8::Value> wrapper = m_grouper[i].wrapper();
312 if (!wrapper.IsEmpty())
313 group.append(wrapper);
314 /* FIXME: Re-enabled this code to avoid GCing these wrappers!
315 Currently this depends on looking up the wrapper
316 during a GC, but we don't know which isolated world
317 we're in, so it's unclear which map to look in...
319 // If the node is styled and there is a wrapper for the inline
320 // style declaration, we need to keep that style declaration
321 // wrapper alive as well, so we add it to the object group.
322 if (node->isStyledElement()) {
323 StyledElement* element = reinterpret_cast<StyledElement*>(node);
324 CSSStyleDeclaration* style = element->inlineStyleDecl();
326 wrapper = getDOMObjectMap().get(style);
327 if (!wrapper.IsEmpty())
328 group.append(wrapper);
334 if (group.size() > 1)
335 v8::V8::AddObjectGroup(&group[0], group.size());
337 ASSERT(i == nextKeyIndex);
342 GrouperList m_grouper;
345 // Create object groups for DOM tree nodes.
346 void V8GCController::gcPrologue()
348 v8::HandleScope scope;
351 DOMObjectVisitor domObjectVisitor;
352 visitDOMObjectsInCurrentThread(&domObjectVisitor);
355 // Run through all objects with possible pending activity making their
356 // wrappers non weak if there is pending activity.
357 GCPrologueVisitor prologueVisitor;
358 visitActiveDOMObjectsInCurrentThread(&prologueVisitor);
360 // Create object groups.
361 ObjectGrouperVisitor objectGrouperVisitor;
362 visitDOMNodesInCurrentThread(&objectGrouperVisitor);
363 objectGrouperVisitor.applyGrouping();
365 // Clean single element cache for string conversions.
367 lastV8String.Clear();
370 class GCEpilogueVisitor : public DOMWrapperMap<void>::Visitor {
372 void visitDOMWrapper(void* object, v8::Persistent<v8::Object> wrapper)
374 WrapperTypeInfo* typeInfo = V8DOMWrapper::domWrapperType(wrapper);
375 if (V8MessagePort::info.equals(typeInfo)) {
376 MessagePort* port1 = static_cast<MessagePort*>(object);
377 // We marked this port as reachable in GCPrologueVisitor. Undo this now since the
378 // port could be not reachable in the future if it gets disentangled (and also
379 // GCPrologueVisitor expects to see all handles marked as weak).
380 if ((!wrapper.IsWeak() && !wrapper.IsNearDeath()) || port1->hasPendingActivity())
381 wrapper.MakeWeak(port1, &DOMDataStore::weakActiveDOMObjectCallback);
383 ActiveDOMObject* activeDOMObject = typeInfo->toActiveDOMObject(wrapper);
384 if (activeDOMObject && activeDOMObject->hasPendingActivity()) {
385 ASSERT(!wrapper.IsWeak());
386 // NOTE: To re-enable weak status of the active object we use
387 // |object| from the map and not |activeDOMObject|. The latter
388 // may be a different pointer (in case ActiveDOMObject is not
389 // the main base class of the object's class) and pointer
390 // identity is required by DOM map functions.
391 wrapper.MakeWeak(object, &DOMDataStore::weakActiveDOMObjectCallback);
397 int V8GCController::workingSetEstimateMB = 0;
401 int getMemoryUsageInMB()
403 #if PLATFORM(CHROMIUM) || PLATFORM(ANDROID)
404 return PlatformBridge::memoryUsageMB();
410 int getActualMemoryUsageInMB()
412 #if PLATFORM(CHROMIUM) || PLATFORM(ANDROID)
413 return PlatformBridge::actualMemoryUsageMB();
419 } // anonymous namespace
421 void V8GCController::gcEpilogue()
423 v8::HandleScope scope;
425 // Run through all objects with pending activity making their wrappers weak
427 GCEpilogueVisitor epilogueVisitor;
428 visitActiveDOMObjectsInCurrentThread(&epilogueVisitor);
430 workingSetEstimateMB = getActualMemoryUsageInMB();
433 // Check all survivals are weak.
434 DOMObjectVisitor domObjectVisitor;
435 visitDOMObjectsInCurrentThread(&domObjectVisitor);
437 EnsureWeakDOMNodeVisitor weakDOMNodeVisitor;
438 visitDOMNodesInCurrentThread(&weakDOMNodeVisitor);
440 enumerateDOMObjectMap(gcProtectedMap());
441 enumerateGlobalHandles();
445 void V8GCController::checkMemoryUsage()
447 #if PLATFORM(CHROMIUM)
448 // These values are appropriate for Chromium only.
449 const int lowUsageMB = 256; // If memory usage is below this threshold, do not bother forcing GC.
450 const int highUsageMB = 1024; // If memory usage is above this threshold, force GC more aggresively.
451 const int highUsageDeltaMB = 128; // Delta of memory usage growth (vs. last workingSetEstimateMB) to force GC when memory usage is high.
452 #elif PLATFORM(ANDROID)
453 // Query the PlatformBridge for memory thresholds as these vary device to device.
454 static const int lowUsageMB = PlatformBridge::lowMemoryUsageMB();
455 static const int highUsageMB = PlatformBridge::highMemoryUsageMB();
456 // We use a delta of -1 to ensure that when we are in a low memory situation we always trigger a GC.
457 static const int highUsageDeltaMB = -1;
462 int memoryUsageMB = getMemoryUsageInMB();
463 if ((memoryUsageMB > lowUsageMB && memoryUsageMB > 2 * workingSetEstimateMB) || (memoryUsageMB > highUsageMB && memoryUsageMB > workingSetEstimateMB + highUsageDeltaMB))
464 v8::V8::LowMemoryNotification();
468 } // namespace WebCore