摘要:一前言的垃圾回收機(jī)制使用垃圾回收機(jī)制來(lái)自動(dòng)管理內(nèi)存。垃圾回收器只會(huì)針對(duì)新生代內(nèi)存區(qū)老生代指針區(qū)以及老生代數(shù)據(jù)區(qū)進(jìn)行垃圾回收。分別對(duì)新生代和老生代使用不同的垃圾回收算法來(lái)提升垃圾回收的效率。
V8 實(shí)現(xiàn)了準(zhǔn)確式 GC,GC 算法采用了分代式垃圾回收機(jī)制。因此,V8 將內(nèi)存(堆)分為新生代和老生代兩部分。
一、前言V8的垃圾回收機(jī)制:JavaScript使用垃圾回收機(jī)制來(lái)自動(dòng)管理內(nèi)存。垃圾回收是一把雙刃劍,其好處是可以大幅簡(jiǎn)化程序的內(nèi)存管理代碼,降低程序員的負(fù)擔(dān),減少因 長(zhǎng)時(shí)間運(yùn)轉(zhuǎn)而帶來(lái)的內(nèi)存泄露問(wèn)題。
但使用了垃圾回收即意味著程序員將無(wú)法掌控內(nèi)存。ECMAScript沒(méi)有暴露任何垃圾回收器的接口。我們無(wú)法強(qiáng)迫其進(jìn) 行垃圾回收,更無(wú)法干預(yù)內(nèi)存管理
內(nèi)存管理問(wèn)題:在瀏覽器中,Chrome V8引擎實(shí)例的生命周期不會(huì)很長(zhǎng)(誰(shuí)沒(méi)事一個(gè)頁(yè)面開(kāi)著幾天幾個(gè)月不關(guān)),而且運(yùn)行在用戶的機(jī)器上。如果不幸發(fā)生內(nèi)存泄露等問(wèn)題,僅僅會(huì) 影響到一個(gè)終端用戶。且無(wú)論這個(gè)V8實(shí)例占用了多少內(nèi)存,最終在關(guān)閉頁(yè)面時(shí)內(nèi)存都會(huì)被釋放,幾乎沒(méi)有太多管理的必要(當(dāng)然并不代表一些大型Web應(yīng)用不需 要管理內(nèi)存)。但如果使用Node作為服務(wù)器,就需要關(guān)注內(nèi)存問(wèn)題了,一旦內(nèi)存發(fā)生泄漏,久而久之整個(gè)服務(wù)將會(huì)癱瘓(服務(wù)器不會(huì)頻繁的重啟)。
二、chrome內(nèi)存限制
2.1存在限制Chrome限制了所能使用的內(nèi)存極限(64位為1.4GB,32位為1.0GB),這也就意味著將無(wú)法直接操作一些大內(nèi)存對(duì)象。
2.2為何限制Chrome之所以限制了內(nèi)存的大小,表面上的原因是V8最初是作為瀏覽器的JavaScript引擎而設(shè)計(jì),不太可能遇到大量?jī)?nèi)存的場(chǎng)景,而深層次的原因 則是由于V8的垃圾回收機(jī)制的限制。由于V8需要保證JavaScript應(yīng)用邏輯與垃圾回收器所看到的不一樣,V8在執(zhí)行垃圾回收時(shí)會(huì)阻塞 JavaScript應(yīng)用邏輯,直到垃圾回收結(jié)束再重新執(zhí)行JavaScript應(yīng)用邏輯,這種行為被稱為“全停頓”(stop-the-world)。 若V8的堆內(nèi)存為1.5GB,V8做一次小的垃圾回收需要50ms以上,做一次非增量式的垃圾回收甚至要1秒以上。這樣瀏覽器將在1s內(nèi)失去對(duì)用戶的響 應(yīng),造成假死現(xiàn)象。如果有動(dòng)畫效果的話,動(dòng)畫的展現(xiàn)也將顯著受到影響
三、chrome V8的堆構(gòu)成V8的堆其實(shí)并不只是由老生代和新生代兩部分構(gòu)成,可以將堆分為幾個(gè)不同的區(qū)域:
1、新生代內(nèi)存區(qū):大多數(shù)的對(duì)象被分配在這里,這個(gè)區(qū)域很小但是垃圾回特別頻繁;
2、老生代指針區(qū):屬于老生代,這里包含了大多數(shù)可能存在指向其他對(duì)象的指針的對(duì)象,大多數(shù)從新生代晉升的對(duì)象會(huì)被移動(dòng)到這里;
3、老生代數(shù)據(jù)區(qū):屬于老生代,這里只保存原始數(shù)據(jù)對(duì)象,這些對(duì)象沒(méi)有指向其他對(duì)象的指針;
4、大對(duì)象區(qū):這里存放體積超越其他區(qū)大小的對(duì)象,每個(gè)對(duì)象有自己的內(nèi)存,垃圾回收其不會(huì)移動(dòng)大對(duì)象;
5、代碼區(qū):代碼對(duì)象,也就是包含JIT之后指令的對(duì)象,會(huì)被分配在這里。唯一擁有執(zhí)行權(quán)限的內(nèi)存區(qū);
6、Cell區(qū)、屬性Cell區(qū)、Map區(qū):存放Cell、屬性Cell和Map,每個(gè)區(qū)域都是存放相同大小的元素,結(jié)構(gòu)簡(jiǎn)單。
每個(gè)區(qū)域都是由一組內(nèi)存頁(yè)構(gòu)成,內(nèi)存頁(yè)是V8申請(qǐng)內(nèi)存的最小單位,除了大對(duì)象區(qū)的內(nèi)存頁(yè)較大以外,其他區(qū)的內(nèi)存頁(yè)都是1MB大小,而且按照1MB對(duì) 齊。內(nèi)存頁(yè)除了存儲(chǔ)的對(duì)象,還有一個(gè)包含元數(shù)據(jù)和標(biāo)識(shí)信息的頁(yè)頭,以及一個(gè)用于標(biāo)記哪些對(duì)象是活躍對(duì)象的位圖區(qū)。另外每個(gè)內(nèi)存頁(yè)還有一個(gè)多帶帶分配在另外內(nèi) 存區(qū)的槽緩沖區(qū),里面放著一組對(duì)象,這些對(duì)象可能指向其他存儲(chǔ)在該頁(yè)的對(duì)象。垃圾回收器只會(huì)針對(duì)新生代內(nèi)存區(qū)、老生代指針區(qū)以及老生代數(shù)據(jù)區(qū)進(jìn)行垃圾回收。
四、chrome V8的垃圾回收機(jī)制 4.1如何判斷回收內(nèi)容如何確定哪些內(nèi)存需要回收,哪些內(nèi)存不需要回收,這是垃圾回收期需要解決的最基本問(wèn)題。我們可以這樣假定,一個(gè)對(duì)象為活對(duì)象當(dāng)且僅當(dāng)它被一個(gè)根對(duì)象 或另一個(gè)活對(duì)象指向。根對(duì)象永遠(yuǎn)是活對(duì)象,它是被瀏覽器或V8所引用的對(duì)象。被局部變量所指向的對(duì)象也屬于根對(duì)象,因?yàn)樗鼈兯诘淖饔糜驅(qū)ο蟊灰暈楦鶎?duì) 象。全局對(duì)象(Node中為global,瀏覽器中為window)自然是根對(duì)象。瀏覽器中的DOM元素也屬于根對(duì)象。
4.2如何識(shí)別指針和數(shù)據(jù)垃圾回收器需要面臨一個(gè)問(wèn)題,它需要判斷哪些是數(shù)據(jù),哪些是指針。由于很多垃圾回收算法會(huì)將對(duì)象在內(nèi)存中移動(dòng)(緊湊,減少內(nèi)存碎片),所以經(jīng)常需要進(jìn)行指針的改寫:
目前主要有三種方法來(lái)識(shí)別指針:
保守法:將所有堆上對(duì)齊的字都認(rèn)為是指針,那么有些數(shù)據(jù)就會(huì)被誤認(rèn)為是指針。于是某些實(shí)際是數(shù)字的假指針,會(huì)背誤認(rèn)為指向活躍對(duì)象,導(dǎo)致內(nèi)存泄露(假指針指向的對(duì)象可能是死對(duì)象,但依舊有指針指向——這個(gè)假指針指向它)同時(shí)我們不能移動(dòng)任何內(nèi)存區(qū)域。
編譯器提示法:如果是靜態(tài)語(yǔ)言,編譯器能夠告訴我們每個(gè)類當(dāng)中指針的具體位置,而一旦我們知道對(duì)象時(shí)哪個(gè)類實(shí)例化得到的,就能知道對(duì)象中所有指針。這是JVM實(shí)現(xiàn)垃圾回收的方式,但這種方式并不適合JS這樣的動(dòng)態(tài)語(yǔ)言
標(biāo)記指針?lè)ǎ哼@種方法需要在每個(gè)字末位預(yù)留一位來(lái)標(biāo)記這個(gè)字段是指針還是數(shù)據(jù)。這種方法需要編譯器支持,但實(shí)現(xiàn)簡(jiǎn)單,而且性能不錯(cuò)。V8采用的是這種方式。V8將所有數(shù)據(jù)以32bit字寬來(lái)存儲(chǔ),其中最低一位保持為0,而指針的最低兩位為01
4.3 V8回收策略自動(dòng)垃圾回收算法的演變過(guò)程中出現(xiàn)了很多算法,但是由于不同對(duì)象的生存周期不同,沒(méi)有一種算法適用于所有的情況。所以V8采用了一種分代回收的策 略,將內(nèi)存分為兩個(gè)生代:新生代和老生代。
新生代的對(duì)象為存活時(shí)間較短的對(duì)象,老生代中的對(duì)象為存活時(shí)間較長(zhǎng)或常駐內(nèi)存的對(duì)象。分別對(duì)新生代和老生代使用 不同的垃圾回收算法來(lái)提升垃圾回收的效率。對(duì)象起初都會(huì)被分配到新生代,當(dāng)新生代中的對(duì)象滿足某些條件(后面會(huì)有介紹)時(shí),會(huì)被移動(dòng)到老生代(晉升)。
五、新生代算法新生代中的對(duì)象一般存活時(shí)間較短,使用 Scavenge GC 算法。在Scavenge的具體實(shí)現(xiàn)中,主要是采用一種復(fù)制的方式的方法--cheney算法。
在新生代空間中,內(nèi)存空間分為兩部分,分別為 From 空間和 To 空間。在這兩個(gè)空間中,必定有一個(gè)空間是使用的,另一個(gè)空間是空閑的。新分配的對(duì)象會(huì)被放入 From 空間中,當(dāng) From 空間被占滿時(shí),新生代 GC 就會(huì)啟動(dòng)了。算法會(huì)檢查 From 空間中存活的對(duì)象并復(fù)制到 To 空間中,如果有失活的對(duì)象就會(huì)銷毀。當(dāng)復(fù)制完成后將 From 空間和 To 空間互換,這樣 GC 就結(jié)束了。
六、老生代算法老生代中的對(duì)象一般存活時(shí)間較長(zhǎng)且數(shù)量也多,使用了兩個(gè)算法,分別是標(biāo)記清除算法和標(biāo)記壓縮算法。
在講算法前,先來(lái)說(shuō)下什么情況下對(duì)象會(huì)出現(xiàn)在老生代空間中:
1、新生代中的對(duì)象是否已經(jīng)經(jīng)歷過(guò)一次 Scavenge 算法,如果經(jīng)歷過(guò)的話,會(huì)將對(duì)象從新生代空間移到老生代空間中。
2、To 空間的對(duì)象占比大小超過(guò) 25 %。在這種情況下,為了不影響到內(nèi)存分配,會(huì)將對(duì)象從新生代空間移到老生代空間中。
老生代中的空間很復(fù)雜,有如下幾個(gè)空間:
enum AllocationSpace { // TODO(v8:7464): Actually map this space"s memory as read-only. RO_SPACE, // 不變的對(duì)象空間 NEW_SPACE, // 新生代用于 GC 復(fù)制算法的空間 OLD_SPACE, // 老生代常駐對(duì)象空間 CODE_SPACE, // 老生代代碼對(duì)象空間 MAP_SPACE, // 老生代 map 對(duì)象 LO_SPACE, // 老生代大空間對(duì)象 NEW_LO_SPACE, // 新生代大空間對(duì)象 FIRST_SPACE = RO_SPACE, LAST_SPACE = NEW_LO_SPACE, FIRST_GROWABLE_PAGED_SPACE = OLD_SPACE, LAST_GROWABLE_PAGED_SPACE = MAP_SPACE };
在老生代中,以下情況會(huì)先啟動(dòng)標(biāo)記清除算法:
1、某一個(gè)空間沒(méi)有分塊的時(shí)候
2、空間中被對(duì)象超過(guò)一定限制
3、空間不能保證新生代中的對(duì)象移動(dòng)到老生代中
Mark Sweep 是將需要被回收的對(duì)象進(jìn)行標(biāo)記,在垃圾回收運(yùn)行時(shí)直接釋放相應(yīng)的地址空間,如下圖所示(紅色的內(nèi)存區(qū)域表示需要被回收的區(qū)域):
Mark Compact 的思想有點(diǎn)像新生代垃圾回收時(shí)采取的 Cheney 算法:將存活的對(duì)象移動(dòng)到一邊,將需要被回收的對(duì)象移動(dòng)到另一邊,然后對(duì)需要被回收的對(duì)象區(qū)域進(jìn)行整體的垃圾回收。
在這個(gè)階段中,會(huì)遍歷堆中所有的對(duì)象,然后標(biāo)記活的對(duì)象,在標(biāo)記完成后,銷毀所有沒(méi)有被標(biāo)記的對(duì)象。在標(biāo)記大型對(duì)內(nèi)存時(shí),可能需要幾百毫秒才能完成一次標(biāo)記。這就會(huì)導(dǎo)致一些性能上的問(wèn)題。為了解決這個(gè)問(wèn)題,2011 年,V8 從 stop-the-world 標(biāo)記切換到增量標(biāo)志。在增量標(biāo)記期間,GC 將標(biāo)記工作分解為更小的模塊,可以讓 JS 應(yīng)用邏輯在模塊間隙執(zhí)行一會(huì),從而不至于讓應(yīng)用出現(xiàn)停頓情況。但在 2018 年,GC 技術(shù)又有了一個(gè)重大突破,這項(xiàng)技術(shù)名為并發(fā)標(biāo)記。該技術(shù)可以讓 GC 掃描和標(biāo)記對(duì)象時(shí),同時(shí)允許 JS 運(yùn)行。
清除對(duì)象后會(huì)造成堆內(nèi)存出現(xiàn)碎片的情況,當(dāng)碎片超過(guò)一定限制后會(huì)啟動(dòng)壓縮算法。在壓縮過(guò)程中,將活的對(duì)象像一端移動(dòng),直到所有對(duì)象都移動(dòng)完成然后清理掉不需要的內(nèi)存。
七、內(nèi)存泄露和優(yōu)化 7.1 什么是內(nèi)存泄露?存泄露是指程序中已分配的堆內(nèi)存由于某種原因未釋放或者無(wú)法釋放,造成系統(tǒng)內(nèi)存的浪費(fèi),導(dǎo)致程序運(yùn)行速度減慢甚至系統(tǒng)奔潰等后果。。
7.2 常見(jiàn)的內(nèi)存泄露的場(chǎng)景7.2.1 緩存
js開(kāi)發(fā)時(shí)候喜歡用對(duì)象的鍵值來(lái)緩存函數(shù)的計(jì)算結(jié)果,但是緩存中存儲(chǔ)的鍵越多,長(zhǎng)期存活的對(duì)象就越多,導(dǎo)致垃圾回收在進(jìn)行掃描和整理時(shí),對(duì)這些對(duì)象做了很多無(wú)用功。
7.2.2 作用域未釋放(閉包)
var leakArray = []; exports.leak = function () { leakArray.push("leak" + Math.random()); }
模塊在編譯執(zhí)行后形成的作用域因?yàn)槟K緩存的原因,不被釋放,每次調(diào)用 leak 方法,都會(huì)導(dǎo)致局部變量 leakArray 不停增加且不被釋放。
閉包可以維持函數(shù)內(nèi)部變量駐留內(nèi)存,使其得不到釋放。
7.2.3 沒(méi)有必要的全局變量
聲明過(guò)多的全局變量,會(huì)導(dǎo)致變量常駐內(nèi)存,要直到進(jìn)程結(jié)束才能夠釋放內(nèi)存。
7.2.4 無(wú)效的DOM引用
//dom still exist function click(){ // 但是 button 變量的引用仍然在內(nèi)存當(dāng)中。 const button = document.getElementById("button"); button.click(); } // 移除 button 元素 function removeBtn(){ document.body.removeChild(document.getElementById("button")); }
7.2.5 定時(shí)器未清除
// vue 的 mounted 或 react 的 componentDidMount componentDidMount() { setInterval(function () { // ...do something }, 1000) }
vue 或 react 的頁(yè)面生命周期初始化時(shí),定義了定時(shí)器,但是在離開(kāi)頁(yè)面后,未清除定時(shí)器,就會(huì)導(dǎo)致內(nèi)存泄漏。
7.2.6 事件監(jiān)聽(tīng)為空白
componentDidMount() { window.addEventListener("scroll", function () { // do something... }); }
在頁(yè)面生命周期初始化時(shí),綁定了事件監(jiān)聽(tīng)器,但在離開(kāi)頁(yè)面后,未清除事件監(jiān)聽(tīng)器,同樣也會(huì)導(dǎo)致內(nèi)存泄漏。
7.3 內(nèi)存泄露優(yōu)化7.3.1 解除引用
確保占用最少的內(nèi)存可以讓頁(yè)面獲得更好的性能。而優(yōu)化內(nèi)存占用的最佳方式,就是為執(zhí)行中的代碼只保存必要的數(shù)據(jù)。一旦數(shù)據(jù)不再有用,最好通過(guò)將其值設(shè)置為 null 來(lái)釋放其引用——這個(gè)做法叫做解除引用(dereferencing)
function createPerson(name){ var localPerson = new Object(); localPerson.name = name; return localPerson; } var globalPerson = createPerson("Nicholas"); // 手動(dòng)解除 globalPerson 的引用 globalPerson = null;
解除一個(gè)值的引用并不意味著自動(dòng)回收該值所占用的內(nèi)存。解除引用的真正作用是讓值脫離執(zhí)行環(huán)境,以便垃圾收集器下次運(yùn)行時(shí)將其回收。
7.3.2 提供手動(dòng)清空變量的方法
var leakArray = []; exports.clear = function () { leakArray = []; }
7.3.3 其他方法
1、在業(yè)務(wù)不需要的用到的內(nèi)部函數(shù),可以重構(gòu)到函數(shù)外,實(shí)現(xiàn)解除閉包。
2、避免創(chuàng)建過(guò)多的生命周期較長(zhǎng)的對(duì)象,或者將對(duì)象分解成多個(gè)子對(duì)象。
3、避免過(guò)多使用閉包。
4、注意清除定時(shí)器和事件監(jiān)聽(tīng)器。
5、nodejs中使用stream或buffer來(lái)操作大文件,不會(huì)受nodejs內(nèi)存限制。
6、使用redis等外部工具來(lái)緩存數(shù)據(jù)。
八、總結(jié)js是一門具有自動(dòng)回收垃圾收集的編程語(yǔ)言,在瀏覽器中主要是通過(guò)標(biāo)記清除的方法回收垃圾,在nodejs中主要是通過(guò)分代回收,Scavenge,標(biāo)記清除,增量標(biāo)記等算法來(lái)回收垃圾。在日常開(kāi)發(fā)中,有一些不引入注意的書(shū)寫方式可能會(huì)導(dǎo)致內(nèi)存泄露,多注意自己代碼規(guī)范。
九、參考1、V8的垃圾回收機(jī)制與內(nèi)存限制
2、node 內(nèi)存限制的問(wèn)題
3、node內(nèi)存控制
4、深入淺出Nodejs
5、javascript高級(jí)程序設(shè)計(jì)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/105006.html
摘要:新生代的對(duì)象為存活時(shí)間較短的對(duì)象,老生代中的對(duì)象為存活時(shí)間較長(zhǎng)或常駐內(nèi)存的對(duì)象。分別對(duì)新生代和老生代使用不同的垃圾回收算法來(lái)提升垃圾回收的效率。如果指向老生代我們就不必考慮它了。 這篇文章的所有內(nèi)容均來(lái)自 樸靈的《深入淺出Node.js》及A tour of V8:Garbage Collection,后者還有中文翻譯版V8 之旅: 垃圾回收器,我在這里只是做了個(gè)記錄和結(jié)合 垃圾回收...
摘要:歡迎來(lái)我的個(gè)人站點(diǎn)性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開(kāi)啟性能優(yōu)化之旅高性能滾動(dòng)及頁(yè)面渲染優(yōu)化理論寫法對(duì)壓縮率的影響唯快不破應(yīng)用的個(gè)優(yōu)化步驟進(jìn)階鵝廠大神用直出實(shí)現(xiàn)網(wǎng)頁(yè)瞬開(kāi)緩存網(wǎng)頁(yè)性能管理詳解寫給后端程序員的緩存原理介紹年底補(bǔ)課緩存機(jī)制優(yōu)化動(dòng) 歡迎來(lái)我的個(gè)人站點(diǎn) 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開(kāi)啟性能優(yōu)化之旅 高性能滾動(dòng) scroll 及頁(yè)面渲染優(yōu)化 理論 | HTML寫法...
摘要:歡迎來(lái)我的個(gè)人站點(diǎn)性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開(kāi)啟性能優(yōu)化之旅高性能滾動(dòng)及頁(yè)面渲染優(yōu)化理論寫法對(duì)壓縮率的影響唯快不破應(yīng)用的個(gè)優(yōu)化步驟進(jìn)階鵝廠大神用直出實(shí)現(xiàn)網(wǎng)頁(yè)瞬開(kāi)緩存網(wǎng)頁(yè)性能管理詳解寫給后端程序員的緩存原理介紹年底補(bǔ)課緩存機(jī)制優(yōu)化動(dòng) 歡迎來(lái)我的個(gè)人站點(diǎn) 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開(kāi)啟性能優(yōu)化之旅 高性能滾動(dòng) scroll 及頁(yè)面渲染優(yōu)化 理論 | HTML寫法...
摘要:歡迎來(lái)我的個(gè)人站點(diǎn)性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開(kāi)啟性能優(yōu)化之旅高性能滾動(dòng)及頁(yè)面渲染優(yōu)化理論寫法對(duì)壓縮率的影響唯快不破應(yīng)用的個(gè)優(yōu)化步驟進(jìn)階鵝廠大神用直出實(shí)現(xiàn)網(wǎng)頁(yè)瞬開(kāi)緩存網(wǎng)頁(yè)性能管理詳解寫給后端程序員的緩存原理介紹年底補(bǔ)課緩存機(jī)制優(yōu)化動(dòng) 歡迎來(lái)我的個(gè)人站點(diǎn) 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開(kāi)啟性能優(yōu)化之旅 高性能滾動(dòng) scroll 及頁(yè)面渲染優(yōu)化 理論 | HTML寫法...
摘要:引擎對(duì)堆內(nèi)存中的對(duì)象進(jìn)行分代管理新生代存活周期較短的對(duì)象,如臨時(shí)變量字符串等。內(nèi)存泄漏對(duì)于持續(xù)運(yùn)行的服務(wù)進(jìn)程,必須及時(shí)釋放不再用到的內(nèi)存。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第一期,本周的主題是調(diào)用堆棧,今天是第4天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)階計(jì)劃...
閱讀 3784·2021-11-25 09:43
閱讀 2202·2021-11-23 10:13
閱讀 835·2021-11-16 11:44
閱讀 2382·2019-08-29 17:24
閱讀 1393·2019-08-29 17:17
閱讀 3488·2019-08-29 11:30
閱讀 2591·2019-08-26 13:23
閱讀 2353·2019-08-26 12:10