V8引擎的垃圾回收機制

本文引用自這里
這篇文章的所有內(nèi)容均來自 樸靈的《深入淺出Node.js》及A tour of V8:Garbage Collection峡蟋,后者還有中文翻譯版V8 之旅: 垃圾回收器,我在這里只是做了個記錄和結(jié)合

垃圾回收器

JavaScript的垃圾回收器

JavaScript使用垃圾回收機制來自動管理內(nèi)存荣刑。垃圾回收是一把雙刃劍蟀瞧,其好處是可以大幅簡化程序的內(nèi)存管理代碼翎冲,降低程序員的負擔棘钞,減少因長時間運轉(zhuǎn)而帶來的內(nèi)存泄露問題鉴竭。但使用了垃圾回收即意味著程序員將無法掌控內(nèi)存。ECMAScript沒有暴露任何垃圾回收器的接口循未。我們無法強迫其進行垃圾回收陷猫,更無法干預內(nèi)存管理

Node的內(nèi)存管理問題

在瀏覽器中,V8引擎實例的生命周期不會很長(誰沒事一個頁面開著幾天幾個月不關)的妖,而且運行在用戶的機器上绣檬。如果不幸發(fā)生內(nèi)存泄露等問題,僅僅會影響到一個終端用戶嫂粟。且無論這個V8實例占用了多少內(nèi)存娇未,最終在關閉頁面時內(nèi)存都會被釋放,幾乎沒有太多管理的必要(當然并不代表一些大型Web應用不需要管理內(nèi)存)星虹。但如果使用Node作為服務器零抬,就需要關注內(nèi)存問題了,一旦內(nèi)存發(fā)生泄漏宽涌,久而久之整個服務將會癱瘓(服務器不會頻繁的重啟)

V8的內(nèi)存限制

存在限制

Node與其他語言不同的一個地方平夜,就是其限制了JavaScript所能使用的內(nèi)存(64位為1.4GB,32位為0.7GB)卸亮,這也就意味著將無法直接操作一些大內(nèi)存對象褥芒。這很令人匪夷所思,因為很少有其他語言會限制內(nèi)存的使用

為何限制

V8之所以限制了內(nèi)存的大小嫡良,表面上的原因是V8最初是作為瀏覽器的JavaScript引擎而設計锰扶,不太可能遇到大量內(nèi)存的場景,而深層次的原因則是由于V8的垃圾回收機制的限制寝受。由于V8需要保證JavaScript應用邏輯與垃圾回收器所看到的不一樣坷牛,V8在執(zhí)行垃圾回收時會阻塞JavaScript應用邏輯,直到垃圾回收結(jié)束再重新執(zhí)行JavaScript應用邏輯很澄,這種行為被稱為“全停頓”(stop-the-world)京闰。若V8的堆內(nèi)存為1.5GB颜及,V8做一次小的垃圾回收需要50ms以上,做一次非增量式的垃圾回收甚至要1秒以上蹂楣。這樣瀏覽器將在1s內(nèi)失去對用戶的響應俏站,造成假死現(xiàn)象。如果有動畫效果的話痊土,動畫的展現(xiàn)也將顯著受到影響

突破限制

當然這個限制是可以打開的肄扎,類似于JVM,我們通過在啟動node時可以傳遞--max-old-space-size或--max-new-space-size來調(diào)整內(nèi)存限制的大小赁酝,前者確定老生代的大小犯祠,單位為MB,后者確定新生代的大小酌呆,單位為KB衡载。這些配置只在V8初始化時生效,一旦生效不能再改變

V8的堆構(gòu)成

V8的堆其實并不只是由老生代和新生代兩部分構(gòu)成隙袁,可以將堆分為幾個不同的區(qū)域:
* 新生代內(nèi)存區(qū):大多數(shù)的對象被分配在這里痰娱,這個區(qū)域很小但是垃圾回特別頻繁
* 老生代指針區(qū):屬于老生代,這里包含了大多數(shù)可能存在指向其他對象的指針的對象菩收,大多數(shù)從新生代晉升的對象會被移動到這里
* 老生代數(shù)據(jù)區(qū):屬于老生代梨睁,這里只保存原始數(shù)據(jù)對象,這些對象沒有指向其他對象的指針
* 大對象區(qū):這里存放體積超越其他區(qū)大小的對象坛梁,每個對象有自己的內(nèi)存而姐,垃圾回收其不會移動大對象
* 代碼區(qū):代碼對象腊凶,也就是包含JIT之后指令的對象划咐,會被分配在這里。唯一擁有執(zhí)行權(quán)限的內(nèi)存區(qū)
* Cell區(qū)钧萍、屬性Cell區(qū)褐缠、Map區(qū):存放Cell、屬性Cell和Map风瘦,每個區(qū)域都是存放相同大小的元素队魏,結(jié)構(gòu)簡單

每個區(qū)域都是由一組內(nèi)存頁構(gòu)成,內(nèi)存頁是V8申請內(nèi)存的最小單位万搔,除了大對象區(qū)的內(nèi)存頁較大以外胡桨,其他區(qū)的內(nèi)存頁都是1MB大小,而且按照1MB對齊瞬雹。內(nèi)存頁除了存儲的對象昧谊,還有一個包含元數(shù)據(jù)和標識信息的頁頭,以及一個用于標記哪些對象是活躍對象的位圖區(qū)酗捌。另外每個內(nèi)存頁還有一個單獨分配在另外內(nèi)存區(qū)的槽緩沖區(qū)呢诬,里面放著一組對象涌哲,這些對象可能指向其他存儲在該頁的對象。垃圾回收器只會針對新生代內(nèi)存區(qū)尚镰、老生代指針區(qū)以及老生代數(shù)據(jù)區(qū)進行垃圾回收

V8的垃圾回收機制

如何判斷回收內(nèi)容

如何確定哪些內(nèi)存需要回收阀圾,哪些內(nèi)存不需要回收,這是垃圾回收期需要解決的最基本問題狗唉。我們可以這樣假定初烘,一個對象為活對象當且僅當它被一個根對象或另一個活對象指向。根對象永遠是活對象敞曹,它是被瀏覽器或V8所引用的對象账月。被局部變量所指向的對象也屬于根對象,因為它們所在的作用域?qū)ο蟊灰暈楦鶎ο蟀钠取H謱ο螅∟ode中為global局齿,瀏覽器中為window)自然是根對象。瀏覽器中的DOM元素也屬于根對象

如何識別指針和數(shù)據(jù)

垃圾回收器需要面臨一個問題橄登,它需要判斷哪些是數(shù)據(jù)抓歼,哪些是指針。由于很多垃圾回收算法會將對象在內(nèi)存中移動(緊湊拢锹,減少內(nèi)存碎片)谣妻,所以經(jīng)常需要進行指針的改寫

目前主要有三種方法來識別指針:
1. 保守法:將所有堆上對齊的字都認為是指針,那么有些數(shù)據(jù)就會被誤認為是指針卒稳。于是某些實際是數(shù)字的假指針蹋半,會背誤認為指向活躍對象,導致內(nèi)存泄露(假指針指向的對象可能是死對象充坑,但依舊有指針指向——這個假指針指向它)同時我們不能移動任何內(nèi)存區(qū)域减江。
2. 編譯器提示法:如果是靜態(tài)語言,編譯器能夠告訴我們每個類當中指針的具體位置捻爷,而一旦我們知道對象時哪個類實例化得到的辈灼,就能知道對象中所有指針。這是JVM實現(xiàn)垃圾回收的方式也榄,但這種方式并不適合JS這樣的動態(tài)語言
3. 標記指針法:這種方法需要在每個字末位預留一位來標記這個字段是指針還是數(shù)據(jù)巡莹。這種方法需要編譯器支持,但實現(xiàn)簡單甜紫,而且性能不錯降宅。V8采用的是這種方式。V8將所有數(shù)據(jù)以32bit字寬來存儲囚霸,其中最低一位保持為0腰根,而指針的最低兩位為01

V8的回收策略

自動垃圾回收算法的演變過程中出現(xiàn)了很多算法,但是由于不同對象的生存周期不同邮辽,沒有一種算法適用于所有的情況唠雕。所以V8采用了一種分代回收的策略贸营,將內(nèi)存分為兩個生代:新生代和老生代。新生代的對象為存活時間較短的對象岩睁,老生代中的對象為存活時間較長或常駐內(nèi)存的對象钞脂。分別對新生代和老生代使用不同的垃圾回收算法來提升垃圾回收的效率。對象起初都會被分配到新生代捕儒,當新生代中的對象滿足某些條件(后面會有介紹)時冰啃,會被移動到老生代(晉升)

V8的分代內(nèi)存

默認情況下,64位環(huán)境下的V8引擎的新生代內(nèi)存大小32MB刘莹、老生代內(nèi)存大小為1400MB阎毅,而32位則減半,分別為16MB和700MB点弯。V8內(nèi)存的最大保留空間分別為1464MB(64位)和732MB(32位)扇调。具體的計算公式是4*reserved_semispace_space_ + max_old_generation_size_,新生代由兩塊reserved_semispace_space_組成抢肛,每塊16MB(64位)或8MB(32位)

新生代

新生代的特點

大多數(shù)的對象被分配在這里狼钮,這個區(qū)域很小但是垃圾回收特別頻繁。在新生代分配內(nèi)存非常容易捡絮,我們只需要保存一個指向內(nèi)存區(qū)的指針熬芜,不斷根據(jù)新對象的大小進行遞增即可。當該指針到達了新生代內(nèi)存區(qū)的末尾福稳,就會有一次清理(僅僅是清理新生代)

新生代的垃圾回收算法

新生代使用Scavenge算法進行回收涎拉。在Scavenge算法的實現(xiàn)中,主要采用了Cheney算法的圆。

Cheney算法算法是一種采用復制的方式實現(xiàn)的垃圾回收算法鼓拧。它將內(nèi)存一分為二,每一部分空間稱為semispace略板。在這兩個semispace中毁枯,一個處于使用狀態(tài)慈缔,另一個處于閑置狀態(tài)叮称。處于使用狀態(tài)的semispace空間稱為From空間,處于閑置狀態(tài)的空間稱為To空間藐鹤,當我們分配對象時瓤檐,先是在From空間中進行分配。當開始進行垃圾回收算法時娱节,會檢查From空間中的存活對象挠蛉,這些存活對象將會被復制到To空間中(復制完成后會進行緊縮),而非活躍對象占用的空間將會被釋放肄满。完成復制后谴古,F(xiàn)rom空間和To空間的角色發(fā)生對換质涛。也就是說,在垃圾回收的過程中掰担,就是通過將存活對象在兩個semispace之間進行復制汇陆。可以很容易看出來带饱,使用Cheney算法時毡代,總有一半的內(nèi)存是空的。但是由于新生代很小勺疼,所以浪費的內(nèi)存空間并不大教寂。而且由于新生代中的對象絕大部分都是非活躍對象,需要復制的活躍對象比例很小执庐,所以其時間效率十分理想酪耕。復制的過程采用的是BFS(廣度優(yōu)先遍歷)的思想,從根對象出發(fā)轨淌,廣度優(yōu)先遍歷所有能到達的對象

具體的執(zhí)行過程大致是這樣:

首先將From空間中所有能從根對象到達的對象復制到To區(qū)因妇,然后維護兩個To區(qū)的指針scanPtr和allocationPtr,分別指向即將掃描的活躍對象和即將為新對象分配內(nèi)存的地方猿诸,開始循環(huán)婚被。循環(huán)的每一輪會查找當前scanPtr所指向的對象,確定對象內(nèi)部的每個指針指向哪里梳虽。如果指向老生代我們就不必考慮它了址芯。如果指向From區(qū),我們就需要把這個所指向的對象從From區(qū)復制到To區(qū)窜觉,具體復制的位置就是allocationPtr所指向的位置谷炸。復制完成后將scanPtr所指對象內(nèi)的指針修改為新復制對象存放的地址,并移動allocationPtr禀挫。如果一個對象內(nèi)部的所有指針都被處理完旬陡,scanPtr就會向前移動,進入下一個循環(huán)语婴。若scanPtr和allocationPtr相遇描孟,則說明所有的對象都已被復制完,F(xiàn)rom區(qū)剩下的都可以被視為垃圾砰左,可以進行清理了

舉個例子匿醒,如果有類似如下的引用情況:

          +----- A對象
          |
根對象----+----- B對象 ------ E對象
          |
          +----- C對象 ----+---- F對象 
                           |
                           +---- G對象 ----- H對象

    D對象

在執(zhí)行Scavenge之前,F(xiàn)rom區(qū)長這幅模樣

+---+---+---+---+---+---+---+---+--------+
| A | B | C | D | E | F | G | H |        |
+---+---+---+---+---+---+---+---+--------+

那么首先將根對象能到達的ABC對象復制到To區(qū)缠导,于是乎To區(qū)就變成了這個樣子:

          allocationPtr
             ↓ 
+---+---+---+----------------------------+
| A | B | C |                            |
+---+---+---+----------------------------+
 ↑
scanPtr  

接下來進入循環(huán)廉羔,掃描scanPtr所指的A對象,發(fā)現(xiàn)其沒有指針僻造,于是乎scanPtr移動憋他,變成如下這樣

          allocationPtr
             ↓ 
+---+---+---+----------------------------+
| A | B | C |                            |
+---+---+---+----------------------------+
     ↑
  scanPtr  

接下來掃描B對象孩饼,發(fā)現(xiàn)其有指向E對象的指針,且E對象在From區(qū)竹挡,那么我們需要將E對象復制到allocationPtr所指的地方并移動allocationPtr指針:

            allocationPtr
                 ↓ 
+---+---+---+---+------------------------+
| A | B | C | E |                        |
+---+---+---+---+------------------------+
     ↑
  scanPtr  

B對象里所有指針都已被復制完捣辆,所以移動scanPtr:

            allocationPtr
                 ↓ 
+---+---+---+---+------------------------+
| A | B | C | E |                        |
+---+---+---+---+------------------------+
         ↑
      scanPtr  

接下來掃描C對象,C對象中有兩個指針此迅,分別指向F對象和G對象汽畴,且都在From區(qū),先復制F對象到To區(qū):

                allocationPtr
                     ↓ 
+---+---+---+---+---+--------------------+
| A | B | C | E | F |                    |
+---+---+---+---+---+--------------------+
         ↑
      scanPtr  

然后復制G對象到To區(qū)

                    allocationPtr
                         ↓ 
+---+---+---+---+---+---+----------------+
| A | B | C | E | F | G |                |
+---+---+---+---+---+---+----------------+
         ↑
      scanPtr  

這樣C對象內(nèi)部的指針已經(jīng)復制完成了耸序,移動scanPtr:

                    allocationPtr
                         ↓ 
+---+---+---+---+---+---+----------------+
| A | B | C | E | F | G |                |
+---+---+---+---+---+---+----------------+
             ↑
          scanPtr  

逐個掃描E忍些,F(xiàn)對象,發(fā)現(xiàn)其中都沒有指針坎怪,移動scanPtr:

                    allocationPtr
                         ↓ 
+---+---+---+---+---+---+----------------+
| A | B | C | E | F | G |                |
+---+---+---+---+---+---+----------------+
                     ↑
                  scanPtr  

掃描G對象罢坝,發(fā)現(xiàn)其中有一個指向H對象的指針,且H對象在From區(qū)搅窿,復制H對象到To區(qū)嘁酿,并移動allocationPtr:

                        allocationPtr
                             ↓ 
+---+---+---+---+---+---+---+------------+
| A | B | C | E | F | G | H |            |
+---+---+---+---+---+---+---+------------+
                     ↑
                  scanPtr  

完成后由于G對象沒有其他指針,且H對象沒有指針移動scanPtr:

                        allocationPtr
                             ↓ 
+---+---+---+---+---+---+---+------------+
| A | B | C | E | F | G | H |            |
+---+---+---+---+---+---+---+------------+
                             ↑
                           scanPtr  

此時scanPtr和allocationPtr重合男应,說明復制結(jié)束

可以對比一下From區(qū)和To區(qū)在復制完成后的結(jié)果:

//From區(qū)
+---+---+---+---+---+---+---+---+--------+
| A | B | C | D | E | F | G | H |        |
+---+---+---+---+---+---+---+---+--------+
//To區(qū)
+---+---+---+---+---+---+---+------------+
| A | B | C | E | F | G | H |            |
+---+---+---+---+---+---+---+------------+

D對象沒有被復制闹司,它將被作為垃圾進行回收

寫屏障

如果新生代中的一個對象只有一個指向它的指針,而這個指針在老生代中沐飘,我們?nèi)绾闻袛噙@個新生代的對象是否存活游桩?為了解決這個問題,需要建立一個列表用來記錄所有老生代對象指向新生代對象的情況耐朴。每當有老生代對象指向新生代對象的時候借卧,我們就記錄下來

對象的晉升

當一個對象經(jīng)過多次新生代的清理依舊幸存,這說明它的生存周期較長筛峭,也就會被移動到老生代铐刘,這稱為對象的晉升。具體移動的標準有兩種:
1. 對象從From空間復制到To空間時影晓,會檢查它的內(nèi)存地址來判斷這個對象是否已經(jīng)經(jīng)歷過一個新生代的清理镰吵,如果是,則復制到老生代中俯艰,否則復制到To空間中
2. 對象從From空間復制到To空間時捡遍,如果To空間已經(jīng)被使用了超過25%锌订,那么這個對象直接被復制到老生代

老生代

老生代的特點

老生代所保存的對象大多數(shù)是生存周期很長的甚至是常駐內(nèi)存的對象竹握,而且老生代占用的內(nèi)存較多

老生代的垃圾回收算法

老生代占用內(nèi)存較多(64位為1.4GB,32位為700MB)辆飘,如果使用Scavenge算法啦辐,浪費一半空間不說谓传,復制如此大塊的內(nèi)存消耗時間將會相當長。所以Scavenge算法顯然不適合芹关。V8在老生代中的垃圾回收策略采用Mark-Sweep和Mark-Compact相結(jié)合

Mark-Sweep(標記清除)

標記清除分為標記和清除兩個階段续挟。在標記階段需要遍歷堆中的所有對象,并標記那些活著的對象侥衬,然后進入清除階段诗祸。在清除階段中,只清除沒有被標記的對象轴总。由于標記清除只清除死亡對象直颅,而死亡對象在老生代中占用的比例很小,所以效率較高

標記清除有一個問題就是進行一次標記清楚后怀樟,內(nèi)存空間往往是不連續(xù)的功偿,會出現(xiàn)很多的內(nèi)存碎片。如果后續(xù)需要分配一個需要內(nèi)存空間較多的對象時往堡,如果所有的內(nèi)存碎片都不夠用械荷,將會使得V8無法完成這次分配,提前觸發(fā)垃圾回收虑灰。

Mark-Compact(標記整理)

標記整理正是為了解決標記清除所帶來的內(nèi)存碎片的問題吨瞎。標記整理在標記清除的基礎上進行修改,將標記清除的清除階段變?yōu)榫o縮極端穆咐。在整理的過程中关拒,將活著的對象向內(nèi)存區(qū)的一段移動,移動完成后直接清理掉邊界外的內(nèi)存庸娱。緊縮過程涉及對象的移動着绊,所以效率并不是太好,但是能保證不會生成內(nèi)存碎片

算法思路

標記清除和標記整理都分為兩個階段:標記階段熟尉、清除或緊縮階段

在標記階段归露,所有堆上的活躍對象都會被標記。每個內(nèi)存頁有一個用來標記對象的位圖斤儿,位圖中的每一位對應內(nèi)存頁中的一個字剧包。這個位圖需要占據(jù)一定的空間(32位下為3.1%,64位為1.6%)往果。另外有兩位用來標記對象的狀態(tài)疆液,這個狀態(tài)一共有三種(所以要兩位)——白,灰陕贮,黑:

  • 如果一個對象為白對象堕油,它還沒未被垃圾回收器發(fā)現(xiàn)
  • 如果一個對象為灰對象,它已經(jīng)被垃圾回收器發(fā)現(xiàn),但其鄰接對象尚未全部處理
  • 如果一個對象為黑對象掉缺,說明他步進被垃圾回收器發(fā)現(xiàn)卜录,其鄰接對象也全部被處理完畢了

如果將堆中的對象看做由指針做邊的有向圖,標記算法的核心就是深度優(yōu)先搜索眶明。在初始時艰毒,位圖為空,所有的對象也都是白對象搜囱。從根對象到達的對象會背染色為灰色丑瞧,放入一個單獨的雙端隊列中。標記階段的每次循環(huán)蜀肘,垃圾回收器都會從雙端隊列中取出一個對象并將其轉(zhuǎn)變?yōu)楹趯ο筻吕椋⑵溧徑拥膶ο筠D(zhuǎn)變?yōu)榛遥缓蟀哑溧徑訉ο蠓湃腚p端隊列幌缝。如果雙端隊列為空或所有對象都變成黑對象灸促,則結(jié)束。特別大的對象涵卵,可能會在處理時進行分片浴栽,防止雙端隊列溢出。如果雙端隊列溢出轿偎,則對象仍然會成為灰對象典鸡,但不會被放入隊列中,這將導致其鄰接對象無法被轉(zhuǎn)變?yōu)榛覍ο蠡祷蕖K栽陔p端隊列為空時萝玷,需要掃描所有對象,如果仍有灰對象昆婿,將它們重新放入隊列中進行處理球碉。標記結(jié)束后,所有的對象都應該非黑即白仓蛆,白對象將成為垃圾睁冬,等待釋放

清除和緊縮階段都是以內(nèi)存頁為單位回收內(nèi)存

清除時垃圾回收器會掃描連續(xù)存放的死對象,將其變成空閑空間看疙,并保存到一個空閑空間的鏈表中豆拨。這個鏈表常被scavenge算法用于分配被晉升對象的內(nèi)存,但也被緊縮算法用于移動對象

緊縮算法會嘗試將碎片頁整合到一起來釋放內(nèi)存能庆。由于頁上的對象會被移動到新的頁上施禾,需要重新分配一些頁。大致過程是搁胆,對目標碎片頁中的每個活躍對象弥搞,在空閑內(nèi)存鏈表中分配一塊內(nèi)存頁邮绿,將該對象復制過去,并在碎片頁中的該對象上寫上新的內(nèi)存地址拓巧。隨后在遷出過程中斯碌,對象的舊地址將會被記錄下來一死,在遷出結(jié)束后肛度,V8會遍歷所有它所記錄的舊對象的地址,將其更新為新地址投慈。由于標記過程中也記錄了不同頁之間的指針承耿,這些指針在此時也會進行更新。如果一個頁非澄泵海活躍加袋,如其中有過多需要記錄的指針,那么地址記錄會跳過它抱既,等到下一輪垃圾回收進行處理

結(jié)合使用標記清除和標記整理

V8的老生代使用標記清除和標記整理結(jié)合的方式职烧,主要采用標記清除算法,如果空間不足以分配從新生代晉升過來的對象時防泵,才使用標記整理

V8的優(yōu)化

Incremental Marking(增量標記)

由于全停頓會造成了瀏覽器一段時間無響應蚀之,所以V8使用了一種增量標記的方式,將完整的標記拆分成很多部分捷泞,每做完一部分就停下來足删,讓JS的應用邏輯執(zhí)行一會,這樣垃圾回收與應用邏輯交替完成锁右。經(jīng)過增量標記的改進后失受,垃圾回收的最大停頓時間可以減少到原來的1/6左右

惰性清理

由于標記完成后,所有的對象都已經(jīng)被標記咏瑟,不是死對象就是活對象拂到,堆上多少空間格局已經(jīng)確定。我們可以不必著急釋放那些死對象所占用的空間码泞,而延遲清理過程的執(zhí)行谆焊。垃圾回收器可以根據(jù)需要逐一清理死對象所占用的內(nèi)存頁

其他

V8后續(xù)還引入了增量式整理(incremental compaction),以及并行標記和并行清理浦夷,通過并行利用多核CPU來提升垃圾回收的性能

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辖试,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子劈狐,更是在濱河造成了極大的恐慌罐孝,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肥缔,死亡現(xiàn)場離奇詭異莲兢,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門改艇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來收班,“玉大人,你說我怎么就攤上這事谒兄∷よ耄” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵承疲,是天一觀的道長邻耕。 經(jīng)常有香客問我,道長燕鸽,這世上最難降的妖魔是什么兄世? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮啊研,結(jié)果婚禮上御滩,老公的妹妹穿的比我還像新娘。我一直安慰自己党远,他們只是感情好削解,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著麸锉,像睡著了一般钠绍。 火紅的嫁衣襯著肌膚如雪撬即。 梳的紋絲不亂的頭發(fā)上溯祸,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音稠曼,去河邊找鬼碱屁。 笑死磷脯,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的娩脾。 我是一名探鬼主播赵誓,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼柿赊!你這毒婦竟也來了俩功?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤碰声,失蹤者是張志新(化名)和其女友劉穎诡蜓,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胰挑,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡蔓罚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年椿肩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片豺谈。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡郑象,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出茬末,到底是詐尸還是另有隱情厂榛,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布团南,位于F島的核電站噪沙,受9級特大地震影響炼彪,放射性物質(zhì)發(fā)生泄漏吐根。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一辐马、第九天 我趴在偏房一處隱蔽的房頂上張望拷橘。 院中可真熱鬧,春花似錦喜爷、人聲如沸冗疮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽术幔。三九已至,卻和暖如春湃密,著一層夾襖步出監(jiān)牢的瞬間诅挑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工泛源, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拔妥,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓达箍,卻偏偏與公主長得像没龙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子缎玫,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

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