日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

4類JavaScript內存泄露及如何避免

瀏覽:202日期:2023-11-18 10:51:53
原文:4 Types of Memory Leaks in JavaScript and How to Get Rid Of Them 譯文來自:Alon’s Blog 本文將探索常見的客戶端 JavaScript 內存泄露,以及如何使用 Chrome 開發工具發現問題。 簡介 內存泄露是每個開發者最終都要面對的問題,它是許多問題的根源:反應遲緩,崩潰,高延遲,以及其他應用問題。 什么是內存泄露? 本質上,內存泄露可以定義為:應用程序不再需要占用內存的時候,由于某些原因,內存沒有被操作系統或可用內存池回收。編程語言管理內存的方式各不相同。只有開發者最清楚哪些內存不需要了,操作系統可以回收。一些編程語言提供了語言特性,可以幫助開發者做此類事情。另一些則寄希望于開發者對內存是否需要清晰明了。 JavaScript 內存管理 JavaScript 是一種垃圾回收語言。垃圾回收語言通過周期性地檢查先前分配的內存是否可達,幫助開發者管理內存。換言之,垃圾回收語言減輕了“內存仍可用”及“內存仍可達”的問題。兩者的區別是微妙而重要的:僅有開發者了解哪些內存在將來仍會使用,而不可達內存通過算法確定和標記,適時被操作系統回收。 JavaScript 內存泄露 垃圾回收語言的內存泄露主因是不需要的引用。理解它之前,還需了解垃圾回收語言如何辨別內存的可達與不可達。 Mark-and-sweep 大部分垃圾回收語言用的算法稱之為 Mark-and-sweep 。算法由以下幾步組成: 1.垃圾回收器創建了一個“roots”列表。Roots 通常是代碼中全局變量的引用。JavaScript 中,“window” 對象是一個全局變量,被當作 root 。window 對象總是存在,因此垃圾回收器可以檢查它和它的所有子對象是否存在(即不是垃圾); 2.所有的 roots 被檢查和標記為激活(即不是垃圾)。所有的子對象也被遞歸地檢查。從 root 開始的所有對象如果是可達的,它就不被當作垃圾。 3.所有未被標記的內存會被當做垃圾,收集器現在可以釋放內存,歸還給操作系統了。 現代的垃圾回收器改良了算法,但是本質是相同的:可達內存被標記,其余的被當作垃圾回收。 不需要的引用是指開發者明知內存引用不再需要,卻由于某些原因,它仍被留在激活的 root 樹中。在JavaScript中,不需要的引用是保留在代碼中的變量,它不再需要,卻指向一塊本該被釋放的內存。有些人認為這是開發者的錯誤。 為了理解JavaScript中最常見的內存泄露,我們需要了解哪種方式的引用容易被遺忘。 三種類型的常見JavaScript內存泄露 1:意外的全局變量 JavaScript 處理未定義變量的方式比較寬松:未定義的變量會在全局對象創建一個新變量。在瀏覽器中,全局對象是 window 。

function foo(arg) { bar = 'this is a hidden global variable';}真相是:

function foo(arg) { window.bar = 'this is an explicit global variable';}函數 foo 內部忘記使用 var ,意外創建了一個全局變量。此例泄露了一個簡單的字符串,無傷大雅,但是有更糟的情況。 另一種意外的全局變量可能由 this 創建:

function foo() { this.variable = 'potential accidental global';}// Foo 調用自己,this 指向了全局對象(window)// 而不是 undefinedfoo();引用在 JavaScript 文件頭部加上 ’use strict’,可以避免此類錯誤發生。啟用嚴格模式解析 JavaScript ,避免意外的全局變量。 全局變量注意事項 盡管我們討論了一些意外的全局變量,但是仍有一些明確的全局變量產生的垃圾。它們被定義為不可回收(除非定義為空或重新分配)。尤其當全局變量用于臨時存儲和處理大量信息時,需要多加小心。如果必須使用全局變量存儲大量數據時,確保用完以后把它設置為 null 或者重新定義。與全局變量相關的增加內存消耗的一個主因是緩存。緩存數據是為了重用,緩存必須有一個大小上限才有用。高內存消耗導致緩存突破上限,因為緩存內容無法被回收。 2:被遺忘的計時器或回調函數 在JavaScript中使用setInterval非常平常。一段常見的代碼:

var someResource = getData();setInterval(function() { var node = document.getElementById(’Node’); if(node) {// 處理 node 和 someResourcenode.innerHTML = JSON.stringify(someResource)); }}, 1000);此例說明了什么:與節點或數據關聯的計時器不再需要,node 對象可以刪除,整個回調函數也不需要了。可是,計時器回調函數仍然沒被回收(計時器停止才會被回收)。同時,someResource 如果存儲了大量的數據,也是無法被回收的。 對于觀察者的例子,一旦它們不再需要(或者關聯的對象變成不可達),明確地移除它們非常重要。老的 IE 6 是無法處理循環引用的。如今,即使沒有明確移除它們,一旦觀察者對象變成不可達,大部分瀏覽器是可以回收觀察者處理函數的。 觀察者代碼示例:

var element = document.getElementById(’button’);function onClick(event) { element.innerHTML = ’text’;}element.addEventListener(’click’, onClick);對象觀察者和循環引用注意事項 老版本的 IE 是無法檢測 DOM 節點與 JavaScript 代碼之間的循環引用,會導致內存泄露。如今,現代的瀏覽器(包括 IE 和 Microsoft Edge)使用了更先進的垃圾回收算法,已經可以正確檢測和處理循環引用了。換言之,回收節點內存時,不必非要調用 removeEventListener 了。 3:脫離 DOM 的引用 有時,保存 DOM 節點內部數據結構很有用。假如你想快速更新表格的幾行內容,把每一行 DOM 存成字典(JSON 鍵值對)或者數組很有意義。此時,同樣的 DOM 元素存在兩個引用:一個在 DOM 樹中,另一個在字典中。將來你決定刪除這些行時,需要把兩個引用都清除。

var elements = { button: document.getElementById(’button’), image: document.getElementById(’image’), text: document.getElementById(’text’)};function doStuff() { image.src = ’http://some.url/image’; button.click(); console.log(text.innerHTML); // 更多邏輯}function removeButton() { // 按鈕是 body 的后代元素 document.body.removeChild(document.getElementById(’button’)); // 此時,仍舊存在一個全局的 #button 的引用 // elements 字典。button 元素仍舊在內存中,不能被 GC 回收。}此外還要考慮 DOM 樹內部或子節點的引用問題。假如你的 JavaScript 代碼中保存了表格某一個 <td> 的引用。將來決定刪除整個表格的時候,直覺認為 GC 會回收除了已保存的 <td> 以外的其它節點。實際情況并非如此:此 <td> 是表格的子節點,子元素與父元素是引用關系。由于代碼保留了 <td> 的引用,導致整個表格仍待在內存中。保存 DOM 元素引用的時候,要小心謹慎。 4:閉包 閉包是JavaScript開發的一個關鍵方面:匿名函數可以訪問父級作用域的變量。 代碼示例:

var theThing = null;var replaceThing = function () { var originalThing = theThing; var unused = function () { if (originalThing) console.log('hi'); }; theThing = { longStr: new Array(1000000).join(’*’), someMethod: function () { console.log(someMessage); } };};setInterval(replaceThing, 1000);代碼片段做了一件事情:每次調用 replaceThing ,theThing 得到一個包含一個大數組和一個新閉包(someMethod)的新對象。同時,變量 unused 是一個引用 originalThing 的閉包(先前的 replaceThing 又調用了 theThing )。思緒混亂了嗎?最重要的事情是,閉包的作用域一旦創建,它們有同樣的父級作用域,作用域是共享的。someMethod 可以通過 theThing 使用,someMethod 與 unused 分享閉包作用域,盡管 unused 從未使用,它引用的 originalThing 迫使它保留在內存中(防止被回收)。當這段代碼反復運行,就會看到內存占用不斷上升,垃圾回收器(GC)并無法降低內存占用。本質上,閉包的鏈表已經創建,每一個閉包作用域攜帶一個指向大數組的間接的引用,造成嚴重的內存泄露。 引用Meteor的博文解釋了如何修復此種問題。在 replaceThing 的最后添加 originalThing = null 。Chrome 內存剖析工具概覽 Chrome 提供了一套很棒的檢測 JavaScript 內存占用的工具。與內存相關的兩個重要的工具:timeline 和 profiles。 4類JavaScript內存泄露及如何避免 timeline 可以檢測代碼中不需要的內存。在此截圖中,我們可以看到潛在的泄露對象穩定的增長,數據采集快結束時,內存占用明顯高于采集初期,Node(節點)的總量也很高。種種跡象表明,代碼中存在 DOM 節點泄露的情況。 Profiles 4類JavaScript內存泄露及如何避免 Profiles 是你可以花費大量時間關注的工具,它可以保存快照,對比 JavaScript 代碼內存使用的不同快照,也可以記錄時間分配。每一次結果包含不同類型的列表,與內存泄露相關的有 summary(概要) 列表和 comparison(對照) 列表。 summary(概要) 列表展示了不同類型對象的分配及合計大小:shallow size(特定類型的所有對象的總大小),retained size(shallow size 加上其它與此關聯的對象大小)。它還提供了一個概念,一個對象與關聯的 GC root 的距離。 對比不同的快照的 comparison list 可以發現內存泄露。 實例:使用Chrome發現內存泄露 實質上有兩種類型的泄露:周期性的內存增長導致的泄露,以及偶現的內存泄露。顯而易見,周期性的內存泄露很容易發現;偶現的泄露比較棘手,一般容易被忽視,偶爾發生一次可能被認為是優化問題,周期性發生的則被認為是必須解決的 bug。 以Chrome文檔中的代碼為例:

var x = [];function createSomeNodes() { var div,i = 100,frag = document.createDocumentFragment(); for (;i > 0; i--) {div = document.createElement('div');div.appendChild(document.createTextNode(i + ' - '+ new Date().toTimeString()));frag.appendChild(div); } document.getElementById('nodes').appendChild(frag);}function grow() { x.push(new Array(1000000).join(’x’)); createSomeNodes(); setTimeout(grow,1000);}當 grow 執行的時候,開始創建 div 節點并插入到 DOM 中,并且給全局變量分配一個巨大的數組。通過以上提到的工具可以檢測到內存穩定上升。 找出周期性增長的內存 timeline 標簽擅長做這些。在 Chrome 中打開例子,打開 Dev Tools ,切換到 timeline,勾選 memory 并點擊記錄按鈕,然后點擊頁面上的 The Button 按鈕。過一陣停止記錄看結果: 4類JavaScript內存泄露及如何避免 兩種跡象顯示出現了內存泄露,圖中的 Nodes(綠線)和 JS heap(藍線)。Nodes 穩定增長,并未下降,這是個顯著的信號。 JS heap 的內存占用也是穩定增長。由于垃圾收集器的影響,并不那么容易發現。圖中顯示內存占用忽漲忽跌,實際上每一次下跌之后,JS heap 的大小都比原先大了。換言之,盡管垃圾收集器不斷的收集內存,內存還是周期性的泄露了。 確定存在內存泄露之后,我們找找根源所在。 保存兩個快照 切換到 Chrome Dev Tools 的 profiles 標簽,刷新頁面,等頁面刷新完成之后,點擊 Take Heap Snapshot 保存快照作為基準。而后再次點擊 The Button 按鈕,等數秒以后,保存第二個快照。 4類JavaScript內存泄露及如何避免 篩選菜單選擇 Summary,右側選擇 Objects allocated between Snapshot 1 and Snapshot 2,或者篩選菜單選擇 Comparison ,然后可以看到一個對比列表。 此例很容易找到內存泄露,看下 (string) 的 Size Delta Constructor,8MB,58個新對象。新對象被分配,但是沒有釋放,占用了8MB。 如果展開 (string) Constructor,會看到許多單獨的內存分配。選擇某一個單獨的分配,下面的 retainers 會吸引我們的注意。 4類JavaScript內存泄露及如何避免 我們已選擇的分配是數組的一部分,數組關聯到 window 對象的 x 變量。這里展示了從巨大對象到無法回收的 root(window)的完整路徑。我們已經找到了潛在的泄露以及它的出處。 我們的例子還算簡單,只泄露了少量的 DOM 節點,利用以上提到的快照很容易發現。對于更大型的網站,Chrome 還提供了 Record Heap Allocations 功能。 Record heap allocations 找內存泄露 回到Chrome Dev Tools 的 profiles 標簽,點擊 Record Heap Allocations。工具運行的時候,注意頂部的藍條,代表了內存分配,每一秒有大量的內存分配。運行幾秒以后停止。 4類JavaScript內存泄露及如何避免 上圖中可以看到工具的殺手锏:選擇某一條時間線,可以看到這個時間段的內存分配情況。盡可能選擇接近峰值的時間線,下面的列表僅顯示了三種 constructor:其一是泄露最嚴重的(string),下一個是關聯的 DOM 分配,最后一個是 Text constructor(DOM 葉子節點包含的文本)。 從列表中選擇一個 HTMLDivElement constructor,然后選擇 Allocation stack。 4類JavaScript內存泄露及如何避免 現在知道元素被分配到哪里了吧(grow -> createSomeNodes),仔細觀察一下圖中的時間線,發現 HTMLDivElement constructor 調用了許多次,意味著內存一直被占用,無法被 GC 回收,我們知道了這些對象被分配的確切位置(createSomeNodes)。回到代碼本身,探討下如何修復內存泄露吧。 另一個有用的特性 在 heap allocations 的結果區域,選擇 Allocation。 4類JavaScript內存泄露及如何避免 這個視圖呈現了內存分配相關的功能列表,我們立刻看到了 grow 和 createSomeNodes。當選擇 grow 時,看看相關的 object constructor,清楚地看到 (string), HTMLDivElement 和 Text 泄露了。 結合以上提到的工具,可以輕松找到內存泄露。 延伸閱讀 Memory Management - Mozilla Developer Network JScript Memory Leaks - Douglas Crockford (old, in relation to Internet Explorer 6 leaks) JavaScript Memory Profiling - Chrome Developer Docs Memory Diagnosis - Google Developers An Interesting Kind of JavaScript Memory Leak - Meteor blog Grokking V8 closures

標簽: JavaScript
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
999久久久国产精品| 亚洲1234区| 国产伊人精品| 亚洲四虎影院| 日韩大片在线| 久久久久中文| 久久久国产精品一区二区中文| 91欧美在线| 久久精品一区二区不卡| 极品日韩av| 亚洲一本视频| 亚洲女同中文字幕| 一区二区视频欧美| 午夜在线视频观看日韩17c| 日韩在线一区二区| 日本va欧美va精品发布| 国产日产精品_国产精品毛片| 国产精品毛片视频| 高清久久一区| 亚洲一级影院| 日韩制服丝袜av| 91精品在线免费视频| 国产免费av一区二区三区| 亚洲精品99| 日本成人在线不卡视频| 欧美日本三区| 97欧美在线视频| 黄色精品网站| 国产欧美日韩免费观看| 精品一级视频| 亚洲成人精选| 日韩欧美高清一区二区三区| 国产精品亚洲四区在线观看| 国产在线一区不卡| 91成人精品| 国产亚洲一区| 亚洲精品88| 一区二区精彩视频| 国内揄拍国内精品久久| 不卡在线一区二区| 日本不卡视频在线观看 | 中文字幕成人| 日韩高清欧美激情| 精品欧美视频| 伊人精品视频| 国产精品日韩精品在线播放| 久久精品在线| 欧美一区激情| 久久亚洲专区| 午夜电影一区| 亚洲性色av| 日韩激情视频网站| 日韩欧美一区二区三区在线观看 | 亚洲精品麻豆| 在线天堂资源www在线污| 久久福利毛片| 成午夜精品一区二区三区软件| 夜夜嗨av一区二区三区网站四季av| 国产日韩一区二区三区在线播放| 欧美日韩精品免费观看视欧美高清免费大片 | 午夜久久福利| 国产精品久久久久9999高清| 极品日韩av| 精品视频网站| 综合日韩在线| 久久婷婷一区| 欧美a在线观看| 伊人国产精品| 亚洲91视频| 久久精品国产福利| 亚洲免费成人av在线| 色婷婷精品视频| 免费亚洲一区| 亚洲伊人精品酒店| 国产一区二区视频在线看| 免费成人在线视频观看| 日韩久久一区二区三区| 欧美亚洲三级| 一区在线免费| 亚洲成人av观看| 国产激情久久| 久久精品国语| av高清不卡| 国产精品毛片aⅴ一区二区三区| 日韩中文字幕1| 国产精品av一区二区| 国产一区不卡| 国产精品极品在线观看| 一区二区日韩免费看| 久久国产免费| 超碰成人av| 亚洲一区有码| 午夜亚洲福利在线老司机| 久久一区二区中文字幕| 国产一区二区三区四区五区| 国产一区 二区| 日韩高清不卡一区二区| 美女久久网站| 亚洲黄色影院| 欧美日韩精品在线一区| 国产欧洲在线| 国产一区二区三区视频在线| 国产精品一区二区精品视频观看| 亚洲日本欧美| 亚洲精华国产欧美| 99久久亚洲精品蜜臀| 亚洲天堂免费电影| 岛国av在线网站| 成人精品久久| 国产精品二区不卡| 97精品在线| 成人小电影网站| 黄色网一区二区| 精品网站aaa| 国产91欧美| 日本一区二区高清不卡| 精品高清久久| 国际精品欧美精品| 国产成人久久精品一区二区三区| 日韩国产欧美三级| 日本成人中文字幕| 青青伊人久久| 国产精品xxxav免费视频| 国产精品成人国产| 久久久久黄色| 成人国产精品一区二区免费麻豆| 麻豆久久一区| 国产成人精品一区二区三区视频| 精品久久精品| 日本а中文在线天堂| 色综合www| 午夜国产一区二区| 免费观看在线综合| 日韩精品欧美大片| 97久久亚洲| 国产精品自在| 精品国产aⅴ| 亚洲成av在线| 国产亚洲毛片| 日韩欧美中文在线观看| 国产欧美日韩免费观看| 国产一区二区三区国产精品| 日韩欧美一区二区三区免费看| 久久久久久一区二区| 欧美亚洲激情| 亚洲一区二区三区高清| 亚洲精品影视| 久久不卡日韩美女| 亚洲成a人片| 宅男在线一区| 亚洲香蕉久久| 国产精品乱战久久久| 欧美天堂视频| 性欧美精品高清| 欧美日韩伊人| 高清一区二区三区av| 91精品成人| 日本色综合中文字幕| 精品国产亚洲一区二区三区| 特黄特色欧美大片| 亚洲2区在线| 国产一区丝袜| 久久福利影视| 国产精品成人**免费视频 | 欧美精品97| 色88888久久久久久影院| 丝袜脚交一区二区| 国产欧美另类| 欧美亚洲在线日韩| 日韩avvvv在线播放| 亚洲欧洲美洲av| 亚洲影视一区| 韩国久久久久久| 亚洲精品日本| 成人久久一区| 欧美精品三级在线| 国产99亚洲| 久久国产欧美日韩精品| 国产66精品| 亚洲精品一二| 99精品电影| 91成人小视频| 久久网站免费观看| 国产精品一区二区三区www| 国产高清久久| 麻豆国产精品一区二区三区| 欧美另类专区| 免费一区二区三区在线视频| 欧美日韩精品一本二本三本| 国产精品v日韩精品v欧美精品网站 | 久久精品三级| 国产精品毛片| 久久丁香四色| 视频一区欧美精品| 97精品国产一区二区三区| 亚洲久久一区| 亚洲欧美一区在线| 鲁大师精品99久久久| 久久国产精品久久久久久电车 | 麻豆精品在线观看|