垃圾回收
- 概述
- 標記清理
- 參考計數
- 性能
- 記憶體管理
概述
- JavaScript 是使用垃圾回收的語言,也就是說執行環境負責在代碼執行時管理記憶體,
- JavaScript通過自動記憶體管理實作記憶體分配和閑置資源回收,
- 基本思路:
確定哪個變數不會再使用然后釋放它占用的記憶體這個程序是周期性的,即垃圾回收程式每隔一定時間(或者說在代碼執行程序中某個預定的收集時間)就會自動運行,
以函式中區域變數的正常生命周期為例,
函式中的區域變數會在函式執行時存在,此時,堆疊(或堆)記憶體會分配空間以保存相應的值,函式在內部使用了變數,然后退出,此時,就不再需要那個區域變數了,它占用的記憶體可以釋放,供后面使用,
這種情況下顯然不再需要區域變數了,但并不是所有時候都會這么明顯,
垃圾回收程式必須跟蹤記錄哪個變數還會使用,以及哪個變數不會再使用,以便回收記憶體,
在瀏覽器的發展史上,用到過兩種主要的標記策略:標記清理、參考計數,
標記清理
- JavaScript 最常用的垃圾回收策略是標記清理(mark-and-sweep),
當變數進入背景關系,比如在函式內部宣告一個變數時,這個變數會被加上存在于背景關系中的標記,而在背景關系中的變數,邏輯上講,永遠不應該釋放它們的記憶體,因為只要背景關系中的代碼在運行,就有可能用到它們,
當變數離開背景關系時,也會被加上離開背景關系的標記
給變數加標記的方式有很多種,例如:
- 當變數進入背景關系時,反轉某一位;
- 或者可以維護“在背景關系中”和“不在背景關系中”兩個變數串列,可以把變數從一個串列轉移到另一個串列,
- 標記的方法不重要,關鍵是策略
垃圾回收程式如何回收:
- 垃圾回收程式運行的時候,會標記記憶體中存盤的所有變數(記住,標記方法有很多種),
- 然后,它會將所有在背景關系中的變數,以及被在背景關系中的變數參考的變數的標記去掉,
- 在此之后再被加上標記 的變數就是待洗掉的了,原因是任何在背景關系中的變數都訪問不到它們了,
- 隨后垃圾回收程式做一次記憶體清理,銷毀帶標記的所有值并識訓它們的記憶體,
參考計數
- 沒那么常用的垃圾回收策略是參考計數,
- 思路是對每個值都記錄它被參考的次數,
- 宣告變數并給它賦一個參考值時,這個值的參考數為 1,
- 如果同一個值又被賦給另一個變
量,那么參考數加 1, - 類似地,如果保存對該值參考的變數被其他值給覆寫了,那么參考數減 1,
- 當一個值的參考數為 0 時,就說明沒辦法再訪問到這個值了,因此可以安全地識訓其記憶體了,
- 垃圾回收程式下次運行的時候就會釋放參考數為 0 的值的記憶體
參考計數遇到的問題:
- 回圈參考
function problem() {
let objectA = new Object();
let objectB = new Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}
在這個例子中:
objectA 和 objectB 通過各自的屬性相互參考,
意味著它們的參考數都是 2
objectA 和 objectB 在函式結束后還會存在
因為它們的參考數永遠不會變成 0,
如果函式被多次呼叫
則會導致大量記憶體永遠不會被釋放,
性能
- 垃圾回收程式會周期性運行,如果記憶體中分配了很多變數,則可能造成性能損失,因此垃圾回收的時間調度很重要,
- 在記憶體有限的移動設備上,垃圾回收有可能會明顯拖慢渲染的速度和幀速率,
調度垃圾回收程式方面IE7之前的版本:它的策略是根據分配數,
- 比如分配了 256 個變數、4096 個物件/陣列字面量和陣列槽位(slot),或者 64KB字串,只要滿足其中某個條件,垃圾回收程式就會運行,
- 分配那么多變數的腳本,很可能在其整個生命周期內始終需要那么多變數,結果就會導致垃圾回收程式過于頻繁地運行,嚴重影響性能,
IE7 發布后,JavaScript 引擎的垃圾回收程式被調優為動態改變分配變數、字面量或陣列槽位等會觸發垃圾回收的閾值,
- 如果垃圾回收程式回收的記憶體不到已分配的 15%, 這些變數、字面量或陣列槽位的閾值就會翻倍,
- 如果有一次回收的記憶體達到已分配的 85%,則閾值重置為默認值,
記憶體管理
- 在使用垃圾回收的編程環境中,開發者通常無須關心記憶體管理,
- 但分配給瀏覽器的記憶體通常比分配給桌面軟體的要少很多,分配給移動瀏覽器的就更少了,
- 為了避免運行大量 JavaScript 的網頁耗盡系
統記憶體而導致作業系統崩潰, - 將記憶體占用量保持在一個較小的值可以讓頁面性能更好,
優化記憶體占用的手段:
保證在執行代碼時只保存必要的資料,- 如果資料不再必要,那么把它設定為 null,從而釋放其參考(或者稱為解除參考),
function createPerson(name){
let localPerson = new Object();
localPerson.name = name;
return localPerson;
}
let globalPerson = createPerson("Nicholas");
// 解除 globalPerson 對值的參考
globalPerson = null;
在上面的代碼中:
變數 globalPerson 保存著 createPerson()函式呼叫回傳的值,
在 createPerson()內部,localPerson 創建了一個物件
并給它添加了一個 name 屬性,
然后,localPerson 作為函式值被回傳,
并被賦值給 globalPerson,
localPerson 在 createPerson()執行完成超出背景關系后會自
動被解除參考,不需要顯式處理,
但 globalPerson 是一個全域變數,應該在不再需要時手動解除其參考,最后一行就是這么做的,
使用const和let宣告提升性能:
- const和let都以塊為作用域,相比于使用var,可能會更早地讓垃圾回收程式介入,盡早回收應該回收的記憶體,
記憶體泄露:
- 意外宣告全域變數是最常見但也最容易修復的記憶體泄漏問題,
function setName() {
name = 'Jake';
}
解釋器會把變數 name 當作 window 的屬性來創建
(相當于 window.name = 'Jake'),
在 window 物件上創建的屬性,只要 window 本身不被清理就不會消失,
這個問題很容易解決:
只要在變數宣告前頭加上 var、let 或 const 關鍵字即可
這樣變數就會在函式執行完畢后離開作用域.
- 定時器也可能會悄悄地導致記憶體泄漏,
// 只要定時器一直運行
// 回呼函式中參考的 name 就會一直占用記憶體,
let name = 'Jake';
setInterval(() => {
console.log(name);
}, 100);
- 使用 JavaScript 閉包很容易在不知不覺間造成記憶體泄漏
let outer = function() {
let name = 'Jake';
return function() {
return name;
};
};
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/345773.html
標籤:其他
上一篇:vue跑馬燈效果
