修复miniblink一处内存泄漏的bug

时间:2020-12-06 19:24:24

最后的结论很简单,是我绑定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引用导致没释放。问题解决。