电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

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

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

瀏覽:98日期: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
相關文章:
主站蜘蛛池模板: Safety light curtain|Belt Sway Switches|Pull Rope Switch|ultrasonic flaw detector-Shandong Zhuoxin Machinery Co., Ltd | 肉嫩度仪-凝胶测试仪-国产质构仪-气味分析仪-上海保圣实业发展有限公司|总部 | 齿轮减速马达一体式_蜗轮蜗杆减速机配电机-德国BOSERL齿轮减速电动机生产厂家 | 螺杆真空泵_耐腐蚀螺杆真空泵_水环真空泵_真空机组_烟台真空泵-烟台斯凯威真空 | 一体化预制泵站-一体化提升泵站-一体化泵站厂家-山东康威环保 | 桥架-槽式电缆桥架-镀锌桥架-托盘式桥架 - 上海亮族电缆桥架制造有限公司 | 干培两用箱-细菌恒温培养箱-菲斯福仪器| 机床导轨_导轨板_滚轮导轨-上海旻佑精密机械有限公司 | 河北码上网络科技|邯郸小程序开发|邯郸微信开发|邯郸网站建设 | 不锈钢水箱生产厂家_消防水箱生产厂家-河南联固供水设备有限公司 | 防水套管厂家-柔性防水套管-不锈钢|刚性防水套管-天翔管道 | 薪动-人力资源公司-灵活用工薪资代发-费用结算-残保金优化-北京秒付科技有限公司 | 棉柔巾代加工_洗脸巾oem_一次性毛巾_浴巾生产厂家-杭州禾壹卫品科技有限公司 | 选矿设备-新型重选设备-金属矿尾矿重选-青州冠诚重工机械有限公司 | 网站制作优化_网站SEO推广解决方案-无锡首宸信息科技公司 | 上海律师事务所_上海刑事律师免费咨询平台-煊宏律师事务所 | 智能汉显全自动量热仪_微机全自动胶质层指数测定仪-鹤壁市科达仪器仪表有限公司 | 施工围挡-施工PVC围挡-工程围挡-深圳市旭东钢构技术开发有限公司 | 耐酸泵,耐腐蚀真空泵,耐酸真空泵-淄博华舜耐腐蚀真空泵有限公司 精密模具-双色注塑模具加工-深圳铭洋宇通 | 珠海白蚁防治_珠海灭鼠_珠海杀虫灭鼠_珠海灭蟑螂_珠海酒店消杀_珠海工厂杀虫灭鼠_立净虫控防治服务有限公司 | 济南办公室装修-厂房装修-商铺装修-工装公司-山东鲁工装饰设计 | C形臂_动态平板DR_动态平板胃肠机生产厂家制造商-普爱医疗 | 鑫达滑石-辽宁鑫达滑石集团 | 外贮压-柜式-悬挂式-七氟丙烷-灭火器-灭火系统-药剂-价格-厂家-IG541-混合气体-贮压-非贮压-超细干粉-自动-灭火装置-气体灭火设备-探火管灭火厂家-东莞汇建消防科技有限公司 | 小型铜米机-干式铜米机-杂线全自动铜米机-河南鑫世昌机械制造有限公司 | 丙烷/液氧/液氮气化器,丙烷/液氧/液氮汽化器-无锡舍勒能源科技有限公司 | 电子书导航网_电子书之家_电子书大全_最新电子书分享发布平台 | 泰来华顿液氮罐,美国MVE液氮罐,自增压液氮罐,定制液氮生物容器,进口杜瓦瓶-上海京灿精密机械有限公司 | 涂层测厚仪_光泽度仪_uv能量计_紫外辐照计_太阳膜测试仪_透光率仪-林上科技 | 深圳展厅设计_企业展馆设计_展厅设计公司_数字展厅设计_深圳百艺堂 | 储气罐,真空罐,缓冲罐,隔膜气压罐厂家批发价格,空压机储气罐规格型号-上海申容压力容器集团有限公司 | 老城街小面官网_正宗重庆小面加盟技术培训_特色面馆加盟|牛肉拉面|招商加盟代理费用多少钱 | 仓储笼_仓储货架_南京货架_仓储货架厂家_南京货架价格低-南京一品仓储设备制造公司 | 欧必特空气能-商用空气能热水工程,空气能热水器,超低温空气源热泵生产厂家-湖南欧必特空气能公司 | 灌木树苗-绿化苗木-常绿乔木-价格/批发/基地 - 四川成都途美园林 | 天津暖气片厂家_钢制散热器_天津铜铝复合暖气片_维尼罗散热器 | 百度关键词优化_网站优化_SEO价格 - 云无限好排名 | 垃圾清运公司_环卫保洁公司_市政道路保洁公司-华富环境 | 上海公众号开发-公众号代运营公司-做公众号的公司企业服务商-咏熠软件 | 超声波电磁流量计-液位计-孔板流量计-料位计-江苏信仪自动化仪表有限公司 | 蜘蛛车-高空作业平台-升降机-高空作业车租赁-臂式伸缩臂叉装车-登高车出租厂家 - 普雷斯特机械设备(北京)有限公司 |