自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Node.js 抓取堆快照過(guò)程解析

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

[[430591]]

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

首先來(lái)看一下 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 

對(duì)應(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 對(duì)象。

  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 對(duì)象,然后通過(guò) HeapSnapshotGenerator 對(duì)象的 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)存,保證看到的是存活的對(duì)象,否則影響內(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 對(duì)象。接著看收集完后的邏輯。

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

HeapSnapshotOutputStream 是用于通知調(diào)用方收集的數(shù)據(jù)(通過(guò) 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 通過(guò) WriteAsciiChunk 告訴調(diào)用方收集的數(shù)據(jù),但是目前我們還沒(méi)有數(shù)據(jù)源,下面看看數(shù)據(jù)源怎么來(lái)的。

  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.   // 開(kāi)始寫 
  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ù)通過(guò) OutputStreamWriter 對(duì)象 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 控制是否還需要寫入,通過(guò) kAbort 和 kContinue 
  12.   if (stream_->WriteAsciiChunk(chunk_.begin(), chunk_pos_) == 
  13.       v8::OutputStream::kAbort) 
  14.     aborted_ = true
  15.   chunk_pos_ = 0; 
  16.  

我們看到最終通過(guò) 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 層的事件。再看一下文章開(kāi)頭的代碼。

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

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

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

 

責(zé)任編輯:姜華 來(lái)源: 編程雜技
相關(guān)推薦

2025-01-13 00:00:00

2022-04-01 08:02:32

Node.js快照加速hooks

2013-11-01 09:34:56

Node.js技術(shù)

2015-03-10 10:59:18

Node.js開(kāi)發(fā)指南基礎(chǔ)介紹

2011-09-09 14:23:13

Node.js

2011-11-01 10:30:36

Node.js

2011-09-08 13:46:14

node.js

2011-09-02 14:47:48

Node

2012-10-24 14:56:30

IBMdw

2011-11-10 08:55:00

Node.js

2021-10-03 15:02:50

HTTPNodejs

2021-12-25 22:29:57

Node.js 微任務(wù)處理事件循環(huán)

2025-03-10 00:00:22

PDF圖片表格

2020-05-29 15:33:28

Node.js框架JavaScript

2012-02-03 09:25:39

Node.js

2015-06-23 15:27:53

HproseNode.js

2021-08-07 07:56:59

Node邏輯對(duì)象

2021-04-06 10:15:29

Node.jsHooks前端

2020-10-26 08:34:13

Node.jsCORS前端

2021-02-01 15:42:45

Node.jsSQL應(yīng)用程序
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)