可视化分析js的内存分配与回收
之前寫了一篇文章瀏覽器是怎么看閉包的,發現有些讀者對js內存分配與回收懵懵懂懂,理解文章的配圖有些困難,我想主要是因為配圖省略了一些細節。今天專門寫一篇關于js內存分配回收的文章,幫助大家理解js代碼的內存表示。原文備份在這里
數據類型
先嘮叨些基本知識:
- javascript的數據類型分為基本類型和引用類型(對象)?;绢愋头譃槿缦聨追N:
- 數字字面量
- 字符串字面量
- 布爾字面量
- undefined
- null
- 引用類型分為如下幾種
- 通過new的方式生成的對象
- new Object()
- new Array()
- new RegExp()
- new String()
- new Number()
- new Bollean()
- new 自定義對象()
- {},[],正則字面量,函數。
- 通過new的方式生成的對象
簡單對象的內存表示
我們都知道的是,javascript中值類型是在變量所在的內存單元中存放的,而對于引用類型的對象,變量所在的內存單元存放的是堆空間中對象的內存地址。我們還應該知道的是,函數在執行時,局部變量是在??臻g中創建,引用對象是在堆空間中創建的。
我們還是從代碼入手:
var a = 'abc' var b = 123 var c = true var d = undefined var e = null var f = {n: 'test' }這段代碼我們定義了六個全局變量,每個變量賦予不同類型的值,我們發現,a、b、c、d、e基本類型的值占據一個內存單元,而變量f內存儲的是堆中對象的地址。如下圖表示:
變量f中存儲的0x00012345是堆中對象的內存地址。
一切都很容易理解。細心的同學也許會指出,null也是對象,通過typeof null 表達式得到的結果是'object'。關于這個,我想說的是typeof null = 'object' 這個現象是歷史遺留bug。事實上null是空值,并不是對象。
js的類型值有1-3位是表示類型,其它位表示真實值。
000: object. The data is a reference to an object.
也就是說,000開頭的被認為是指向對象引用。由于js中的null是空指針,在大多數平臺上空指針的前兩位是0x00,再加上null的數值表示是0,所以null的前三位是0x000,js引擎會認為它是指向對象的引用,這是一個歷史遺留bug。但事實上,null是空值。詳細解釋參見這里。
說到null,我們還要用圖形表示一下null所起到的作用。對于上面的代碼,我們將引用類型f置為null,該變量將不再指向堆中對象。圖形表示如下:
你會發現,原本f指向堆對象的線消失了,堆中對應的對象不再被f引用。
看到這里,你也許會問:咦,那沒有任何對象指向那個堆對象了,它還占據內存嗎?如果還占據的話,豈不是占著茅坑不那啥嗎?我想,如果能想到這一點,說明你是一個有追求的js開發者。
是的,原本堆空間中的那個對象確實沒有引用了,js引擎會在下一個垃圾回收節點將它回收掉。
為了幫助大家更好的理解內存的分配與釋放,建議大家在看配圖的時候,一定要謹記箭頭的走向,認真看箭頭從哪個對象出發,又是指向哪個對象的。因為箭頭指向代表變量引用,而引用是垃圾回收器辨別內存垃圾的依據。什么是垃圾呢?按照垃圾回收器的理解是,從根對象觸發,沿著箭頭指向,能夠找到的對象,都不應該判定為垃圾。相反,從根對象觸發,沿著箭頭指向,不能夠找到的對象,被判定為垃圾,將在下一個垃圾回收節點回收掉。
那么,與之相伴的是內存泄漏,什么叫內存泄漏呢?通俗一點講,就是某個對象已經不會再被我們用到,但是垃圾回收器卻發現從根對象仍然能夠找到它,所以不認為它是垃圾,因此不會回收它,但是它確實對我們沒有用處。這樣就造成了內存的浪費,這種現象稱作內存泄漏。
理解了判定內存垃圾的方式以及內存泄漏,我們就可以通過畫圖的方式來檢驗代碼是否存在內存泄漏,代碼是否健壯。
復雜對象的內存表示
上面代碼中,f指向的對象是一個簡單對象,只包含一個屬性,如果堆中的是一個復雜對象,又該如何表示呢?讓我們繼續看代碼
var a = {b: 123,c: 'abc',d: true,e: null,f: {h: 'test',j: {k: 567}} }我們定義了一個全局變量a,指向堆內存中的一個復雜對象。如下圖:
全局定義的變量是常駐內存的,為什么常駐內存?我們從垃圾回收的角度分析一下:
- 從根對象window沿著箭頭尋找,首先找到a。
- 通過a能找到堆中最左側的大對象。
- 通過最左側的對象中的變量f,能找到右側下方的對象。
- 通過右側下方對象的變量j,能找到右側上方對象。
所以,全局定義的變量a所關聯的對象是常駐內存的。
再次思考一下,我們如何讓垃圾回收器回收堆空間右側的那兩個對象呢?聰明的你也許想到了
是的,將a.f的指針置為null就可以了。我們從垃圾回收的角度分析一下,a.f = null這段代碼執行以后,f變量中存儲的值變成了null,不再指向右側的兩個對象,按照我們之前的方法,從根對象window開始,沿著箭頭尋找,找不到右側對象,所以右側兩個對象成為內存垃圾,將被GC(垃圾回收器)回收掉。這就是當我們為一個變量賦值null之后,變量在內存中的變化。如下圖所示:
當然,我們也可以為變量賦予其他基礎類型的值,斷開和堆中對象的聯系。
函數定義的內存表示
對于再復雜的對象,大家可以舉一反三。接下來,我們看一下函數的定義:
function say() {var a = '測試'var b = {c: 123} }可以看到函數對象指針在全局變量區,函數本身在堆中存放,函數我只畫了了幾個常見的屬性。細心的你也許發現有個[[Socpes]]的屬性,以后講閉包的時候再對它作詳細介紹,這里只大概介紹一下。
[[Scopes]]屬性是在函數創建的時候附加的屬性,代表該函數的作用域鏈。
函數運行時的內存表示
繼續看一段代碼:
function say() {var a = '測試'var b = {c: 123} } say()很簡單,我們定義一個函數,并執行它,變量的內存圖如下:
函數運行時,局部變量分配在??臻g,此時,外部window對象與棧空間中的變量沒有引用關系。局部變量a是值類型,在棧中存放,局部變量b是引用類型,棧中存放對象在堆中的內存地址。
函數運行結束后的圖示如下:
函數運行結束,局部變量由于沒有外部引用,所以全部釋放,同時堆中的對象也失去了引用,成為內存垃圾,被GC回收掉。
結語
至此,關于js代碼的內存表示就先告一段落,通過畫圖的方式,希望大家能對程序的執行有感性的理解,也希望能幫助大家通過畫圖的方式去判定內存垃圾。另外,大家在看下一篇文章瀏覽器是怎么看閉包的的時候,會發現有一些細節沒有表示,大家不要太過于糾結,只需注意箭頭走向即可。
更多專業前端知識,請上 【猿2048】www.mk2048.com
總結
以上是生活随笔為你收集整理的可视化分析js的内存分配与回收的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: json-schema 可视化编辑器发布
- 下一篇: Bourbon: 让你的sass更简洁