<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                &emsp;&emsp;本節會重點分析內存和進程奔潰,并且會給出相應的監控方法。 &emsp;&emsp;本系列所有的示例源碼都已上傳至Github,[點擊此處](https://github.com/pwstrick/node)獲取。 ## 一、內存 &emsp;&emsp;雖然在 Node.js 中并不需要手動的對內存進行分配和銷毀,但是在開發中因為程序編寫問題也會發生內存泄漏的情況。 &emsp;&emsp;所以還是有必要了解一些 Node.js 開放的內存操作和常見的內存泄漏場景。 **1)內存指標** &emsp;&emsp;Node.js 項目在啟動后(例如 node index.js),會創建一個服務進程。進程是具有獨立功能的程序在一個數據集合上運行的過程,它是系統進行資源分配和調度的一個獨立單位。 &emsp;&emsp;程序在運行時會被分配一些內存空間,這個空間稱為常駐內存(Resident Set),V8 會將內存分為幾個段(也叫存儲空間): * 代碼(Code):存儲可執行的代碼。 * 棧(Stack):存儲原始類型的值(例如整數、布爾值等),以及對象的引用地址(指針)。 * 堆(Heap):存儲引用類型的值,例如對象、字符串和閉包。 &emsp;&emsp;在下圖中描繪了各個段,以及之間的關系。 :-: ![](https://img.kancloud.cn/78/8c/788cf3463bac222e0bd2b123dcc212de_959x947.png =600x) &emsp;&emsp;Node.js 提供了?[process.memoryUsage()](https://nodejs.org/dist/latest-v18.x/docs/api/process.html#processmemoryusage)?方法,用于讀取一個描述 Node.js 進程的內存使用量對象,所有屬性值都以字節為單位。 * rss:resident set size (常駐內存大小)的縮寫,表示進程使用了多少內存(RAM中的物理內存),包括所有 C++ 和 JavaScript 對象和代碼。 * heapTotal:堆的總大小,包括不能分配的內存,例如在垃圾回收之前對象之間的內存碎片。 * heapUsed:堆的使用量,已分配的內存,即堆中所有對象的總大小。 * external:使用到的系統鏈接庫所占用的內存,包含 C++ 模塊的內存使用量。 * arrayBuffers:為所有 Buffer 分配的內存,它被包含在 external 中。當 Node.js 被用作嵌入式庫時,此值可能為 0,在這種情況下可能不會追溯 ArrayBuffer 的分配。 &emsp;&emsp;下面的例子演示了本機的進程內存使用情況,默認都是字節,為了便于閱讀,已將輸出結果換算成 MB。 ~~~ // 換算成 MB function format (bytes) { return (bytes / 1024 / 1024).toFixed(2) + 'MB'; }; // 進程的內存使用 const mem = process.memoryUsage(); // 單位 字節 // { // rss: '20.05MB', // heapTotal: '3.86MB', // heapUsed: '3.02MB', // external: '0.24MB', // arrayBuffers: '0.01MB' // } console.log({ rss: format(mem.rss), heapTotal: format(mem.heapTotal), heapUsed: format(mem.heapUsed), external: format(mem.external), arrayBuffers: format(mem.arrayBuffers) }); ~~~ &emsp;&emsp;在 os 模塊中,有兩個方法:[freemem()](https://nodejs.org/dist/latest-v18.x/docs/api/os.html#osfreemem)?和?[totalmem()](https://nodejs.org/dist/latest-v18.x/docs/api/os.html#ostotalmem),分別表示系統的空閑內存和總內存。 &emsp;&emsp;以本機為例,電腦的內存是 16G,因此總內存也是這個數,而系統的空閑內存會動態變化。 ~~~ const os = require('os'); // 系統的空閑內存 const freemem = os.freemem(); format(freemem); // 178.58MB // 系統所有的內存 const totalmem = os.totalmem(); format(totalmem); // 16384.00MB = 16G ~~~ **2)內存泄漏** &emsp;&emsp;內存泄漏(memory leak)是計算機科學中的一種資源泄漏,主因是程序的內存管理失當,因而失去對一段已分配內存的控制。 &emsp;&emsp;程序繼續占用已不再使用的內存空間,或是存儲器所存儲對象無法透過執行代碼而訪問,令內存資源空耗。下面會羅列幾種內存泄漏的場景: &emsp;&emsp;第一種是全局變量,它不會被自動回收,而是會常駐在內存中,因為它總能被垃圾回收器訪問到。 &emsp;&emsp;第二種是閉包(closure),當一個函數能夠訪問和操作另一個函數作用域中的變量時,就會構成一個閉包,即使另一個函數已經執行結束,但其變量仍然會被存儲在內存中。 &emsp;&emsp;如果引用閉包的函數是一個全局變量或某個可以從根元素追溯到的對象,那么就不會被回收,以后不再使用的話,就會造成內存泄漏。 &emsp;&emsp;第三種是事件監聽,如果對某個目標重復注冊同一個事件,并且沒有移除,那么就會造成內存泄漏,之前記錄過一次這類[內存泄漏的排查](https://www.cnblogs.com/strick/p/14754867.html)。 &emsp;&emsp;第四種是緩存,當緩存中的對象屬性越來越多時,長期存活的概率就越大,垃圾回收器也不會清理,部分不需要的對象就會造成內存泄漏。 **3)heapdump** &emsp;&emsp;想要定位內存泄漏,可以使用快照工具(例如?[heapdump](https://github.com/bnoordhuis/node-heapdump)、v8-profiler 等)導出內存快照,使用 DevTools 查看內存快照。 &emsp;&emsp;在下面的示例中,會在全局緩存之前和之后導出一份內存快照。 ~~~ const heapdump = require('heapdump'); // 內存泄漏前的快照 heapdump.writeSnapshot('prev.heapsnapshot'); // 全局緩存 const cached = []; for(let i = 0; i < 10; i++) cached.push(new Array(1000000)); // 內存泄漏后的快照 heapdump.writeSnapshot('next.heapsnapshot'); ~~~ &emsp;&emsp;得到文件后,打開 Chrome DevTools,選擇 Memory =》Profiles =》Load 加載內存快照。 &emsp;&emsp;默認是 Summary 視圖,顯示按構造函數名稱分組的對象,如下圖所示。 :-: ![](https://img.kancloud.cn/31/b9/31b9549c938a76ce233e6960f25c4e0b_2092x1190.png =800x) &emsp;&emsp;視圖中的字段包括: * Contructor:使用構造函數創建的對象,其中 (closure) 表示閉包。后面增加 \* number 表示構造函數創建的實例個數。 * Distance:到 GC 根元素的距離,距離越大,引用越深。 * Shallow Size:對象自身的大小,即在 V8 堆上分配的大小,不包括它引用的對象。 * Retained Size:對象自身的大小和它引用的對象的大小,即可以釋放的內存大小。 &emsp;&emsp;切換到 Comparison 視圖,選擇比較的內存快照(next.heapsnapshot),檢查兩者的數據差異和內存變化,如下圖所示。 :-: ![](https://img.kancloud.cn/7a/b2/7ab24d1e2554f57e93d58329be17996c_2092x1200.png =800x) &emsp;&emsp;如果 Delta 一直增長,那么需要特別注意,有可能發生了內存泄漏,視圖中的所有字段說明如下所列: * \# New:新建的對象個數。 * \# Deleted:刪除的對象個數。 * \# Delta:發生變化的對象個數,凈增對象個數。 * Alloc.Size:已經分配的使用中的內存。 * Freed Size:為新對象釋放的內存。 * Size Delta:可用內存總量的變化,上圖中的數字是負數,說明可用內存變少了。 * Containment 視圖提供了一種從根元素作為入口的對象結構鳥瞰圖,如下圖所示。 :-: ![](https://img.kancloud.cn/30/27/3027856e61c960aefd72fb713a2336dd_2098x1202.png =800x) &emsp;&emsp;打開 GC roots =》 Isolate =》 Array 可以看到在代碼中插入給 cached 數組的 10 個元素。 :-: ![](https://img.kancloud.cn/d6/03/d60383f93c13e357d735cd984087e8e2_2092x1198.png =800x) &emsp;&emsp;要想能快速定位線上的內存泄漏,需要很多次的實踐,知道字段含義僅僅是第一步。 &emsp;&emsp;還需要在這么多信息中,定位到問題代碼所在的位置,這才是監控地最終目的。 ## 二、Core Dump &emsp;&emsp;Core Dump([核心轉儲](https://en.wikipedia.org/wiki/Core_dump))是操作系統在進程收到某些信號而終止運行時,將此時進程地址空間的內容以及有關進程狀態的其他信息寫入一個磁盤文件中。 &emsp;&emsp;在這個文件中包含內存分配信息 、堆棧指針等關鍵信息,對于診斷和分析程序異常非常重要,因為可以還原真實的案發現場。 **1)lldb** &emsp;&emsp;本機是 Mac OS,默認自帶了 lldb 命令,先用此命令來加載和分析 Core Dump 文件。 &emsp;&emsp;首先要在終端放開 Core Dump 文件的大小限制,這樣才能成功生成,命令如下。 ~~~ ulimit -c unlimited ~~~ &emsp;&emsp;但是一開始怎么樣都生成不了,查了[Mac 官方文檔](https://developer.apple.com/library/archive/technotes/tn2124/_index.html#//apple_ref/doc/uid/DTS10003391-CH1-SECCOREDUMPS)、[stackoverflow](https://stackoverflow.com/questions/9412156/how-to-generate-core-dumps-in-mac-os-x)等各種網絡資料都無濟于事。 &emsp;&emsp;后面自己才不經意的發現,這個命名只有在當前終端才有效,換個終端或 Tab 頁都將無效,白白浪費了 3 個小時。 &emsp;&emsp;然后創建 error.js 文件,里面就寫一段會報錯的代碼,例如讀取 undefined 的屬性。 ~~~ const test = { }; setTimeout(() => { console.log(test.obj.name); }, 1000); ~~~ &emsp;&emsp;接著在終端輸入啟動的命令,但是需要帶上參數 --abort-on-uncaught-exception。 ~~~ node --abort-on-uncaught-exception error.js ~~~ &emsp;&emsp;代碼運行完成后,Mac OS 就會在 /cores 目錄中生成一個 core.\[pid\] 的文件,pid 就是當前進程的編號,通過 process.pid 也能讀取到。 &emsp;&emsp;在本地生成了一個 core.5889 文件,足足有 1.8G,怪不得不能隨便生成,硬盤吃不消。最后輸入 lldb 命令加載和分析文件。 ~~~ lldb -c core.5889 ~~~ &emsp;&emsp;在加載成功成功后,會有一段提示。在最后一行需要手動輸入 bt(backtrace)查看堆棧信息。 ![](https://img.kancloud.cn/9f/e7/9fe72dc5bac53240a28ec43d960889dd_2154x1210.png =800x) &emsp;&emsp;上述是 C++ 的堆棧,可以看到 uv\_run 開啟事件循環,然后運行 uv\_\_run\_timers 階段,接著就發生了錯誤,底層的錯誤內容看不大懂。 **2)llnode** &emsp;&emsp;這個[llnode](https://github.com/nodejs/llnode)其實是 lldb 的一個插件,能還原 JavaScript 堆棧幀、對象、源代碼等可讀信息,類似于 Source Map 的功能。 &emsp;&emsp;直接運行安裝命令 npm install llnode 會報錯,如下所示。 ~~~ Reading lldb version... xcode-select: error: tool 'xcodebuild' requires Xcode, but active developer directory '/Library/Developer/CommandLineTools' is a command line tools instance Error: Command failed: xcodebuild -version xcode-select: error: tool 'xcodebuild' requires Xcode, but active developer directory '/Library/Developer/CommandLineTools' is a command line tools instance ~~~ &emsp;&emsp;查看[官方文檔](https://github.com/nodejs/llnode#prerequisites-install-lldb-and-its-library),在 Mac OS 中,需要安裝 LLDB 及其庫或者直接安裝[Xcode](https://developer.apple.com/xcode/)并使用它附帶的 LLDB,前者的命令如下。 ~~~ brew install --with-lldb --with-toolchain llvm ~~~ &emsp;&emsp;但是這條命令會報下面的錯誤,于是將 --with-lldb 參數去除。 ~~~ Error: invalid option: --with-lldb ~~~ &emsp;&emsp;再運行一次,持續了一個小時,才下載 32%,最后又是報錯。 ~~~ Error: invalid option: --with-toolchain ~~~ &emsp;&emsp;無奈就想到去安裝 Xcode,但是集成軟件太大,要 10G多,于是選擇[Command Line Tools (macOS 10.14) for Xcode 10.1](https://developer.apple.com/download/all/?q=Xcode),下載了 20 多分鐘。 &emsp;&emsp;安裝完成后,還是無法下載 llnode 包,只得去下載 Xcode 10.1,又是 20 多分鐘,Xcode\_10.1.xip 是一個壓縮包,需要解壓。 &emsp;&emsp;解壓安裝完成后,將當前目錄的 Xcode 移動到應用程序目錄,運行下面命令。 ~~~ sudo xcode-select -s /Applications/Xcode.app/Contents/Developer ~~~ &emsp;&emsp;重新下載 llnode 包,這次終于不報錯了,開始出現下面的提示。 ~~~ Looking for llvm-config... ? [0/1] Installing llnode@*No llvm-config found Reading lldb version... ? [0/1] Installing llnode@*Deduced lldb version from Xcode version: Xcode 10.1 -> lldb 3.9 Installing llnode for lldb, lldb version 3.9 Looking for headers for lldb 3.9... Could not find the headers, will download them later Looking for shared libraries for lldb 3.9... Could not find the shared libraries llnode will be linked to the LLDB shared framework from the Xcode installation ~~~ &emsp;&emsp;因為沒有全局安裝 llnode,所以加載命令要加 npx,core.5889 加了絕對路徑。 ~~~ npx llnode -c /cores/core.5889 ~~~ &emsp;&emsp;下圖是成功加載后的圖,運行 v8 bt 命令后,并沒有得到預期的堆棧信息。 :-: ![](https://img.kancloud.cn/3b/30/3b303f6db8f69f4cd02d7adde656d35f_2204x1265.png =800x) &emsp;&emsp;過程非常曲折,最后還是很遺憾沒有成功解析,不知道是 lldb 的問題還是生成的文件問題,亦或是 Node 版本的問題。 &emsp;&emsp;如果不想這么麻煩的解析,還可以直接使用成熟的[Node.js 性能平臺](https://www.aliyun.com/product/nodejs),也有 Coredump 文件分析,并且做了深度定制,能更清晰地看到錯誤源碼。 參考資料: [Node.js 環境性能監控探究](https://juejin.cn/post/6844903781889474567) [Nodejs: MemoryUsage()返回的rss,heapTotal,heapUsed,external的含義和區別](https://blog.csdn.net/pengpengzhou/article/details/106811717) [What do the return values of node.js process.memoryUsage() stand for??](https://stackoverflow.com/questions/12023359/what-do-the-return-values-of-node-js-process-memoryusage-stand-for) [如何分析 Node.js 中的內存泄漏](https://zhuanlan.zhihu.com/p/25736931)??[Node.js 應用故障排查手冊](https://github.com/aliyun-node/Node.js-Troubleshooting-Guide) [Record heap snapshots](https://developer.chrome.com/docs/devtools/memory-problems/heap-snapshots/) [前端內存泄露淺析](https://juejin.cn/post/6844903934235148296)??[Node常用dump分析](https://www.tripfe.cn/node-commonly-used-dump-analysis/) [Chrome Memory Tab: Learn to Find JavaScript Memory Leaks](https://www.bitdegree.org/learn/chrome-memory-tab) [Node 案發現場揭秘 —— Coredump 還原線上異常](https://zhuanlan.zhihu.com/p/41178823) [Node.js調試之llnode篇](https://zhuanlan.zhihu.com/p/58181765)??[Node調試指南-uncaughtException](https://wizardforcel.gitbooks.io/node-in-debugging/content/3.7.html) [coredump](https://ahuigo.github.io/b/c/c-debug-coredump#/)?[lldb常用命令與調試技巧](https://juejin.cn/post/6872764160640450574) [node常用dump分析](https://www.tripfe.cn/node-commonly-used-dump-analysis/)?[Node調試指南-內存篇](https://juejin.cn/post/6844903779632988174) [Explore Node.js core dumps using the llnode plugin for lldb](https://developer.ibm.com/articles/explore-nodejs-core-dumps-using-the-llnode-plugin-for-lldb/) [v8 source list always fails w/ error: USAGE: v8 source list](https://github.com/nodejs/llnode/issues/138) ***** > 原文出處: [博客園-Node.js精進](https://www.cnblogs.com/strick/category/2154090.html) [知乎專欄-前端性能精進](https://www.zhihu.com/column/c_1611672656142725120) 已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。 ![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200) 推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看