最后的结论很简单,是我绑定v8 function的时候没释放。但查找问题的过程比较艰难,因为
v8的代码实在太难读了。
下面先大概了解下v8的垃圾回收机制。
v8\src\global-handles.cc里有个GlobalHandles类,管理了所有使用v8::Persistent<XXX>形式声明的永久性
v8变量。说永久性的意思是,除非自己主动调用v8::Persistent::reset,不然就一直占稳不释放了。
但有个例外,就是v8::Persistent::SetWeak形式设置了释放回调,或者叫弱回调(这里有点存疑)。
看段堆栈:
node.dll!v8::internal::GlobalHandles::NodeBlock::IncreaseUses C++ node.dll!v8::internal::GlobalHandles::Node::IncreaseBlockUses C++ node.dll!v8::internal::GlobalHandles::Node::Acquire C++ node.dll!v8::internal::GlobalHandles::Create C++ node.dll!v8::V8::GlobalizeReference C++ node.dll!v8::PersistentBase<v8::ObjectTemplate>::New C++ > node.dll!v8::PersistentBase<v8::ObjectTemplate>::Reset<v8::ObjectTemplate> C++ node.dll!blink::V8Window::getShadowObjectTemplate C++ node.dll!blink::WindowProxy::createContext C++ node.dll!blink::WindowProxy::initialize C++ node.dll!blink::WindowProxy::initializeIfNeeded C++ node.dll!blink::ScriptController::windowProxy C++ node.dll!blink::LocalFrame::windowProxy C++ node.dll!blink::toV8ContextEvenIfDetached C++ node.dll!blink::ScriptState::forWorld C++ node.dll!blink::ScriptState::forMainWorld C++ node.dll!blink::WebLocalFrameImpl::mainWorldScriptContext C++ node.dll!wke::CWebView::globalExec C++ node.dll!wkeGlobalExec C++ wkexe.exe!handleDocumentReady C++ node.dll!wke::CWebWindow::_onDocumentReady C++ node.dll!wke::CWebWindow::_staticOnDocumentReady C++ node.dll!content::WebFrameClientImpl::didFinishDocumentLoad C++ node.dll!blink::FrameLoaderClientImpl::dispatchDidFinishDocumentLoad C++ node.dll!blink::FrameLoader::finishedParsing C++ node.dll!blink::Document::finishedParsing C++ node.dll!blink::HTMLConstructionSite::finishedParsing C++ node.dll!blink::HTMLTreeBuilder::finished C++ node.dll!blink::HTMLDocumentParser::end C++ node.dll!blink::HTMLDocumentParser::attemptToRunDeferredScriptsAndEnd C++ node.dll!blink::HTMLDocumentParser::prepareToStopParsing C++ node.dll!blink::HTMLDocumentParser::processParsedChunkFromBackgroundParser C++ node.dll!blink::HTMLDocumentParser::pumpPendingSpeculations C++ node.dll!blink::HTMLDocumentParser::resumeParsingAfterYield C++ node.dll!blink::HTMLParserScheduler::continueParsing C++ node.dll!WTF::FunctionWrapper node.dll!WTF::PartBoundFunctionImpl node.dll!blink::CancellableTaskFactory::CancellableTask::run C++ node.dll!content::WebTimerBase::fired C++ node.dll!content::WebThreadImpl::schedulerTasks C++ node.dll!content::WebThreadImpl::fire C++ node.dll!wkeWake C++ wkexe.exe!RunMessageLoop C++
这显示了如何把永久对象放到GlobalHandles里。
当垃圾回收的时刻到来时,
> node.dll!v8::internal::GlobalHandles::IdentifyWeakHandles C++ node.dll!v8::internal::MarkCompactCollector::MarkLiveObjects C++ node.dll!v8::internal::MarkCompactCollector::CollectGarbage C++ node.dll!v8::internal::Heap::MarkCompact C++ node.dll!v8::internal::Heap::PerformGarbageCollection C++ node.dll!v8::internal::Heap::CollectGarbage C++ node.dll!v8::internal::Heap::CollectAllAvailableGarbage C++ node.dll!v8::Isolate::LowMemoryNotification C++ node.dll!content::BlinkPlatformImpl::doGarbageCollected C++ node.dll!content::BlinkPlatformImpl::garbageCollectedTimer C++ node.dll!blink::Timer<content::BlinkPlatformImpl>::fired C++
可以看到GlobalHandles::IdentifyWeakHandles这里会遍历所有v8::Persistent永久化对象。
当设置了弱回调的时候,这些永久化对象就靠v8自己的垃圾处理机制了。此时就有个问题,v8如何知道这个对象无人引用了呢?
下面看几个v8的数据结构:
class GlobalHandles::Node
class Object
class HeapObject
class Marking
要读懂垃圾回收机制,连得理清楚这几个对象的关系。
实际上是这样:
Object相对于对应js里的Object。不过注意下,v8分外部导出到头文件的Object和内部Object,这两货其实是一样的,只是
为了工程上的严谨性。
这个GlobalHandles::Node代表每个存在GlobalHandles里的对象,如上面那个持久化东西。Object也被存在这个node里。
但这里有个特别要强调的是,一个object可以放在多个 node。这点对后来解决内存泄漏比较关键。
每个object,其实是个以HeapObject开头的内存。这个HeapObject就是为了方便内存管理而设计的。
HeapObject的头部,通过一系列位运算,地址运算,得到了Marking对象。这过程我没怎么看懂,因为都是类似
static_cast<uint32_t>(addr - this->address()) >> kPointerSizeLog2;
这种风格的运算…实在不想搞明白算出真实Marking地址了。
总之得到Marking地址后,就可以通过Marking::IsBlack,Marking::IsWhite来判断Marking的状态(或者叫颜色)了。
black表示内存被占用,white表示内存没人占用。
(插一句,其实准确的说,应该是Marking里的cell类来记录这个颜色,不同object最后是在cell里标记的)
那问题来了,什么时候会去设置这些颜色呢?
最终是通过Marking::WhiteToBlack、Marking::MarkBlack来标记的。下个断点,
> node.dll!v8::internal::Marking::WhiteToBlack C++ node.dll!v8::internal::MarkCompactCollector::MarkObject C++ node.dll!v8::internal::MarkCompactMarkingVisitor::MarkObjectByPointer C++ node.dll!v8::internal::MarkCompactMarkingVisitor::VisitPointers C++ node.dll!v8::internal::StaticMarkingVisitor<v8::internal::MarkCompactMarkingVisitor>::VisitMap C++ node.dll!v8::internal::StaticMarkingVisitor<v8::internal::MarkCompactMarkingVisitor>::IterateBody C++ node.dll!v8::internal::MarkCompactCollector::EmptyMarkingDeque C++ node.dll!v8::internal::MarkCompactCollector::ProcessMarkingDeque C++ node.dll!v8::internal::MarkCompactCollector::PrepareForCodeFlushing C++ node.dll!v8::internal::MarkCompactCollector::MarkLiveObjects C++ node.dll!v8::internal::MarkCompactCollector::CollectGarbage C++ node.dll!v8::internal::Heap::MarkCompact C++ node.dll!v8::internal::Heap::PerformGarbageCollection C++ node.dll!v8::internal::Heap::CollectGarbage C++ node.dll!v8::internal::Heap::CollectAllAvailableGarbage C++ node.dll!v8::Isolate::LowMemoryNotification C++ node.dll!content::BlinkPlatformImpl::doGarbageCollected C++
其实关键就是这句
v8::internal::MarkCompactMarkingVisitor::VisitPointers
v8构造了一个图结构,通过不停遍历里面的节点,如果访问到的节点,就标记为block。
那回到最上面的问题,
一个object可以放在多个 node。如果一个node没人引用了,会被标记为white。这是下面堆栈做的:
> node.dll!v8::internal::Sweep<0,0,1,0> C++ node.dll!v8::internal::MarkCompactCollector::SweepSpace C++ node.dll!v8::internal::MarkCompactCollector::SweepSpaces C++ node.dll!v8::internal::MarkCompactCollector::CollectGarbage C++ node.dll!v8::internal::Heap::MarkCompact C++ node.dll!v8::internal::Heap::PerformGarbageCollection C++ node.dll!v8::internal::Heap::CollectGarbage C++ node.dll!v8::internal::Heap::CollectAllAvailableGarbage C++ node.dll!v8::Isolate::LowMemoryNotification C++
但如果一个object放在两个node呢?比如有两个v8::Persistent<Objcet>持有了这个object,那就是一个object放在两个node里。
当第一个node在v8::internal::Sweep<0,0,1,0>里被标记white时,
下面堆栈可能又因为另个node在引用这个object,而导致同样的Marking又被标记成black:
> node.dll!v8::internal::MarkBit::Set() node.dll!v8::internal::Marking::WhiteToBlack node.dll!v8::internal::MarkCompactCollector::SetMark node.dll!v8::internal::RootMarkingVisitor::MarkObjectByPointer node.dll!v8::internal::RootMarkingVisitor::VisitPointer node.dll!v8::internal::GlobalHandles::IterateStrongRoots node.dll!v8::internal::Heap::IterateStrongRoots node.dll!v8::internal::MarkCompactCollector::MarkRoots node.dll!v8::internal::MarkCompactCollector::MarkLiveObjects node.dll!v8::internal::MarkCompactCollector::CollectGarbage node.dll!v8::internal::Heap::MarkCompact node.dll!v8::internal::Heap::PerformGarbageCollection node.dll!v8::internal::Heap::CollectGarbage node.dll!v8::internal::Heap::CollectAllAvailableGarbage node.dll!v8::Isolate::LowMemoryNotification node.dll!content::BlinkPlatformImpl::doGarbageCollected node.dll!content::BlinkPlatformImpl::garbageCollectedTimer node.dll!blink::Timer最后跟踪到这里时,才发现miniblink是因为同个object被两个node引用导致没释放。问题解决。