GC 演算法
1. 引用計數
核心思想:設定引用數,判斷當前引用是否為0
優點:
- 發現垃圾時,立即回收
- 最大限度減少程式暫停
缺點:
- 無法回收迴圈引用的物件
- 時間開銷大(需要監聽計數值的變化)
function fn() {
const obj1 = {}
const obj2 = {}
}
2. 標記清除
核心思想:分標記和清除兩個階段
缺點:
1、 空間碎片化(回收物件在地址上的不連續)
2、 不能立即回收
3. 標記整理
標記,然後將活動物件的地址進行整理,儘量使得活動物件地址連續
4. 分代回收
回收新生代物件(存活時間較短的變數物件)
- 回收過程採用複製演算法+標記整理
- 新生代記憶體區分為兩個等大小的空間
- 使用空間為From,用於儲存活動物件, 空閒空間為To,用於儲存From中的活動物件
- 活動物件儲存於From空間
- 標記整理後將活動物件拷貝至To
- From 與To交換空間完成釋放
回收細節:From和To之間的拷貝過程可能出現晉升(將新生代物件移動至老生代)下面兩種情況會產生晉升
- 一輪GC後還存活的新生代需要晉升
- To空間的使用率超過了25%
回收老生代物件
空間大,無法使用複製演算法,使用增量標記法優化效率
- 標記清除
- 標記整理: 使得活動物件的地址連續
- 標記增量: 將原本標記整理的工作拆分為多個小的標記工作,防止程式阻塞
V8
v8垃圾回收策略
v8記憶體有上限
- 採用分代回收思想
- 記憶體分為新生代,老生代
- 針對不同物件,採用不同演算法
常用演算法
- 分代回收
- 空間複製
- 標記清除
- 標記整理
- 標記增量
頻繁GC(垃圾回收)會帶來什麼
- GC工作時,應用程式是停止的
- 頻繁且過長的GC會導致應用假死
- 使用者使用中感知應用卡頓
如何確定是否頻繁回收垃圾:
- Timeline中頻繁的上升下降
- 工作管理員中資料頻繁的增加減小
堆快照查詢分離dom,這個都是無用的dom引用,需要使用堆快照功能超找
detachedNode
v8引擎執行流程
程式碼優化 函式巢狀會導致v8進行多次的預解析,因此不要巢狀太深
堆疊操作
- js執行環境
- 執行環境棧(ECStack, execution context stack)
- 執行上下文
- VO(G),全域性變數物件
- EC(G),全域性執行上下文
基本型別 (棧操作)
- 基本資料型別是按值進行操作
- 基本資料型別值是存放在棧區的
- 無論我們當前看到的棧記憶體,還是後續引用資料型別會使用的堆記憶體都屬於計算機記憶體
- GO(全域性物件)
引用型別 (堆操作)
函式執行
- 確定作用域鏈(當前執行上下文,上級執行上下文)
- 確定this指向,如果是在全域性作用域那麼就是window
- 初始化arguments物件
- 形參賦值
- 變數提升(var宣告的關鍵字,或者函式內部的function宣告)
- 執行程式碼
- 如果函式內部沒有被其他地方所引用,那麼就會進行出棧操作,釋放棧記憶體
閉包理解
function fn() { var a = 1 return function(b) { console.log(a + b) } } const f = fn() f(5) f10()
- 閉包是一種機制,通過私有的上下文來保護其中的變數的一種機制
- 也可以認為,在建立的某一個執行上下文不被釋放的時候 就形成了閉包
- 保護、儲存資料
迴圈新增事件,看閉包使用的取捨
如果使用閉包,進行多個事件的註冊,因為閉包的原因,會申請一個對地址來儲存,如果事件越多,那麼申請的記憶體越多,因此需要用到事件代理
變數的申明
變數申明最好是放在區域性變數,否則程式碼在執行的時候 查詢作用域鏈上的變數會比較花時間, 對比下面兩段程式碼 使用的是 jsbench這個工具
var i, str = ""
function parseDom() {
for(i = 0; i < 100; i++) {
str += i
}
}
parseDom()
function parseDom() {
let str = ''
for(let i = 0; i < 100; i++) {
str += i
}
}
parseDom()
變數快取
- 陣列長度
- 多次使用的dom變數等
減少判斷層級
- 儘可能的減少判斷的層級,如果有巢狀判斷,看看是否可以將判斷條件往外提