渲染,就是根據描述或者定義構建數學模型,生成圖像的過程。node
瀏覽器內核主要的做用是將頁面轉變成可視化/可聽化的多媒體結果,一般也被稱爲渲染引擎。將HTML/CSS/JavaScript文本及其餘相應的媒體類型資源文件轉換成網頁。算法
上圖中實線框內模塊是全部移植的共有部分,虛線框內不一樣的廠商能夠本身實現。下面進行介紹:數據庫
WebCore 是各個瀏覽器使用的共享部分,包括HTML解析器、CSS解析器、DOM和SVG等。編程
JavaScriptCore是WebKit的默認引擎,在谷歌系列產品中被替換爲V8引擎。數組
WebKit Ports是WebKit中的非共享部分,因爲平臺差別、第三方庫和需求的不一樣等緣由,不一樣的移植致使了WebKit不一樣版本行爲不一致,它是不一樣瀏覽器性能和功能差別的關鍵部分。瀏覽器
WebKit嵌入式編程接口,供瀏覽器調用,與移植密切相關,不一樣的移植有不一樣的接口規範。緩存
瀏覽器內核的層次結構,渲染引擎解析網頁資源,調用第三方庫進行渲染繪製。閉包
從上面兩圖能夠大概看出渲染引擎的內部組成和大模塊。WebCore,負責解析HTML、CSS生成DOM樹等渲染進程,js引擎負責解析和執行js邏輯進程。函數
JavaScript引擎和渲染引擎的關係如上圖所示。渲染引擎使用JS引擎的接口來處理邏輯代碼並獲取結果。JS引擎經過橋接接口訪問渲染引擎中的DOM及CSSOM(性能低下)。工具
JavaScript本質上是一種解釋型語言,與編譯型語言不一樣的是它須要一遍執行一邊解析,而編譯型語言在執行時已經完成編譯,可直接執行,有更快的執行速度(如上圖所示)。
上圖描述了JS代碼執行的過程。具體過程先不看,一個JS引擎主要包括如下幾個部分:
編譯器。將源代碼通過詞法分析,語法分析(你不知的js待查)編譯成抽象語法樹,在某些引擎中還包含將抽象語法樹轉化成字節碼。
解釋器。在某些引擎中,解釋器主要是接收字節碼,解釋執行這個字節碼,同時也依賴垃圾回收機制等。
JIT工具。將字節碼或者抽象語法樹轉換成本地代碼。
垃圾回收器和分析工具(Profiler)。負責垃圾回收和收集引擎中的信息,幫助改善引擎的性能和功效。
Js語言中,只有基本數據類型Boolean、Number、String、Null、Undefined,其餘都是對象。
在V8中,數據的表示分紅兩個部分。第一個部分是數據的實際內容,它們是變長的,並且內容的類型也不同,如String、對象等;第二部分是數據的句柄,大小是固定的,包含指向第一部分數據的指針。除了極少數的數據例如整型數據,其餘的內容都是從堆中申請內存來存儲,由於句柄自己可以存儲整型,同時也能快速訪問。
V8須要進行垃圾回收,並須要移動這些數據內容,若是直接使用指針的話就會出問題或者須要比較大的開銷。使用句柄就不存在這些問題,只須要修改句柄中的指針便可,使用者使用的仍是句柄,它自己沒有發生變化。
由上圖能夠看出,一個Handle對象的大小是4字節(32位機器)或者8字節(64位機器);不一樣於JavascriptCore引擎,後者是使用8個字節來表示數據的句柄。小整數(只有31位可使用)直接在Value_中獲取值,而無須從堆中分配。
由於堆中存放的對象都是4字節對齊的,因此指向它們的指針的最後兩位都是00,這兩位實際上是不須要的。在V8中,它們被用來表示句柄中包含數據的類型。
對象句柄的實如今V8中包含3個成員。第一個是隱藏類指針,爲對象建立的隱藏類;第二個指向這個對象包含的屬性值;第三個指向這個對象包含的元素。
包括兩個階段:編譯和執行。還有一個重要的特色就是延遲思想,使得不少js代碼的編譯直到運行時被調用纔會發生(以函數單位),減小時間開銷。
Js代碼通過編譯器,生成抽象語法樹,再經過JIT全代碼生成器直接生成本地代碼。減小抽象樹到字節碼的轉換時間。
生成本地代碼後,爲了性能考慮,經過 數據分析器 來採集一些信息,以幫助決策哪些本地代碼須要優化,以生成效率更高的本地代碼,這是一個逐步改進的過程。
編譯器會作比較樂觀和大膽的預測,就是認爲這些代碼比較穩定,變量類型不會發生改變,來生成高效的本地代碼。當引擎發現一些變量的類型已經發生變化的時候,V8會將優化回滾到以前的通常狀況。
如上,函數ABC被調用不少次以後,數據分析器認爲函數內的代碼的類型都已經被獲知了,可是當對於unkonwn的變量發生賦值是,V8只能將代碼回滾到一個通用的狀態。
優化回滾是一個很費時的操做,並且會將以前優化的代碼恢復到一個沒有特別優化的代碼,這是一個很是不高效的過程。
V8使用類和偏移位置思想,將原本須要字符串匹配來查找屬性值的算法改進爲,使用相似C++編譯器的偏移位置的機制來實現,這就是隱藏類。
隱藏類根據對象擁有相同的屬性名和屬性值的狀況,分爲不一樣的組(類型)。對於相同的組,將這些屬性名和對應的偏移位置保存在一個隱藏類中,組內的對象共享該信息。同時,也能夠識別屬性不一樣的對象。
如圖,使用構造函數建立了兩個對象a、b。這兩個對象包含相同的屬性名,在V8中它們被歸爲同一個組,也就是隱藏類,這些屬性在隱藏類中有相同的偏移值。對象a、b能夠共享這個分組的信息,當訪問這些對象的時候,根據隱藏類的偏移值就能夠知道它們的位置並進行訪問。
由於JavaScript是動態類型語言,因此當加入代碼 d.z = 2。那麼b對象所對應的將是一個新的隱藏類,這樣a、b將屬於不一樣的組。
function add(a) { return a.x };
訪問對象屬性基本過程爲:獲取隱藏類的地址,根據屬性名查找偏移值,計算該屬性的堆內存地址。這一過程仍是比較耗費時間,實際上會用到緩存機制,叫作內嵌緩存。
基本思想是將使用以前查找的結果(隱藏類和偏移值)緩存起來,再次訪問時能夠避免屢次哈希表查找的問題。
注意:當對象內的屬性值出現多個類型是,那麼緩存失誤的機率就會高不少。退回到以前的方式來查找哈希表。
主要講兩點:一、內存的劃分使用二、對於JS代碼的垃圾回收機制
管理一系列的小塊內存,這些小內存的生命週期相似,可使用一個Zone對象。
Zone對象先對本身申請一塊內存,而後管理和分配一些小內存。當一塊小內存被分配以後,不能被Zone回收,只能一次性回收Zone分配的全部小內存。例如:抽象語法樹的內存分配和使用,在構建以後,會生成本地代碼,而後其內存被一次性所有收回,效率很是高。
可是有一個嚴重的缺陷,當一個過程須要不少內存,Zone將須要分配大量的內存,卻又不能及時回收,會致使內存不足狀況。
V8使用堆來管理JavaScript使用的數據、以及生成的代碼、哈希表等。爲了更方便地實現垃圾回收,同不少虛擬機同樣,V8將堆分紅三個部分。年輕代、年老代、和大對象。
年輕分代:爲新建立的對象分配內存空間,常常須要進行垃圾回收。爲方便年輕分代中的內容回收,可再將年輕分代分爲兩半,一半用來分配,另外一半在回收時負責將以前還須要保留的對象複製過來。
年老分代:根據須要將年老的對象、指針、代碼等數據保存起來,較少地進行垃圾回收。
大對象:爲那些須要使用較多內存對象分配內存,固然一樣可能包含數據和代碼等分配的內存,一個頁面只分配一個對象。
當在代碼中聲明變量並賦值時,所使用對象的內存就分配在堆中。若是已申請的堆空閒內存不夠分配新的對象,將繼續申請堆內存,知道堆的大小達到V8的限制爲止。
V8的內存使用限制:64位系統中約爲1.4G,32位系統中約爲0.7G。在瀏覽器頁面中足夠使用;在node中則會有不足,致使Node沒法直接操做大內存對象,在打個node進程的狀況下,沒法充分利用計算機的內存資源。
內存的限制有兩方面緣由,防止瀏覽器的一個頁面佔用太多系統內存資源,另外一方面,V8垃圾回收的效率問題。對於1.5G內存,作一次小的垃圾回收須要50毫秒以上,作一次全量的回收甚至要1秒以上,這個過程會阻塞JS線程的執行。
V8的垃圾回收策略主要基於分代式回收機制。按對象的存活時間將內存的垃圾回收進行不一樣的分代,而後分別對不一樣的內存使用更高效的算法。
一、新生代Scavenge(清除)算法
主要採用Cheney(人名)算法。一種採用複製的方式實現的垃圾回收算法。
一、將新生代堆內存分一爲二,每一部分空間稱爲semispace。其中一個處於使用之中的稱爲from空間,另外一個處於閒置稱爲to空間。
二、當咱們分配對象時,先是在From空間中進行分配。
三、垃圾回收時,檢查from空間內的存活對象,一是否經歷過清除回收,二to空間是否已經使用了25%(保證新分配有足夠的空間)。
. 四、將這些存活對象複製到to空間中。非存活對象佔用的空間將會被釋放。
五、完成複製後,from空間與to空間角色發生對換。
注:實際使用的堆內存是新生代中的兩個semispace空間大小,和老生代所用內存大小之和。
如何判斷對象是否存活呢?做用域?是一套存儲和查詢變量的規則。這套規則決定了內存裏對象可否訪問。
特色:
清除算法是典型的犧牲空間換取時間的算法,沒法大規模地應用到全部回收中,卻很是適合應用在新生代生命週期短的變量。
一、 標記階段遍歷堆中的全部對象,並標記活着的對象
二、 清除階段,只清除沒有被標記的對象。
最大的問題是,在進行一次標記清除以後會出現不連續的狀態。這種內存碎片會對後續的內存分配形成問題。極可能須要分配一個大對象時,全部的碎片空間都沒法完成,就會提早觸發垃圾回收,而此次全量回收是沒必要要的。
在標記清除的基礎上發展而來,在整理的過程當中
一、 將活着的對象往一段移動
二、 移動完成後,直接清理掉邊界外的內存
垃圾回收的過程都須要將應用邏輯暫停下來。
爲了下降全量回收帶來的停頓時間,在標記階段,將本來一口氣要完成的動做改成增量標記。垃圾回收與應用邏輯交替執行到標記階段完成。最大停頓時間較少的1/6左右.
後續還引入了延遲清理與增量整理,讓清理和整理動做也變成增量式的。
函數在每次被調用時會建立對應的做用域(自身的執行環境,和變量對象),做用域中聲明的局部變量分配在改做用域中。執行結束後,做用域銷燬,作死亡標記,在下次垃圾回收時被釋放。
變量的主動釋放,解除引用關係:
若是變量是全局變量,因爲全局做用域須要直到進程退出才能釋放,致使引用的對象常駐內存(老生代)。能夠經過delete來刪除引用關係,或者將變量從新賦值。可是在V8中經過delete刪除對象的屬性有可能干擾V8的優化。
閉包:
當函數執行結束,但做用域(變量對象)仍須要被引用時,其變量對象不能被標記失效,佔用的內存空間不會獲得清除釋放。除非再也不有引用,纔會逐步釋放。
在正常的js執行中,沒法當即回收的內存有閉包和全局變量這兩種狀況。要避免這些變量無限制地增長,致使老生代中的對象增多,甚至內存泄漏。
實質:應當回收的對象出現意外,而沒有被回收,變成了常駐在老生代中的對象。
一般,形成內存泄漏的緣由有以下:
一、 緩存
緩存無限制增加,長期存在於老生代,佔據大量內存。
策略:對緩存增長數量和有效期限制。
使用Redis等進程外緩存,不佔用V8緩存限制,進程間能夠共享緩存。
二、 隊列消費不及時
Js能夠經過隊列(數組對象)來完成許多特殊的需求。在消費者—生產者模型中常常充當中間產物。當消費速度低於生成速度時,將會造成堆積。
如:日誌記錄,採用低效率的數據庫寫入時,可能會是寫入事件堆積。
策略:使用高效的消費方式。監控隊列的長度。
三、 做用域得不到釋放
大量閉包的使用,要注意釋放做用域。