iOS 內(nèi)存管理之AutoReleasePool

背景

自從蘋果推出了ARC管理內(nèi)存后秽五,對(duì)于iOS開發(fā)這而言饥悴,內(nèi)存管理就變得so easy了西设,只要正確使用相關(guān)規(guī)則,再也不用擔(dān)心double release棠笑,野指針的等問題了禽绪,而ARC的背后,除了強(qiáng)大的編譯器之外循捺,還要得益于運(yùn)行時(shí)起作用的AutoReleasePool雄人。

研究AutoReleasePool

iOS的項(xiàng)目中础钠,除了特別需求外恰力,整個(gè)項(xiàng)目就一個(gè)地方明確寫了autoReleasePool的代碼了旗吁,就是main函數(shù):

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

autoreleasepool做了什么很钓?

我們知道oc代碼在編譯期間都會(huì)轉(zhuǎn)化為c/c++代碼,然后轉(zhuǎn)化為匯編回还,最終轉(zhuǎn)化為對(duì)應(yīng)的架構(gòu)的二進(jìn)制文件柠硕;也可以這么說,oc的底層實(shí)現(xiàn)就是c/c++闻葵,既然這樣癣丧,我們把他轉(zhuǎn)化為對(duì)應(yīng)的c/c++代碼應(yīng)該就可以窺探到其中的密碼了:
轉(zhuǎn)化為c/c++代碼胁编,Xcode有自帶的工具,打開命令行早直,輸入一下命令就可以:

xcrun -sdk iphoneos   clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

為了減少代碼量市框,重新建了一個(gè)macOS命令行項(xiàng)目:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

轉(zhuǎn)化為cpp文件枫振,看下對(duì)應(yīng)的代碼:

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_k5_h0x40m15075dxn_z956dk2500000gn_T_main_6e2ecd_mi_0);
    }
    return 0;
}

從上面的C++源碼可以發(fā)現(xiàn)@autoreleasepool {}最后變成了:

{ __AtAutoreleasePool __autoreleasepool;//定義了一個(gè)__AtAutoreleasePool結(jié)構(gòu)體變量
粪滤。。。
 }

我們分析下流程:
1窍侧、進(jìn)入大括號(hào)转绷,定義了一個(gè)__AtAutoreleasePool的結(jié)構(gòu)體局部變量议经;
2、定義這個(gè)結(jié)構(gòu)體變量的時(shí)候咧织,會(huì)走結(jié)構(gòu)體的構(gòu)造方法籍救,間接的會(huì)調(diào)用objc_autoreleasePoolPush函數(shù):

__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}

3、當(dāng)走出大括號(hào)是闪萄,局部變量__autoreleasepool败去,會(huì)被銷毀,因此會(huì)走結(jié)構(gòu)體的析構(gòu)函數(shù)广鳍,間接就會(huì)調(diào)用objc_autoreleasePoolPop函數(shù):

  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}

從上面的分析搜锰,我們發(fā)現(xiàn)了兩個(gè)重要的函數(shù)objc_autoreleasePoolPush和objc_autoreleasePoolPop耿战,這兩個(gè)函數(shù)是全局函數(shù)剂陡,而且是以objc開頭的,應(yīng)該是在objc的源碼中歌馍,下載objc源碼的地址晕鹊,macOS 最新系統(tǒng)下面的objc4溅话。

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

他們調(diào)用的是AutoreleasePoolPage類對(duì)應(yīng)的push跟pop兩個(gè)靜態(tài)函數(shù),那么我們就要研究下AutoreleasePoolPage這個(gè)類了砚哆。

研究AutoreleasePoolPage

class AutoreleasePoolPage :
{
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
...
}

從AutoreleasePoolPage的成員變量可以分析出躁锁,AutoreleasePoolPage是一個(gè)雙向鏈表的結(jié)構(gòu)卵史,每個(gè)實(shí)例都會(huì)存在一個(gè)parent實(shí)例指針以躯,跟一個(gè)child實(shí)例指針。其他成員變量暫時(shí)不知道表示什么意思色鸳,只能繼續(xù)研究AutoreleasePoolPage的實(shí)現(xiàn)邏輯了命雀,還是從push跟pop函數(shù)入手:

AutoreleasePoolPage 的push函數(shù):

  static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) {
            // Each autorelease pool starts on a new pool page .debug模式新建一個(gè)page對(duì)象,實(shí)際不需要關(guān)注
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {//實(shí)際走這里
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

追根溯源autoreleaseFast:

static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
//拿到當(dāng)前正在被使用的page撵儿,因?yàn)槊總€(gè)page都是有對(duì)象obj數(shù)量限
//制的淀歇,當(dāng)page放滿了匈织,就會(huì)創(chuàng)建一個(gè)child page來繼續(xù)放缀匕。
        if (page && !page->full()) {//沒滿,直接添加到當(dāng)前的page上
            return page->add(obj);//添加obj
        } else if (page) {//full阔加,滿了
            return autoreleaseFullPage(obj, page);//將obj添加到對(duì)應(yīng)的未滿的child page里面胜榔,并將其設(shè)置為hot page
        } else {//沒有page
            return autoreleaseNoPage(obj);
        }
    }
 static inline AutoreleasePoolPage *hotPage() 
    {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        if (result) result->fastcheck();
        return result;
    }
 static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
        // The hot page is full. 
        // Step to the next non-full page, adding a new page if necessary.
        // Then add the object to that page.
        assert(page == hotPage());
        assert(page->full()  ||  DebugPoolAllocation);

        do {
            if (page->child) page = page->child;//若有child page夭织,將當(dāng)前的指針指向child page
            else page = new AutoreleasePoolPage(page);//new一個(gè)新的page牵辣,并將其賦值給child page纬向;
        } while (page->full());//page是否滿了戴卜,沒滿,跳出

        setHotPage(page);//將page設(shè)置為當(dāng)前hotpage
        return page->add(obj);//添加obj到page
    }
    id *autoreleaseNoPage(id obj)
    {
        // "No page" could mean no pool has been pushed
        // or an empty placeholder pool has been pushed and has no contents yet
        assert(!hotPage());

        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
            // We are pushing a second pool over the empty placeholder pool
            // or pushing the first object into the empty placeholder pool.
            // Before doing that, push a pool boundary on behalf of the pool 
            // that is currently represented by the empty placeholder.
            pushExtraBoundary = true;
        }
        else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
            // We are pushing an object with no pool in place, 
            // and no-pool debugging was requested by environment.
            _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         pthread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
            // We are pushing a pool with no pool in place,
            // and alloc-per-pool debugging was not requested.
            // Install and return the empty pool placeholder.
            return setEmptyPoolPlaceholder();//設(shè)置一個(gè)占位的空的page
        }

        // We are pushing an object or a non-placeholder'd pool.

        // Install the first page.第一次創(chuàng)建一個(gè)page
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);//并將它設(shè)置為hotpage
        
        // Push a boundary on behalf of the previously-placeholder'd pool.
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);//添加哨兵對(duì)象
        }
        
        // Push the requested object or pool.
        return page->add(obj);//添加obj
    }
   id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));//page的起始地址+成員變量的大小
    }

    id * end() {
        return (id *) ((uint8_t *)this+SIZE);///一個(gè)page的大小是size,4096字節(jié)
    }

    bool empty() {
        return next == begin();
    }

    bool full() { 
        return next == end();
    }
  id *add(id obj)
    {
        assert(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;//將obj的指針賦值給next所在的位置糕篇,然后將next指向下一個(gè)位置
        protect();
        return ret;//返回前一個(gè)obj
    }

總結(jié)一下:

  1. push操作從取出當(dāng)前的hotpage酌心,然后將一個(gè)哨兵對(duì)象(其實(shí)是nil)放入到page的next位置安券,并將next指向的位置向下+1;
  2. 取出的hotpage存在鹦筹,但是hotpage是一個(gè)fullpage(一個(gè)page址貌,PAGE_MAX_SIZE = 4096字節(jié)大小芳誓,除了存放內(nèi)部成員變量的值之外,其他的都用來存放autorelease對(duì)象的地址)匿值,這時(shí)就會(huì)循環(huán)查找他的child指向的page對(duì)象赂摆,知道找到?jīng)]使用完的child page烟号,如果沒有child page,則創(chuàng)建一個(gè)达传,將找到的child page并設(shè)置為hotpage宪赶,然后將一個(gè)哨兵對(duì)象添加進(jìn)去脯燃;
  3. 取出的當(dāng)前hotpage不存在辕棚,則通過autoreleaseNoPage創(chuàng)建一個(gè)新的page邓厕,并設(shè)置為hotpage详恼,然后將然后將一個(gè)哨兵對(duì)象添加進(jìn)去涤妒;
  4. 這樣做的結(jié)果她紫,除了第一層page(沒有parent page),每次push返回的obj的都是哨兵對(duì)象渐逃,最開始的push返回的是第一層page最開始的位置page.begin()民褂,后面的pop會(huì)用到這個(gè)返回值赊堪。

AutoreleasePoolPage 的pop函數(shù):

    static inline void pop(void *token) 
    {//token就是對(duì)應(yīng)push返回值哭廉,上面提到過要么是哨兵對(duì)象,要么是第一層page最開始的位置
        AutoreleasePoolPage *page;
        id *stop;

        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            if (hotPage()) {
                // Pool was used. Pop its contents normally.
                // Pool pages remain allocated for re-use as usual.
                pop(coldPage()->begin());
            } else {
                // Pool was never used. Clear the placeholder.
                setHotPage(nil);
            }
            return;
        }

        page = pageForPointer(token);
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            if (stop == page->begin()  &&  !page->parent) {
                //要么是第一層page最開始的位置
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {//其他情況不存在,壞的page
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

        if (PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);//釋放存放在page的指針?biāo)傅膶?duì)象乌企,直到遇到哨兵對(duì)象或者全部釋放完成

        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top) 
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) {//將page對(duì)象釋放掉
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }
    void releaseUntil(id *stop) 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        
        while (this->next != stop) {//循環(huán)釋放對(duì)象加酵,直到遇到哨兵對(duì)象或者全部釋放完
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            AutoreleasePoolPage *page = hotPage();

            // fixme I think this `while` can be `if`, but I can't prove it
            while (page->empty()) {//當(dāng)前page釋放完哭当,還沒遇到哨兵對(duì)象荣病,拿到parent page渗柿,并設(shè)置為hotpage,繼續(xù)釋放柴梆,直到遇到哨兵對(duì)象或者全部釋放完
                page = page->parent;
                setHotPage(page);
            }

            page->unprotect();
            id obj = *--page->next;//拿到obj對(duì)象终惑,并將next指針指向上一個(gè)位置
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));//清空next位置雹有,這里是設(shè)置為0x3A
            page->protect();

            if (obj != POOL_BOUNDARY) {
                objc_release(obj);//釋放掉對(duì)象
            }
        }

        setHotPage(this);//釋放完后,將當(dāng)前page設(shè)置為hotpage

#if DEBUG
        // we expect any children to be completely empty
        for (AutoreleasePoolPage *page = child; page; page = page->child) {
            assert(page->empty());
        }
#endif
    }

pop函數(shù)總結(jié)幾點(diǎn):

  1. 會(huì)拿到最近一次push函數(shù)(pop函數(shù)與push一一對(duì)應(yīng))返回的哨兵對(duì)象溜宽,作為pop函數(shù)的入?yún)ⅲ?/li>
  2. 遍歷hotpage的next指針指向的對(duì)象适揉,并釋放煤惩,直到遇到入?yún)⒌纳诒鴮?duì)象魄揉;
  3. 如果當(dāng)前page釋放完了,還沒遇到哨兵對(duì)象票彪,就會(huì)往parent page遍歷降铸,直到遇到哨兵對(duì)象摇零,一次類推驻仅;
  4. 最后釋放掉為空(empty)的page對(duì)象,但是需要注意的是:當(dāng)他的parent的使用空間超過了1/2毡泻,保留它對(duì)應(yīng)的child page粘优。

從上面的分析大致了解了AutoreleasePoolPage的工作流程,在程序運(yùn)行的時(shí)候是怎么樣工作的呢廊遍?
我們知道贩挣,在MRC時(shí)代王财,需要程序員手寫對(duì)象的retain和release,后面最智能的就是new一個(gè)對(duì)象的時(shí)候狭握,我們需要帶上autorelease代碼:

[[[NSObject alloc] init] autorelease];//MRC手動(dòng)管理內(nèi)存

到了ARC论颅,我們不需要這樣寫囱嫩,因?yàn)榫幾g器在編譯的時(shí)候墨闲,會(huì)自動(dòng)幫忙加上這些代碼,所以說不管是ARC還是MRC時(shí)期盾鳞,oc對(duì)象的內(nèi)存管理入口都是autorelease方法:

autorelease方法的研究:

- (id)autorelease {
    return ((id)self)->rootAutorelease();
}
inline id 
objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this;//tagPointer 不需要
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}
__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);//調(diào)用的是AutoreleasePoolPage的autorelease函數(shù)
}
static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);//調(diào)用autoreleaseFast腾仅,上面提到過
        assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }

就是說推励,每個(gè)oc對(duì)象創(chuàng)建的時(shí)候(alloc/new/copy/mutableCopy)验辞,都是通過autorelease方法添加到page對(duì)象中的喊衫。pop那具體的調(diào)用時(shí)機(jī)又是什么呢族购?我們知道财著,項(xiàng)目只在main函數(shù)有一個(gè)autoreleasepool撑碴,其他的地方除非程序員自己手動(dòng)添加醉拓,就不會(huì)有了收苏,也就是說:我們暫且認(rèn)為編譯器幫忙添加的autorelease鹿霸,那也只是添加進(jìn)page懦鼠,pop函數(shù)還是只有一個(gè),而且是程序退出的時(shí)候調(diào)用街氢,如果是這種情況的話珊肃,程序整個(gè)運(yùn)行期間,內(nèi)存得不停的增長(zhǎng)馅笙,因?yàn)橹挥猩暾?qǐng)伦乔,沒有釋放。顯然這種做法是行不通的 董习。

那么系統(tǒng)是怎么做的呢烈和?直接說結(jié)論:我們知道程序運(yùn)行期間是通過runloop維持的,而runloop就是不停的監(jiān)聽事件和timer皿淋,處理事件和timer斥杜,沒有事件和timer的時(shí)候就進(jìn)入休眠,系統(tǒng)會(huì)在runloop里面添加了兩個(gè)autorelease相關(guān)的observers:
autorelease相關(guān)的observers.png
RunLoop& AutoReleasePool關(guān)系幾點(diǎn)說明:
  1. App啟動(dòng)后蔗喂,蘋果在主線程 RunLoop 里注冊(cè)了兩個(gè) Observer,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler()高帖。
  2. 第一個(gè) Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop)缰儿,其回調(diào)內(nèi)會(huì)調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池。其 order 是-2147483647散址,優(yōu)先級(jí)最高乖阵,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前宣赔。
  3. 第二個(gè) Observer 監(jiān)視了兩個(gè)事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop) 時(shí)調(diào)用 _objc_autoreleasePoolPop() 來釋放自動(dòng)釋放池瞪浸。這個(gè) Observer 的 order 是 2147483647,優(yōu)先級(jí)最低钩蚊,保證其釋放池子發(fā)生在其他所有回調(diào)之后。

總結(jié)

通過上面的源碼及流程的分析,我們對(duì)于autoreleasepool的工作原理及流程有了充分的了解:

  1. autoreleasepool底層的數(shù)據(jù)結(jié)構(gòu)是一個(gè)autoreleasepage的雙向鏈表,每個(gè)page的大小為4096字節(jié)赡茸,除了存儲(chǔ)成員變量的大小,其他的位置都用來存儲(chǔ)autorelease對(duì)象的地址,next變量永遠(yuǎn)指向下一個(gè)可以存放autorelease對(duì)象地址的地址空間叭喜,具體結(jié)構(gòu)如下:
    數(shù)據(jù)結(jié)構(gòu)
  2. 當(dāng)一個(gè)page1存儲(chǔ)空間用完后,會(huì)創(chuàng)建一個(gè)新的page2,新的page2的parent指針指向滿了的page1溉知,page1的child指針會(huì)指向page2;
  3. 程序啟動(dòng)就會(huì)創(chuàng)建一個(gè)autoreleasepool舌劳,會(huì)調(diào)用push,程序結(jié)束時(shí),最后會(huì)調(diào)用pop建邓,回收所有autorelease對(duì)象的內(nèi)存沸手;
  4. 程序運(yùn)行期間,同過監(jiān)聽runloop的休眠狀態(tài)诡渴,調(diào)用push/pop方法,管理autorelease對(duì)象妄辩;
  5. 每次push的時(shí)候,會(huì)往page里面添加一個(gè)哨兵對(duì)象眼耀,這個(gè)哨兵對(duì)象作為下次pop函數(shù)的入?yún)ⅲ龅缴诒鴮?duì)象哮伟,說明這次runloop循環(huán)添加到page的autorelease對(duì)象release完畢。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子勾邦,更是在濱河造成了極大的恐慌,老刑警劉巖荔泳,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蕉饼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡玛歌,警方通過查閱死者的電腦和手機(jī)昧港,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來支子,“玉大人创肥,你說我怎么就攤上這事≈蹬螅” “怎么了叹侄?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)昨登。 經(jīng)常有香客問我趾代,道長(zhǎng),這世上最難降的妖魔是什么丰辣? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任撒强,我火速辦了婚禮,結(jié)果婚禮上笙什,老公的妹妹穿的比我還像新娘尿褪。我一直安慰自己,他們只是感情好得湘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布杖玲。 她就那樣靜靜地躺著,像睡著了一般淘正。 火紅的嫁衣襯著肌膚如雪摆马。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天鸿吆,我揣著相機(jī)與錄音囤采,去河邊找鬼。 笑死惩淳,一個(gè)胖子當(dāng)著我的面吹牛蕉毯,可吹牛的內(nèi)容都是我干的乓搬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼代虾,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼进肯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起棉磨,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤江掩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后乘瓤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體环形,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年衙傀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抬吟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡统抬,死狀恐怖火本,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蓄喇,我是刑警寧澤发侵,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布交掏,位于F島的核電站妆偏,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏盅弛。R本人自食惡果不足惜钱骂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挪鹏。 院中可真熱鬧见秽,春花似錦、人聲如沸讨盒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽返顺。三九已至禀苦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間遂鹊,已是汗流浹背振乏。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留秉扑,地道東北人慧邮。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親误澳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子耻矮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355