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