聊聊V8引擎的垃圾回收

前言

我們知道越除,JavaScript之所以能在瀏覽器環(huán)境和NodeJS環(huán)境運(yùn)行敞临,都是因?yàn)橛蠽8引擎在幕后保駕護(hù)航态辛。從編譯、內(nèi)存分配哟绊、運(yùn)行以及垃圾回收等整個過程因妙,都離不開它。

在寫這篇文章之前票髓,我也在網(wǎng)上看了很多博客攀涵,包括一些英文原版的內(nèi)容,于是想通過這篇文章來做一個歸納整理洽沟,文中加入了我自己的思考以故,以及純手工制作流程圖~~

希望這篇文章能幫到你,同時本文也會收錄到我自己的個人網(wǎng)站裆操。

為什么要有垃圾回收

在C語言和C++語言中怒详,我們?nèi)绻胍_辟一塊堆內(nèi)存的話,需要先計(jì)算需要內(nèi)存的大小踪区,然后自己通過malloc函數(shù)去手動分配昆烁,在用完之后,還要時刻記得用free函數(shù)去清理釋放缎岗,否則這塊內(nèi)存就會被永久占用静尼,造成內(nèi)存泄露。

但是我們在寫JavaScript的時候传泊,卻沒有這個過程鼠渺,因?yàn)槿思乙呀?jīng)替我們封裝好了,V8引擎會根據(jù)你當(dāng)前定義對象的大小去自動申請分配內(nèi)存眷细。

不需要我們?nèi)ナ謩庸芾韮?nèi)存了拦盹,所以自然要有垃圾回收,否則的話只分配不回收溪椎,豈不是沒多長時間內(nèi)存就被占滿了嗎普舆,導(dǎo)致應(yīng)用崩潰恬口。

垃圾回收的好處是不需要我們?nèi)ス芾韮?nèi)存,把更多的精力放在實(shí)現(xiàn)復(fù)雜應(yīng)用上奔害,但壞處也來自于此楷兽,不用管理了,就有可能在寫代碼的時候不注意华临,造成循環(huán)引用等情況,導(dǎo)致內(nèi)存泄露端考。

內(nèi)存結(jié)構(gòu)分配

由于V8最開始就是為JavaScript在瀏覽器執(zhí)行而打造的雅潭,不太可能遇到使用大量內(nèi)存的場景,所以它可以申請的最大內(nèi)存就沒有設(shè)置太大却特,在64位系統(tǒng)下大約為1.4GB扶供,在32位系統(tǒng)下大約為700MB。

在NodeJS環(huán)境中裂明,我們可以通過process.memoryUsage()來查看內(nèi)存分配椿浓。

node環(huán)境v8內(nèi)存

process.memoryUsage返回一個對象,包含了 Node 進(jìn)程的內(nèi)存占用信息闽晦。該對象包含四個字段扳碍,含義如下:

node環(huán)境v8內(nèi)存
rss(resident set size):所有內(nèi)存占用,包括指令區(qū)和堆棧

heapTotal:V8引擎可以分配的最大堆內(nèi)存仙蛉,包含下面的 heapUsed

heapUsed:V8引擎已經(jīng)分配使用的堆內(nèi)存

external: V8管理C++對象綁定到JavaScript對象上的內(nèi)存

以上所有內(nèi)存單位均為字節(jié)(Byte)笋敞。

如果說想要擴(kuò)大Node可用的內(nèi)存空間,可以使用Buffer等堆外內(nèi)存內(nèi)存荠瘪,這里不詳細(xì)說明了夯巷,大家有興趣可以去看一些資料。

下面是Node的整體架構(gòu)圖哀墓,有助于大家理解上面的內(nèi)容:

node環(huán)境v8內(nèi)存
Node Standard Library: 是我們每天都在用的標(biāo)準(zhǔn)庫趁餐,如Http, Buffer 模塊

Node Bindings: 是溝通JS 和 C++的橋梁,封裝V8和Libuv的細(xì)節(jié)篮绰,向上層提供基礎(chǔ)API服務(wù)

第三層是支撐 Node.js 運(yùn)行的關(guān)鍵后雷,由 C/C++ 實(shí)現(xiàn):
1. V8 是Google開發(fā)的JavaScript引擎,提供JavaScript運(yùn)行環(huán)境阶牍,可以說它就是 Node.js 的發(fā)動機(jī)
2. Libuv 是專門為Node.js開發(fā)的一個封裝庫喷面,提供跨平臺的異步I/O能力
3. C-ares:提供了異步處理 DNS 相關(guān)的能力
4. http_parser、OpenSSL走孽、zlib 等:提供包括 http 解析惧辈、SSL、數(shù)據(jù)壓縮等其他的能力

垃圾回收機(jī)制

如何判斷是否可以回收

1.1 標(biāo)記清除

當(dāng)變量進(jìn)入環(huán)境(例如磕瓷,在函數(shù)中聲明一個變量)時盒齿,就將這個變量標(biāo)記為“進(jìn)入環(huán)境”念逞。從邏輯上講,永遠(yuǎn)不能釋放進(jìn)入環(huán)境的變量所占用的內(nèi)存边翁,因?yàn)橹灰獔?zhí)行流進(jìn)入相應(yīng)的環(huán)境翎承,就可能會用到它們。而當(dāng)變量離開環(huán)境時符匾,則將其標(biāo)記為“離開環(huán)境”叨咖。

可以使用任何方式來標(biāo)記變量。比如啊胶,可以通過翻轉(zhuǎn)某個特殊的位來記錄一個變量何時進(jìn)入環(huán)境甸各,或者使用一個“進(jìn)入環(huán)境的”變量列表及一個“離開環(huán)境的”變量列表來跟蹤哪個變量發(fā)生了變化。如何標(biāo)記變量并不重要焰坪,關(guān)鍵在于采取什么策略趣倾。

  • (1)垃圾收集器在運(yùn)行的時候會給存儲在內(nèi)存中的所有變量都加上標(biāo)記(當(dāng)然,可以使用任何標(biāo)記方式)某饰。
  • (2)然后列赎,它會去掉運(yùn)行環(huán)境中的變量以及被環(huán)境中變量所引用的變量的標(biāo)記
  • (3)此后怕膛,依然有標(biāo)記的變量就被視為準(zhǔn)備刪除的變量,原因是在運(yùn)行環(huán)境中已經(jīng)無法訪問到這些變量了。
  • (4)最后块促,垃圾收集器完成內(nèi)存清除工作职抡,銷毀那些帶標(biāo)記的值并回收它們所占用的內(nèi)存空間脊串。

目前研铆,IE、Firefox劳较、Opera驹止、Chrome和Safari的JavaScript實(shí)現(xiàn)使用的都是標(biāo)記清除式的垃圾回收策略(或類似的策略),只不過垃圾收集的時間間隔互有不同观蜗。

標(biāo)記清除

活動對象就是上面的root臊恋,如果不清楚活動對象的可以先查一下資料,當(dāng)一個對象和其關(guān)聯(lián)對象不再通過引用關(guān)系被當(dāng)前root引用了墓捻,這個對象就會被垃圾回收抖仅。

1.2 引用計(jì)數(shù)

引用計(jì)數(shù)的垃圾收集策略不太常見。含義是跟蹤記錄每個值被引用的次數(shù)砖第。當(dāng)聲明了一個變量并將一個引用類型值賦給該變量時撤卢,則這個值的引用次數(shù)就是1。

如果同一個值又被賦給另一個變量梧兼,則該值的引用次數(shù)加1放吩。相反,如果包含對這個值引用的變量改變了引用對象羽杰,則該值引用次數(shù)減1渡紫。

當(dāng)這個值的引用次數(shù)變成0時到推,則說明沒有辦法再訪問這個值了,因而就可以將其占用的內(nèi)存空間回收回來惕澎。

這樣莉测,當(dāng)垃圾收集器下次再運(yùn)行時,它就會釋放那些引用次數(shù)為0的值所占用的內(nèi)存唧喉。

Netscape Navigator 3.0是最早使用引用計(jì)數(shù)策略的瀏覽器捣卤,但很快它就遇到了一個嚴(yán)重的問題:循環(huán)引用

循環(huán)引用是指對象A中包含一個指向?qū)ο驜的指針八孝,而對象B中也包含一個指向?qū)ο驛的引用腌零,看個例子:

function foo () {
    var objA = new Object();
    var objB = new Object();
    
    objA.otherObj = objB;
    objB.anotherObj = objA;
}

這個例子中,objA和objB通過各自的屬性相互引用唆阿,也就是說,這兩個對象的引用次數(shù)都是2锈锤。

在采用標(biāo)記清除策略的實(shí)現(xiàn)中驯鳖,由于函數(shù)執(zhí)行后,這兩個對象都離開了作用域久免,因此這種相互引用不是問題浅辙。

但在采用引用次數(shù)策略的實(shí)現(xiàn)中,當(dāng)函數(shù)執(zhí)行完畢后阎姥,objA和objB還將繼續(xù)存在记舆,因?yàn)樗鼈兊囊么螖?shù)永遠(yuǎn)不會是0。

加入這個函數(shù)被重復(fù)多次調(diào)用呼巴,就會導(dǎo)致大量內(nèi)存無法回收泽腮。為此,Netscape在Navigator 4.0中也放棄了引用計(jì)數(shù)方式衣赶,轉(zhuǎn)而采用標(biāo)記清除來實(shí)現(xiàn)其垃圾回收機(jī)制诊赊。

還要注意的是,我們大部分人時刻都在寫著循環(huán)引用的代碼府瞄,看下面這個例子碧磅,相信大家都這樣寫過:

var el = document.getElementById('#el');
el.onclick = function (event) {
    console.log('element was clicked');
}

我們?yōu)橐粋€元素的點(diǎn)擊事件綁定了一個匿名函數(shù),我們通過event參數(shù)是可以拿到相應(yīng)元素el的信息的遵馆。

大家想想鲸郊,這是不是就是一個循環(huán)引用呢?
el有一個屬性onclick引用了一個函數(shù)(其實(shí)也是個對象)货邓,函數(shù)里面的參數(shù)又引用了el秆撮,這樣el的引用次數(shù)一直是2,即使當(dāng)前這個頁面關(guān)閉了逻恐,也無法進(jìn)行垃圾回收像吻。

如果這樣的寫法很多很多峻黍,就會造成內(nèi)存泄露。我們可以通過在頁面卸載時清除事件引用拨匆,這樣就可以被回收了:

var el = document.getElementById('#el');
el.onclick = function (event) {
    console.log('element was clicked');
}

// ...
// ...

// 頁面卸載時將綁定的事件清空
window.onbeforeunload = function(){
    el.onclick = null;
}

V8垃圾回收策略

自動垃圾回收有很多算法姆涩,由于不同對象的生存周期不同,所以無法只用一種回收策略來解決問題惭每,這樣效率會很低骨饿。

所以,V8采用了一種代回收的策略台腥,將內(nèi)存分為兩個生代:新生代(new generation)老生代(old generation)宏赘。

新生代中的對象為存活時間較短的對象,老生代中的對象為存活時間較長或常駐內(nèi)存的對象黎侈,分別對新老生代采用不同的垃圾回收算法來提高效率察署,對象最開始都會先被分配到新生代(如果新生代內(nèi)存空間不夠,直接分配到老生代)峻汉,新生代中的對象會在滿足某些條件后贴汪,被移動到老生代,這個過程也叫晉升休吠,后面我會詳細(xì)說明扳埂。

分代內(nèi)存

默認(rèn)情況下,32位系統(tǒng)新生代內(nèi)存大小為16MB瘤礁,老生代內(nèi)存大小為700MB阳懂,64位系統(tǒng)下,新生代內(nèi)存大小為32MB柜思,老生代內(nèi)存大小為1.4GB岩调。

新生代平均分成兩塊相等的內(nèi)存空間,叫做semispace酝蜒,每塊內(nèi)存大小8MB(32位)或16MB(64位)誊辉。

新生代

1. 分配方式

新生代存的都是生存周期短的對象,分配內(nèi)存也很容易亡脑,只保存一個指向內(nèi)存空間的指針堕澄,根據(jù)分配對象的大小遞增指針就可以了,當(dāng)存儲空間快要滿時霉咨,就進(jìn)行一次垃圾回收蛙紫。

2. 算法

新生代采用Scavenge垃圾回收算法,在算法實(shí)現(xiàn)時主要采用Cheney算法途戒。

Cheney算法將內(nèi)存一分為二坑傅,叫做semispace,一塊處于使用狀態(tài)喷斋,一塊處于閑置狀態(tài)唁毒。

新老生代

處于使用狀態(tài)的semispace稱為From空間蒜茴,處于閑置狀態(tài)的semispace稱為To空間

我畫了一套詳細(xì)的流程圖浆西,接下來我會結(jié)合流程圖來詳細(xì)說明Cheney算法是怎么工作的粉私。
垃圾回收在下面我統(tǒng)稱為 GC(Garbage Collection)

step1. 在From空間中分配了3個對象A近零、B诺核、C

cheney-step1

step2. GC進(jìn)來判斷對象B沒有其他引用,可以回收久信,對象A和C依然為活躍對象

cheney-step1

step3. 將活躍對象A窖杀、C從From空間復(fù)制到To空間

cheney-step1

step4. 清空From空間的全部內(nèi)存

cheney-step1

step5. 交換From空間和To空間

cheney-step1

step6. 在From空間中又新增了2個對象D、E

cheney-step1

step7. 下一輪GC進(jìn)來發(fā)現(xiàn)對象D沒有引用了裙士,做標(biāo)記

cheney-step1

step8. 將活躍對象A入客、C、E從From空間復(fù)制到To空間

cheney-step1

step9. 清空From空間全部內(nèi)存

cheney-step1

step10. 繼續(xù)交換From空間和To空間腿椎,開始下一輪

cheney-step1

通過上面的流程圖痊项,我們可以很清楚的看到,進(jìn)行From和To交換酥诽,就是為了讓活躍對象始終保持在一塊semispace中,另一塊semispace始終保持空閑的狀態(tài)皱埠。

Scavenge由于只復(fù)制存活的對象肮帐,并且對于生命周期短的場景存活對象只占少部分,所以它在時間效率上有優(yōu)異的體現(xiàn)边器。Scavenge的缺點(diǎn)是只能使用堆內(nèi)存的一半训枢,這是由劃分空間和復(fù)制機(jī)制所決定的。

由于Scavenge是典型的犧牲空間換取時間的算法忘巧,所以無法大規(guī)模的應(yīng)用到所有的垃圾回收中恒界。但我們可以看到,Scavenge非常適合應(yīng)用在新生代中砚嘴,因?yàn)樾律袑ο蟮纳芷谳^短十酣,恰恰適合這個算法。

3. 晉升

當(dāng)一個對象經(jīng)過多次復(fù)制仍然存活時际长,它就會被認(rèn)為是生命周期較長的對象耸采。這種較長生命周期的對象隨后會被移動到老生代中,采用新的算法進(jìn)行管理工育。

對象從新生代移動到老生代的過程叫作晉升虾宇。

對象晉升的條件主要有兩個:

  1. 對象從From空間復(fù)制到To空間時,會檢查它的內(nèi)存地址來判斷這個對象是否已經(jīng)經(jīng)歷過一次Scavenge回收如绸。如果已經(jīng)經(jīng)歷過了嘱朽,會將該對象從From空間移動到老生代空間中旭贬,如果沒有,則復(fù)制到To空間搪泳。總結(jié)來說稀轨,如果一個對象是第二次經(jīng)歷從From空間復(fù)制到To空間,那么這個對象會被移動到老生代中森书。

  2. 當(dāng)要從From空間復(fù)制一個對象到To空間時靶端,如果To空間已經(jīng)使用了超過25%,則這個對象直接晉升到老生代中凛膏。設(shè)置25%這個閾值的原因是當(dāng)這次Scavenge回收完成后杨名,這個To空間會變?yōu)镕rom空間,接下來的內(nèi)存分配將在這個空間中進(jìn)行猖毫。如果占比過高台谍,會影響后續(xù)的內(nèi)存分配。

老生代

1. 介紹

在老生代中吁断,存活對象占較大比重趁蕊,如果繼續(xù)采用Scavenge算法進(jìn)行管理,就會存在兩個問題:

  1. 由于存活對象較多仔役,復(fù)制存活對象的效率會很低掷伙。
  2. 采用Scavenge算法會浪費(fèi)一半內(nèi)存,由于老生代所占堆內(nèi)存遠(yuǎn)大于新生代又兵,所以浪費(fèi)會很嚴(yán)重任柜。

所以,V8在老生代中主要采用了Mark-SweepMark-Sweep相結(jié)合的方式進(jìn)行垃圾回收沛厨。

2. Mark-Sweep

Mark-Sweep是標(biāo)記清除的意思宙地,它分為標(biāo)記和清除兩個階段。

與Scavenge不同逆皮,Mark-Sweep并不會將內(nèi)存分為兩份宅粥,所以不存在浪費(fèi)一半空間的行為。Mark-Sweep在標(biāo)記階段遍歷堆內(nèi)存中的所有對象电谣,并標(biāo)記活著的對象秽梅,在隨后的清除階段,只清除沒有被標(biāo)記的對象剿牺。

也就是說风纠,Scavenge只復(fù)制活著的對象,而Mark-Sweep只清除死了的對象牢贸≈窆郏活對象在新生代中只占較少部分,死對象在老生代中只占較少部分,這就是兩種回收方式都能高效處理的原因臭增。

我們還是通過流程圖來看一下:

step1. 老生代中有對象A懂酱、B、C誊抛、D列牺、E、F

mark-sweep-step1

step2. GC進(jìn)入標(biāo)記階段拗窃,將A瞎领、C、E標(biāo)記為存活對象

mark-sweep-step1

step3. GC進(jìn)入清除階段随夸,回收掉死亡的B九默、D、F對象所占用的內(nèi)存空間

mark-sweep-step1

可以看到宾毒,Mark-Sweep最大的問題就是驼修,在進(jìn)行一次清除回收以后,內(nèi)存空間會出現(xiàn)不連續(xù)的狀態(tài)诈铛。這種內(nèi)存碎片會對后續(xù)的內(nèi)存分配造成問題乙各。

如果出現(xiàn)需要分配一個大內(nèi)存的情況,由于剩余的碎片空間不足以完成此次分配幢竹,就會提前觸發(fā)垃圾回收耳峦,而這次回收是不必要的。

2. Mark-Compact

為了解決Mark-Sweep的內(nèi)存碎片問題焕毫,Mark-Compact就被提出來了妇萄。

Mark-Compact是標(biāo)記整理的意思,是在Mark-Sweep的基礎(chǔ)上演變而來的咬荷。Mark-Compact在標(biāo)記完存活對象以后,會將活著的對象向內(nèi)存空間的一端移動轻掩,移動完成后幸乒,直接清理掉邊界外的所有內(nèi)存。如下圖所示:

step1. 老生代中有對象A唇牧、B罕扎、C、D丐重、E腔召、F(和Mark—Sweep一樣)

mark-sweep-step1

step2. GC進(jìn)入標(biāo)記階段,將A扮惦、C臀蛛、E標(biāo)記為存活對象(和Mark—Sweep一樣)

mark-sweep-step1

step3. GC進(jìn)入整理階段,將所有存活對象向內(nèi)存空間的一側(cè)移動,灰色部分為移動后空出來的空間

mark-sweep-step1

step4. GC進(jìn)入清除階段浊仆,將邊界另一側(cè)的內(nèi)存一次性全部回收

mark-sweep-step1

3. 兩者結(jié)合

在V8的回收策略中客峭,Mark-Sweep和Mark-Conpact兩者是結(jié)合使用的。

由于Mark-Conpact需要移動對象抡柿,所以它的執(zhí)行速度不可能很快舔琅,在取舍上,V8主要使用Mark-Sweep洲劣,在空間不足以對從新生代中晉升過來的對象進(jìn)行分配時备蚓,才使用Mark-Compact。

總結(jié)

V8的垃圾回收機(jī)制分為新生代和老生代囱稽。

新生代主要使用Scavenge進(jìn)行管理郊尝,主要實(shí)現(xiàn)是Cheney算法,將內(nèi)存平均分為兩塊粗悯,使用空間叫From虚循,閑置空間叫To,新對象都先分配到From空間中样傍,在空間快要占滿時將存活對象復(fù)制到To空間中横缔,然后清空From的內(nèi)存空間,此時衫哥,調(diào)換From空間和To空間茎刚,繼續(xù)進(jìn)行內(nèi)存分配,當(dāng)滿足那兩個條件時對象會從新生代晉升到老生代撤逢。

老生代主要采用Mark-Sweep和Mark-Compact算法膛锭,一個是標(biāo)記清除,一個是標(biāo)記整理蚊荣。兩者不同的地方是初狰,Mark-Sweep在垃圾回收后會產(chǎn)生碎片內(nèi)存,而Mark-Compact在清除前會進(jìn)行一步整理互例,將存活對象向一側(cè)移動奢入,隨后清空邊界的另一側(cè)內(nèi)存,這樣空閑的內(nèi)存都是連續(xù)的媳叨,但是帶來的問題就是速度會慢一些腥光。在V8中,老生代是Mark-Sweep和Mark-Compact兩者共同進(jìn)行管理的糊秆。

以上就是本文的全部內(nèi)容武福,書寫過程中參考了很多中外文章,參考書籍包括樸大大的《深入淺出NodeJS》以及《JavaScript高級程序設(shè)計(jì)》等痘番。我們這里并沒有對具體的算法實(shí)現(xiàn)進(jìn)行探討捉片,感興趣的朋友可以繼續(xù)深入研究一下平痰。

最后,謝謝大家能夠讀到這里界睁,如果文中有任何不明確或錯誤的地方觉增,歡迎給我留言~~

參考鏈接

https://medium.com/@_lrlna/garbage-collection-in-v8-an-illustrated-guide-d24a952ee3b8
http://alinode.aliyun.com/blog/14
http://www.ruanyifeng.com/blog/2017/04/memory-leak.html
https://segmentfault.com/a/1190000000440270

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市翻斟,隨后出現(xiàn)的幾起案子逾礁,更是在濱河造成了極大的恐慌,老刑警劉巖访惜,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘹履,死亡現(xiàn)場離奇詭異,居然都是意外死亡债热,警方通過查閱死者的電腦和手機(jī)砾嫉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窒篱,“玉大人焕刮,你說我怎么就攤上這事∏奖” “怎么了配并?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長高镐。 經(jīng)常有香客問我溉旋,道長,這世上最難降的妖魔是什么嫉髓? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任观腊,我火速辦了婚禮,結(jié)果婚禮上算行,老公的妹妹穿的比我還像新娘梧油。我一直安慰自己,他們只是感情好州邢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布儡陨。 她就那樣靜靜地躺著,像睡著了一般偷霉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上褐筛,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天类少,我揣著相機(jī)與錄音,去河邊找鬼渔扎。 笑死硫狞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播残吩,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼财忽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了泣侮?” 一聲冷哼從身側(cè)響起即彪,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎活尊,沒想到半個月后隶校,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蛹锰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年深胳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铜犬。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡舞终,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出癣猾,到底是詐尸還是另有隱情敛劝,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布煎谍,位于F島的核電站攘蔽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏呐粘。R本人自食惡果不足惜满俗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望作岖。 院中可真熱鬧唆垃,春花似錦、人聲如沸痘儡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沉删。三九已至渐尿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間矾瑰,已是汗流浹背砖茸。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留殴穴,地道東北人凉夯。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓货葬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親劲够。 傳聞我的和親對象是個殘疾皇子震桶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容