一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

node.js|vue.js|jquery|angularjs|React|json|js教程|

服務(wù)器之家 - 編程語言 - JavaScript - Node.js 抓取堆快照過程解析

Node.js 抓取堆快照過程解析

2021-10-25 21:17編程雜技theanarkh JavaScript

在 Node.js 中,我們有時(shí)候需要抓取進(jìn)程堆快照來判斷是否有內(nèi)存泄漏,本文介紹Node.js 中抓取堆快照的實(shí)現(xiàn)。

Node.js 抓取堆快照過程解析

前言:在 Node.js 中,我們有時(shí)候需要抓取進(jìn)程堆快照來判斷是否有內(nèi)存泄漏,本文介紹Node.js 中抓取堆快照的實(shí)現(xiàn)。

首先來看一下 Node.js 中如何抓取堆快照。

  1. const { Session } = require('inspector'); 
  2.  
  3. const session = new Session(); 
  4.  
  5. let chunk = ''
  6.  
  7. const cb = (result) => { 
  8.  
  9.   chunk += result.params.chunk; 
  10.  
  11. }; 
  12.  
  13.  
  14. session.on('HeapProfiler.addHeapSnapshotChunk', cb); 
  15. session.post('HeapProfiler.takeHeapSnapshot', (err, r) => { 
  16.   session.off('HeapProfiler.addHeapSnapshotChunk', cb); 
  17.     console.log(err || chunk); 
  18.  
  19. }); 

下面看一下 HeapProfiler.addHeapSnapshotChunk 命令的實(shí)現(xiàn)。

  1.       v8_crdtp::SpanFrom("takeHeapSnapshot"), 
  2.       &DomainDispatcherImpl::takeHeapSnapshot 

對應(yīng) DomainDispatcherImpl::takeHeapSnapshot 函數(shù)。

  1. void DomainDispatcherImpl::takeHeapSnapshot(const v8_crdtp::Dispatchable& dispatchable){ 
  2.     std::unique_ptr<DomainDispatcher::WeakPtr> weak = weakPtr(); 
  3.     // 抓取快照  
  4.     DispatchResponse response = m_backend->takeHeapSnapshot(std::move(params.reportProgress), std::move(params.treatGlobalObjectsAsRoots), std::move(params.captureNumericValue)); 
  5.     // 抓取完畢,響應(yīng) 
  6.     if (weak->get()) 
  7.         weak->get()->sendResponse(dispatchable.CallId(), response); 
  8.     return
  9.  

上面代碼中 m_backend 是 V8HeapProfilerAgentImpl 對象。

  1. Response V8HeapProfilerAgentImpl::takeHeapSnapshot( 
  2.     Maybe<bool> reportProgress, Maybe<bool> treatGlobalObjectsAsRoots, 
  3.     Maybe<bool> captureNumericValue) { 
  4.   v8::HeapProfiler* profiler = m_isolate->GetHeapProfiler(); 
  5.   // 抓取快照 
  6.   const v8::HeapSnapshot* snapshot = profiler->TakeHeapSnapshot( 
  7.       progress.get(), &resolver, treatGlobalObjectsAsRoots.fromMaybe(true), 
  8.       captureNumericValue.fromMaybe(false)); 
  9.   // 抓取完畢后通知調(diào)用方     
  10.   HeapSnapshotOutputStream stream(&m_frontend); 
  11.   snapshot->Serialize(&stream); 
  12.   const_cast<v8::HeapSnapshot*>(snapshot)->Delete(); 
  13.   // HeapProfiler.takeHeapSnapshot 命令結(jié)束,回調(diào)調(diào)用方 
  14.   return Response::Success(); 
  15.  

我們重點(diǎn)看一下 profiler->TakeHeapSnapshot。

  1. const HeapSnapshot* HeapProfiler::TakeHeapSnapshot( 
  2.     ActivityControl* control, ObjectNameResolver* resolver, 
  3.     bool treat_global_objects_as_roots, bool capture_numeric_value) { 
  4.   return reinterpret_cast<const HeapSnapshot*>( 
  5.       reinterpret_cast<i::HeapProfiler*>(this)->TakeSnapshot( 
  6.           control, resolver, treat_global_objects_as_roots, 
  7.           capture_numeric_value)); 
  8.  

繼續(xù)看真正的 TakeSnapshot。

  1. HeapSnapshot* HeapProfiler::TakeSnapshot( 
  2.     v8::ActivityControl* control, 
  3.     v8::HeapProfiler::ObjectNameResolver* resolver, 
  4.     bool treat_global_objects_as_roots, bool capture_numeric_value) { 
  5.   is_taking_snapshot_ = true
  6.   HeapSnapshot* result = new HeapSnapshot(this, treat_global_objects_as_roots, 
  7.                                           capture_numeric_value); 
  8.   { 
  9.     HeapSnapshotGenerator generator(result, control, resolver, heap()); 
  10.     if (!generator.GenerateSnapshot()) { 
  11.       delete result; 
  12.       result = nullptr; 
  13.     } else { 
  14.       snapshots_.emplace_back(result); 
  15.     } 
  16.   } 
  17.   return result; 
  18.  

我們看到新建了一個(gè) HeapSnapshot 對象,然后通過 HeapSnapshotGenerator 對象的 GenerateSnapshot 抓取快照。看一下 GenerateSnapshot。

  1. bool HeapSnapshotGenerator::GenerateSnapshot() { 
  2.   Isolate* isolate = Isolate::FromHeap(heap_); 
  3.   base::Optional<HandleScope> handle_scope(base::in_place, isolate); 
  4.   v8_heap_explorer_.CollectGlobalObjectsTags(); 
  5.   // 抓取前先回收不用內(nèi)存,保證看到的是存活的對象,否則影響內(nèi)存泄漏的分析 
  6.   heap_->CollectAllAvailableGarbage(GarbageCollectionReason::kHeapProfiler); 
  7.   // 收集內(nèi)存信息 
  8.   snapshot_->AddSyntheticRootEntries(); 
  9.   FillReferences(); 
  10.   snapshot_->FillChildren(); 
  11.   return true
  12.  

GenerateSnapshot 的邏輯是首先進(jìn)行GC 回收不用的內(nèi)存,然后收集 GC 后的內(nèi)存信息到 HeapSnapshot 對象。接著看收集完后的邏輯。

  1. HeapSnapshotOutputStream stream(&m_frontend); 
  2. snapshot->Serialize(&stream); 

HeapSnapshotOutputStream 是用于通知調(diào)用方收集的數(shù)據(jù)(通過 m_frontend)。

  1. explicit HeapSnapshotOutputStream(protocol::HeapProfiler::Frontend* frontend) 
  2.       : m_frontend(frontend) {} 
  3.   void EndOfStream() override {} 
  4.   int GetChunkSize() override { return 102400; } 
  5.   WriteResult WriteAsciiChunk(char* data, int size) override { 
  6.     m_frontend->addHeapSnapshotChunk(String16(data, size)); 
  7.     m_frontend->flush(); 
  8.     return kContinue; 

HeapSnapshotOutputStream 通過 WriteAsciiChunk 告訴調(diào)用方收集的數(shù)據(jù),但是目前我們還沒有數(shù)據(jù)源,下面看看數(shù)據(jù)源怎么來的。

  1. snapshot->Serialize(&stream); 

看一下 Serialize。

  1. void HeapSnapshot::Serialize(OutputStream* stream, 
  2.                              HeapSnapshot::SerializationFormat format) const { 
  3.   i::HeapSnapshotJSONSerializer serializer(ToInternal(this)); 
  4.   serializer.Serialize(stream); 
  5.  

最終調(diào)了 HeapSnapshotJSONSerializer 的 Serialize。

  1. void HeapSnapshotJSONSerializer::Serialize(v8::OutputStream* stream) { 
  2.   // 寫者 
  3.   writer_ = new OutputStreamWriter(stream); 
  4.   // 開始寫 
  5.   SerializeImpl(); 
  6.  

我們看一下 SerializeImpl。

  1. void HeapSnapshotJSONSerializer::SerializeImpl() { 
  2.   DCHECK_EQ(0, snapshot_->root()->index()); 
  3.   writer_->AddCharacter('{'); 
  4.   writer_->AddString("\"snapshot\":{"); 
  5.   SerializeSnapshot(); 
  6.   if (writer_->aborted()) return
  7.   writer_->AddString("},\n"); 
  8.   writer_->AddString("\"nodes\":["); 
  9.   SerializeNodes(); 
  10.   if (writer_->aborted()) return
  11.   writer_->AddString("],\n"); 
  12.   writer_->AddString("\"edges\":["); 
  13.   SerializeEdges(); 
  14.   if (writer_->aborted()) return
  15.   writer_->AddString("],\n"); 
  16.  
  17.   writer_->AddString("\"trace_function_infos\":["); 
  18.   SerializeTraceNodeInfos(); 
  19.   if (writer_->aborted()) return
  20.   writer_->AddString("],\n"); 
  21.   writer_->AddString("\"trace_tree\":["); 
  22.   SerializeTraceTree(); 
  23.   if (writer_->aborted()) return
  24.   writer_->AddString("],\n"); 
  25.  
  26.   writer_->AddString("\"samples\":["); 
  27.   SerializeSamples(); 
  28.   if (writer_->aborted()) return
  29.   writer_->AddString("],\n"); 
  30.  
  31.   writer_->AddString("\"locations\":["); 
  32.   SerializeLocations(); 
  33.   if (writer_->aborted()) return
  34.   writer_->AddString("],\n"); 
  35.  
  36.   writer_->AddString("\"strings\":["); 
  37.   SerializeStrings(); 
  38.   if (writer_->aborted()) return
  39.   writer_->AddCharacter(']'); 
  40.   writer_->AddCharacter('}'); 
  41.   writer_->Finalize(); 
  42.  

SerializeImpl 函數(shù)的邏輯就是把快照數(shù)據(jù)通過 OutputStreamWriter 對象 writer_ 寫到 writer_ 持有的 stream 中。寫的數(shù)據(jù)有很多種類型,這里以 AddCharacter 為例。

  1. void AddCharacter(char c) { 
  2.   chunk_[chunk_pos_++] = c; 
  3.   MaybeWriteChunk(); 
  4.  

每次寫的時(shí)候都會(huì)判斷是不達(dá)到閾值,是的話則先推給調(diào)用方。看一下 MaybeWriteChunk。

  1. void MaybeWriteChunk() { 
  2.   if (chunk_pos_ == chunk_size_) { 
  3.     WriteChunk(); 
  4.   } 
  5.  
  6.  
  7.  
  8.  
  9. void WriteChunk() { 
  10.  
  11.   // stream 控制是否還需要寫入,通過 kAbort 和 kContinue 
  12.   if (stream_->WriteAsciiChunk(chunk_.begin(), chunk_pos_) == 
  13.       v8::OutputStream::kAbort) 
  14.     aborted_ = true
  15.   chunk_pos_ = 0; 
  16.  

我們看到最終通過 stream 的 WriteAsciiChunk 寫到 stream 中。

  1. WriteResult WriteAsciiChunk(char* data, int size) override { 
  2.   m_frontend->addHeapSnapshotChunk(String16(data, size)); 
  3.   m_frontend->flush(); 
  4.   return kContinue; 
  5.  

WriteAsciiChunk 調(diào)用 addHeapSnapshotChunk 通知調(diào)用方。

  1. void Frontend::addHeapSnapshotChunk(const String& chunk){ 
  2.     v8_crdtp::ObjectSerializer serializer; 
  3.     serializer.AddField(v8_crdtp::MakeSpan("chunk"), chunk); 
  4.     frontend_channel_->SendProtocolNotification(v8_crdtp::CreateNotification("HeapProfiler.addHeapSnapshotChunk", serializer.Finish())); 
  5.  

觸發(fā) HeapProfiler.addHeapSnapshotChunk 事件,并傳入快照的數(shù)據(jù),最終觸發(fā) JS 層的事件。再看一下文章開頭的代碼。

  1. let chunk = ''
  2.  
  3. const cb = (result) => { 
  4.  
  5.   chunk += result.params.chunk; 
  6.  
  7. }; 
  8.  
  9.  
  10.  
  11. session.on('HeapProfiler.addHeapSnapshotChunk', cb); 
  12. session.post('HeapProfiler.takeHeapSnapshot', (err, r) => { 
  13.   session.off('HeapProfiler.addHeapSnapshotChunk', cb); 
  14.     console.log(err || chunk); 
  15.  
  16. }); 

這個(gè)過程是否清晰了很多。從過程中也看到,抓取快照雖然傳入了回調(diào),但是其實(shí)是以同步的方式執(zhí)行的,因?yàn)樘峤?HeapProfiler.takeHeapSnapshot 命令后,V8 就開始收集內(nèi)存,然后不斷觸發(fā)

HeapProfiler.addHeapSnapshotChunk 事件,直到堆數(shù)據(jù)寫完,然后執(zhí)行 JS 回調(diào)。

總結(jié):整個(gè)過程不算復(fù)雜,因?yàn)槲覀儧]有涉及到堆內(nèi)存管理那部分,V8 Inspector 提供了很多命令,有時(shí)間的話后續(xù)再分析其他的命令。

原文鏈接:https://mp.weixin.qq.com/s/MNSpplVuD4gJG3we2_GgIQ

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 91精品大神国产在线播放 | 日本高清在线观看天码888 | 国产成人在线小视频 | 91青青视频 | 久久精品国产免费播高清无卡 | 欧美日韩国产中文字幕 | 日本在线观看www鲁啊鲁视频 | 911亚洲精品国内自产 | 毛片免 | 亚洲欧美专区 | 新版孕妇bbwbbwbbw | 99这里只有精品在线 | 91普通话国产对白在线 | 国产午夜精品久久久久小说 | leslessexvideos日本 | 思思玖玖| 日韩亚洲人成在线综合 | 久久亚洲国产成人影院 | 公园吃女人奶野战视频 | 午夜人妻理论片天堂影院 | 暖暖视频免费观看视频中国.韩剧 | 精品日韩欧美一区二区三区 | 亚洲精品在线免费看 | 久久久久久久伊人电影 | aaa一级毛片免费 | 国产在线精品香蕉综合网一区 | 国产精品亚洲一区二区 | 波多野给衣一区二区三区 | haodiaocao的视频这里看 | 91赵邦贺| sss在线播放 | 日本天堂影院在线播放 | 92国产福利久久青青草原 | 亚洲精品国产A久久久久久 亚洲精品福利一区二区在线观看 | 性关系视频网站 | 男人狂躁女人下面的视频免费 | 98成人网| 美女漫画网 | japanesexxxx日本妞| 国产成人www免费人成看片 | 国产精品男人的天堂 |