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

服務器之家:專注于服務器技術及軟件下載分享
分類導航

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

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

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

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

在 Node.js 中,我們有時候需要抓取進程堆快照來判斷是否有內存泄漏,本文介紹Node.js 中抓取堆快照的實現。

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

前言:在 Node.js 中,我們有時候需要抓取進程堆快照來判斷是否有內存泄漏,本文介紹Node.js 中抓取堆快照的實現。

首先來看一下 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 命令的實現。

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

對應 DomainDispatcherImpl::takeHeapSnapshot 函數。

  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.     // 抓取完畢,響應 
  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.   // 抓取完畢后通知調用方     
  10.   HeapSnapshotOutputStream stream(&m_frontend); 
  11.   snapshot->Serialize(&stream); 
  12.   const_cast<v8::HeapSnapshot*>(snapshot)->Delete(); 
  13.   // HeapProfiler.takeHeapSnapshot 命令結束,回調調用方 
  14.   return Response::Success(); 
  15.  

我們重點看一下 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.  

繼續看真正的 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.  

我們看到新建了一個 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.   // 抓取前先回收不用內存,保證看到的是存活的對象,否則影響內存泄漏的分析 
  6.   heap_->CollectAllAvailableGarbage(GarbageCollectionReason::kHeapProfiler); 
  7.   // 收集內存信息 
  8.   snapshot_->AddSyntheticRootEntries(); 
  9.   FillReferences(); 
  10.   snapshot_->FillChildren(); 
  11.   return true
  12.  

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

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

HeapSnapshotOutputStream 是用于通知調用方收集的數據(通過 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 告訴調用方收集的數據,但是目前我們還沒有數據源,下面看看數據源怎么來的。

  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.  

最終調了 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 函數的邏輯就是把快照數據通過 OutputStreamWriter 對象 writer_ 寫到 writer_ 持有的 stream 中。寫的數據有很多種類型,這里以 AddCharacter 為例。

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

每次寫的時候都會判斷是不達到閾值,是的話則先推給調用方。看一下 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 調用 addHeapSnapshotChunk 通知調用方。

  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.  

觸發 HeapProfiler.addHeapSnapshotChunk 事件,并傳入快照的數據,最終觸發 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. }); 

這個過程是否清晰了很多。從過程中也看到,抓取快照雖然傳入了回調,但是其實是以同步的方式執行的,因為提交 HeapProfiler.takeHeapSnapshot 命令后,V8 就開始收集內存,然后不斷觸發

HeapProfiler.addHeapSnapshotChunk 事件,直到堆數據寫完,然后執行 JS 回調。

總結:整個過程不算復雜,因為我們沒有涉及到堆內存管理那部分,V8 Inspector 提供了很多命令,有時間的話后續再分析其他的命令。

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

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 免费观看无人区完整版 | 日韩一二三 | 好大好硬抽搐好爽想要 | 国内精品一区二区在线观看 | www.日本黄色| 美女被上漫画 | 亚洲精美视频 | 女子监狱第二季未删减在线看 | 精品视频在线免费看 | 亚洲国产精品日本无码网站 | 99re8在这里只有精品23 | 成人欧美一区二区三区白人 | 美女翘臀跪床被打屁股作文 | 久久久久影视 | 国产色在线观看 | 日韩精品成人a在线观看 | 精品一区二区三区色花堂 | 免费yjsp妖精com | 国产在线观看精品 | 全黄h全肉细节修仙玄幻文 全彩调教侵犯h本子全彩妖气he | 欧美香蕉视频 | 国产麻豆精品入口在线观看 | 99视频有精品 | 国产精品拍拍拍福利在线观看 | 欧美高清无砖专区欧美精品 | 国产99久久精品一区二区 | 韩国伦理hd| 美女脱了内裤张开腿亲吻男生 | 亚洲第一页综合 | 99久久99久久久精品齐齐鬼色 | 女生被草 | 小寡妇好紧进去了好大看视频 | 精品一区二区三区在线成人 | 日本高清中文字幕一区二区三区 | 男人的j进入女人的j免费 | 欧美一级在线播放 | 国内揄拍国内精品久久 | 87影院在线观看视频在线观看 | 亚洲国产欧美在线人网站 | 波多野结衣作品在线观看 | 网站视频免费 |