成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Javascript的垃圾回收機制知多少?

開發 前端
本文主要圍繞JS引擎相關知識,來深入了解底層運行邏輯,這對于日常開發維護高性能Javascript代碼以及排查代碼性能問題有著很好的幫助。

[[438972]]

1 寫在前面

本文主要圍繞JS引擎相關知識,來深入了解底層運行邏輯,這對于日常開發維護高性能Javascript代碼以及排查代碼性能問題有著很好的幫助。關于JS引擎底層的垃圾回收機制,后面才能理解內存泄漏的問題以及手動預防和優化,實現對JS內存管理以及內存溢出的處理。

  • 那么我們需要考慮幾個問題:
  • 什么是垃圾回收機制(GC)?
  • 垃圾是怎樣產生的?
  • 為什么要進行垃圾回收?
  • Javascript的內存是如何管理的?
  • Chrome瀏覽器又是如何進行垃圾回收的?

2 內存管理

在Javascript編程中,內存管理大概分成三個步驟,也是內存的生命周期:

  • 分配你所需系統內存的空間
  • 使用分配到的內存進行讀寫操作
  • 不需要使用內存時,將空間進行釋放和歸還

內存的生命周期

與其它手動管理內存的語言不一樣的是,在Javascript中,當我們創建變量時,系統會給對象進行自動分配對應的內存空間以及閑置資源回收,也就是不需要我們手動進行分配。但是,正是因為垃圾回收機制導致開發者有著錯誤的感覺,就是他們不用關心內存管理。

  1. const name = "yichuan";//給字符串分配棧內存 
  2. const age = 18;//給數值分配棧內存 
  3.  
  4. //給對象以及包含的值分配堆內存 
  5. const user = { 
  6.   name"onechuan"
  7.   age: 19 
  8. //給數組以及包含的值分配堆內存 
  9. const arr = ["yichuan","onechuan",18]; 
  10. //給函數對象分配堆內存 
  11. function sum(x,y){ 
  12.   return x + y; 

在前面《Javascript的數據類型知多少》文中,我們知道了基礎數據類型和引用數據類型的分配機制,即:

  • 簡單數據類型內存保存在固定的棧空間中,可直接通過值進行訪問
  • 引用數據類型的值大小不固定,其引用地址保存在棧空間、引用所指向的值保存在堆空間中,需要通過引用進行訪問

棧內存中的基本數據類型,可以直接通過操作系統進行處理,而堆內存中的引用數據類型的值大小不確定,因此需要JS的引擎通過垃圾回收機制進行處理。

3 內存回收機制(GC)

Javascript的V8引擎被限制了內存的使用,因此根據不同操作系統的內存大小會不一樣。

V8引擎最初設計是作為瀏覽器的引擎,并未考慮占據過多的內存空間,隨著web技術工程化的發展,占據了越來越多的內存空間。又由于被v8的會回收機制所限制,這樣就引起了js執行的線程被掛起,會影響當前執行的頁面應用性能。

垃圾回收算法:就是垃圾收集器按照固定的時間間隔,周期性地尋找那些不再使用的變量,然后將其清楚或釋放內存。但是垃圾回收算法是個不完美的方案,因為某塊內存是否還可用,屬于不可預判的問題,也就意味著單純依靠算法是解決不了的。還有為什么不是實時的找出無用內存并釋放呢?其實很簡單,實時開銷太大了。

我們知道了垃圾是如何產生的,那么我們應該如何清除呢?在瀏覽器的發展歷史上有兩種解決策略:

  • 標記清除
  • 引用計數

標記清除

標記清除分為:標記階段和清除階段。

首先它會遍歷堆內存上所有的對象,分別給它們打上標記,然后在代碼執行過程結束之后,對所使用過的變量取消標記。在清除階段再把具有標記的內存對象進行整體清除,從而釋放內存空間。

整個標記清除算法大致過程就像下面這樣

  • 垃圾收集器在運行時會給內存中的所有變量都加上一個標記
  • 然后從各個根對象開始遍歷,把還在被上下文變量引用的變量標記去掉標記
  • 清理所有帶有標牌機的變量,銷毀并回收它們所占用的內存空間
  • 最后垃圾回收程序做一次內存清理

使用標記清除策略的最重要的優點在于簡單,無非是標記和不標記的差異。通過標記清除之后,剩余的對象內存位置是不變的,也會導致空閑內存空間是不連續的,這就造成出現內存碎片的問題。內存碎片多了后,如果要存儲一個新的需要占據較大內存空間的對象,就會造成影響。對于通過標記清除產生的內存碎片,還是需要通過標記整理策略進行解決。

簡而言之:

  • 優點:簡單
  • 缺點:內存碎片化、分配速度慢

標記整理

經過標記清除策略整理后,老生代內存中因此產生了許多內存碎片,如果不進行清理內存碎片,就會對存儲造成影響。

標記整理(Mark-Compact)算法 就可以有效地解決標記清除的兩個缺點。它的標記階段和標記清除算法沒有什么不同,只是標記結束后,標記整理算法會將活著的對象(即不需要清理的對象)向內存的一端移動,最后清理掉邊界的內存。

引用計數

引用計數是一種不常見的垃圾回收策略,其思路就是對每個值都記錄其的引用次數。具體的:

  • 當變量進行聲明并賦值后,值的引用數為1。
  • 當同一個值被賦值給另一個變量時,引用數+1
  • 當保存該值引用的變量被其它值覆蓋時,引用數-1
  • 當該值的引用數為0時,表示無法再訪問該值了,此時就可以放心地將其清除并回收內存。
  1. let a = new Object()  // 此對象的引用計數為 1(a引用) 
  2. let b = a   // 此對象的引用計數是 2(a,b引用) 
  3. a = null    // 此對象的引用計數為 1(b引用) 
  4. b = null    // 此對象的引用計數為 0(無引用) 
  5. ...   // GC 回收此對象 

這種回收策略看起來很方便,但是當其進行循環引用時就會出現問題,會造成大量的內存不會被釋放。當函數結束后,兩個對象都不在作用域中,A 和 B 都會被當作非活動對象來清除掉,相比之下,引用計數則不會釋放,也就會造成大量無用內存占用,這也是后來放棄引用計數,使用標記清除的原因之一。

4 V8對于垃圾回收機制的優化

大多數瀏覽器都是基于標記清除算法,不同的只是在運行垃圾回收的頻率具有差異。V8 對其進行了一些優化加工處理,那接下來我們主要就來看 V8 中對垃圾回收機制的優化。

分代式垃圾回收

V8 的垃圾回收策略主要基于分代式垃圾回收機制,V8 中將堆內存分為新生代和老生代兩區域,采用不同的垃圾回收器也就是不同的策略管理垃圾回收。

新生代的對象為存活時間較短的對象,簡單來說就是新產生的對象,通常只支持 1~8M 的容量,而老生代的對象為存活事件較長或常駐內存的對象,簡單來說就是經歷過新生代垃圾回收后還存活下來的對象,容量通常比較大。

V8 整個堆內存的大小就等于新生代加上老生代的內存,對于新老兩塊內存區域的垃圾回收,V8 采用了兩個垃圾回收器來管控。

新生代和老生代

新生代內存回收

在64操作系統下分配為32MB,因為新生代中的變量存活時間短,不太容易產生太大的內存壓力,因此不夠大也是能夠理解。

對于新生代內存的回收,通常是通過Scavenge 的算法進行垃圾回收,就是將新生代內存進行一分為二,正在被使用的內存空間稱為使用區,而限制狀態的內存空間稱為空閑區。

新生代內存回收的原理是:

  • 新加入的對象都會存放在使用區,當使用區快寫滿時就進行一次垃圾清理操作。
  • 在開始進行垃圾回收時,新生代回收器會對使用區內的對象進行標記
  • 標記完成后,需要對使用區內的活動對象拷貝到空閑區進行排序
  • 而后進入垃圾清理階段,將非活動對象占用的內存空間進行清理
  • 最后對使用區和空閑區進行交換,使用區->空閑區,空閑區->使用區

新生代中的變量如果經過回收之后依然一直存在,那么會放入到老生代內存中,只要是已經經歷過一次Scavenge算法回收的,就可以晉升為老生代內存的對象。

老生代內存回收

當然,Scavenge算法也有其適用場景范圍,對于內存空間較大的就不適合使用Scavenge算法。此時應該使用Mark-Sweep(標記清除)和Mark-Compact(標記整理)的策略進行老生代內存中的垃圾回收。

首先是標記階段,從一組根元素開始,遞歸遍歷這組根元素,遍歷過程中能到達的元素稱為活動對象,沒有到達的元素就可以判斷為非活動對象。清除階段老生代垃圾回收器會直接將非活動對象,也就是數據清理掉。

同樣的標記清除策略會產生內存碎片,因此還需要進行標記整理策略進行優化。

5 內存泄漏與優化

內存泄漏,指在JS中已經分配內存地址的對象由于長時間未進行內存釋放或無法清除,造成了長期占用內存,使得內存資源浪費,最終導致運行的應用響應速度變慢以及最終崩潰的情況。

在代碼中創建對象和變量時會占據內存,但是JS基于自己的內存回收機制是可以確定哪些變量不再需要,并將其進行清除。但是,當你的代碼中存在邏輯缺陷時,你以為你已經不需要,但是程序中還存在這引用,這就導致程序運行完后并沒有進行合適的回收所占有的內存空間。運行時間越長占用內存越多,隨之出現的問題就是:性能不佳、高延遲、頻繁崩潰。

造成內存泄漏的常見原因有:

  • 過多的緩存。及時清理過多的緩存。
  • 濫用閉包。盡量避免使用大量的閉包。
  • 定時器或回調太多。與節點或數據相關聯的計時器不再需要時,DOM節點對象可以清除,整個回調函數也不再需要。可是,計時器回調函數仍然沒有被回收(計時器停止才會被回收)。當不需要setTimeout或setInterval時,定時器沒有被清除,定時器的糊掉函數以及其內部依賴的變量都不能被回收,會造成內存泄漏。解決方法:在定時器完成工作時,需要手動清除定時器。
  • 太多無效的DOM引用。DOM刪除了,但是節點的引用還在,導致GC無法實現對其所占內存的回收。解決方法:給刪除的DOM節點引用設置為null。
  • 濫用全局變量。全局變量是根據定義無法被垃圾回收機制進行收集的,因此需要特別注意臨時存儲和處理大量信息的全局變量。如果必須使用全局變量來存儲數據,請確保將其指定為null或在完成后重新分配它。解決方法:使用嚴格模式。
  • 從外到內執行appendChild。此時即使調用removeChild也無法進行釋放內存。解決方法:從內到外appendChild。
  • 反復重寫同一個數據會造成內存大量占用,但是IE瀏覽器關閉后會被釋放。
  • 注意程序邏輯,避免編寫『死循環』之類的代碼。
  • DOM對象和JS對象相互引用。

關于內存泄漏,如果你想要更好地排查以及提前避免問題的發生,最好的解決方法是通過熟練使用Chrome的內存剖析工具,多分析多定位Chrome幫你分析保留的內存快照,來查看持續占用大量內存的對象。

6 參考文章

  • 《「硬核JS」你真的了解垃圾回收機制嗎》
  • 《Javascript核心原理精講》
  • 《Javascript高級程序設計》

7 寫在后面

本篇文章聊了JS的內存管理機制,以及v8垃圾回收機制,最后我們也分析了一些日常編碼中經常遇到內存泄漏問題,根據不同的原因給出對應的解決方案。

【編輯推薦】

 

責任編輯:姜華 來源: 前端萬有引力
相關推薦

2010-09-25 15:33:19

JVM垃圾回收

2017-03-03 09:26:48

PHP垃圾回收機制

2017-08-17 15:40:08

大數據Python垃圾回收機制

2011-07-04 16:48:56

JAVA垃圾回收機制GC

2009-06-23 14:15:00

Java垃圾回收

2017-06-12 17:38:32

Python垃圾回收引用

2021-11-05 15:23:20

JVM回收算法

2010-09-16 15:10:24

JVM垃圾回收機制

2010-09-25 15:26:12

JVM垃圾回收

2021-05-27 21:47:12

Python垃圾回收

2010-10-13 10:24:38

垃圾回收機制JVMJava

2015-06-04 09:38:39

Java垃圾回收機

2011-06-28 12:39:34

Java垃圾回收

2017-10-12 12:41:11

PHP圾回收機制變量容器

2009-12-09 17:28:34

PHP垃圾回收機制

2011-07-04 13:12:04

JavaScript

2024-10-28 13:18:54

2023-03-26 22:48:46

Python引用計數內存

2011-01-18 14:06:58

JavaScriptweb

2011-06-28 10:19:40

C#開發
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91porn成人精品 | 久久久久久成人 | 精品中文字幕一区二区三区 | 伊人爽 | 在线91| 色婷综合网| 日韩中文字幕在线播放 | 在线午夜 | 福利网址| 最新国产精品精品视频 | 日本中文字幕一区 | 超碰97av| 日本不卡一区 | 免费视频一区二区 | 伊人色综合久久天天五月婷 | 亚洲成人福利视频 | www.久久| 日本一区二区三区在线观看 | 亚洲精色 | 亚洲精品一区二区三区丝袜 | 日本特黄特色aaa大片免费 | 久久久精品一区二区三区 | 日韩av成人| 男女视频免费 | 干干干操操操 | 亚洲国产精品99久久久久久久久 | 欧美一区在线视频 | 狠狠av| 欧美激情精品久久久久久变态 | 精品伦精品一区二区三区视频 | 精品久久久久久18免费网站 | 欧美日韩亚洲系列 | 日韩成人一区二区 | 日本不卡一区二区三区 | 亚洲欧美激情国产综合久久久 | 91偷拍精品一区二区三区 | 国产视频一区在线观看 | 国产99久久精品一区二区永久免费 | 日韩资源 | 日本成人中文字幕 | 欧美视频一区二区三区 |