iOS 內(nèi)存管理底層探究

內(nèi)存管理方式:

MRC:手動(dòng)管理內(nèi)存极谊,需要開(kāi)發(fā)人員管理內(nèi)存魏保,手動(dòng)調(diào)用Release,以控制對(duì)象內(nèi)存的釋放褂始。
ARC:自動(dòng)內(nèi)存管理,系統(tǒng)控制內(nèi)存的釋放時(shí)機(jī)描函,主要由AutoReleasePool管理崎苗,釋放時(shí)機(jī)有所延后,與RunLoop相關(guān)赘阀。

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

iOS對(duì)象的內(nèi)存釋放主要是引用計(jì)數(shù)決定益缠,當(dāng)一個(gè)對(duì)象初始化開(kāi)始,每有一個(gè)指針指向該內(nèi)存地址基公,引用計(jì)數(shù)就會(huì)增加幅慌,調(diào)用Retain增加引用計(jì)數(shù),調(diào)用Release則減少引用計(jì)數(shù)轰豆,由哈希表管理每個(gè)對(duì)象的引用計(jì)數(shù)胰伍。

sideTable:
由sideTable管理,內(nèi)部包含引用計(jì)數(shù)表和weak指針表酸休。

struct SideTable {
    spinlock_t slock;
  # mark 引用計(jì)數(shù)表     key:對(duì)象地址   value:引用計(jì)數(shù)
    RefcountMap refcnts;
   # mark 存放weak指針的表  key:對(duì)象地址   value:對(duì)應(yīng)weak指針的數(shù)組
    weak_table_t weak_table;
};

PS:
在底層代碼里會(huì)看到很多isTaggedPointer和nonpointer的判斷骂租,這兩者都是用于64位上的優(yōu)化機(jī)制,大概了解一下即可斑司。
isTaggedPointer:對(duì)NSNumber渗饮,NSString之類(lèi)常量的數(shù)據(jù)類(lèi)型進(jìn)行區(qū)別處理,將值直接保存在指針中宿刮,避免申請(qǐng)堆區(qū)的內(nèi)存空間互站,所以也不需要維護(hù)引用計(jì)數(shù)。
nonpointer:isa在64位上不再是一個(gè)單純的指針僵缺,還會(huì)存儲(chǔ)一些其他信息胡桃,nonpointer用來(lái)區(qū)別,兩種情況區(qū)別處理磕潮。

獲取引用計(jì)數(shù)的方法:

objc_object::rootRetainCount()
{

#mark 
    if (isTaggedPointer()) return (uintptr_t)this;
    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
#mark兩種情況處理類(lèi)似翠胰,只是nonpointer的話,部分?jǐn)?shù)據(jù)可以直接在isa上取到
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }
    sidetable_unlock(); 
    #mark 單純的指針類(lèi)型 
    return sidetable_retainCount();
}
objc_object::sidetable_retainCount()
{
   #mark 通過(guò)對(duì)象的地址獲取對(duì)應(yīng)的表
    SideTable& table = SideTables()[this];
    size_t refcnt_result = 1; 
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}

release方法:
每次release調(diào)用都會(huì)減少引用計(jì)數(shù)自脯,當(dāng)引用計(jì)數(shù)小于臨界值時(shí)的時(shí)候之景,會(huì)調(diào)用析構(gòu)函數(shù)dealloc。

- (oneway void)release {
    ((id)self)->rootRelease();
}
ALWAYS_INLINE bool 
objc_object::rootRelease()
{
    return rootRelease(true, false);
}
ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
 #mark  rootRelease內(nèi)部相關(guān)邏輯較多冤今,包含指針類(lèi)型判斷和數(shù)據(jù)異常處理闺兢,節(jié)省篇幅,這里只有部分……
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
 
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return false;
            if (sideTableLocked) sidetable_unlock();
          #mark 加鎖保護(hù),真正處理的函數(shù)為sidetable_release屋谭。
            return sidetable_release(performDealloc);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {
            // don't ClearExclusive()
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
       ……
}

sidetable_release方法的處理:
1脚囊、根據(jù)對(duì)象獲取對(duì)應(yīng)的sideTable
2、判斷引用計(jì)數(shù)是否小于臨界值
2桐磁、引用計(jì)數(shù)小于臨界值的悔耘,標(biāo)記為需要釋放,否則進(jìn)行-1我擂。
4衬以、如果標(biāo)記為需要釋放,則調(diào)用dealloc方法校摩。


objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
#mark  獲取到SideTable
    SideTable& table = SideTables()[this];
    bool do_dealloc = false;
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
  #mark 表里找不到
    if (it == table.refcnts.end()) { 
        do_dealloc = true;
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
  #mark 引用計(jì)數(shù)小于臨界值了看峻,需要釋放
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {
        // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
        do_dealloc = true;
        it->second |= SIDE_TABLE_DEALLOCATING;
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
 #mark 引用計(jì)數(shù)-1
        it->second -= SIDE_TABLE_RC_ONE;    
    }
    table.unlock();
  #mark 需要釋放
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);//發(fā)送消息調(diào)用dealloc銷(xiāo)毀對(duì)象。
    }
    return do_dealloc;
}

dealloc方法:
對(duì)象自身的釋放和對(duì)象相關(guān)變量和關(guān)聯(lián)數(shù)據(jù)的處理衙吩。
sideTable的處理互妓,清除引用計(jì)數(shù)記錄,釋放weak指針坤塞。

- (void)dealloc {
    _objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj)
{
    assert(obj);
    obj->rootDealloc();
}
inline void objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?
    #nark 沒(méi)有其他關(guān)聯(lián)的數(shù)據(jù)或者表需要處理冯勉,直接釋放
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

#mark object_dispose方法:

id object_dispose(id obj) 
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
       #mark 釋放對(duì)象的實(shí)例變量
        if (cxx) object_cxxDestruct(obj);
       #mark 移除動(dòng)態(tài)關(guān)聯(lián)的對(duì)象
        if (assoc) _object_remove_assocations(obj);
    
        obj->clearDeallocating();
    }

    return obj;
}

接下來(lái)是clearDeallocating方法:

objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

sidetable_clearDeallocating和clearDeallocating_slow內(nèi)部處理相似,通過(guò)weak表找到指向該對(duì)象的weak指針并釋放摹芙,有引用計(jì)數(shù)記錄的灼狰,則清除該記錄。

objc_object::sidetable_clearDeallocating()
{
    SideTable *table = SideTable::tableForPointer(this);
    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)

    spinlock_lock(&table->slock);
    RefcountMap::iterator it = table->refcnts.find(this);
    if (it != table->refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            #mark  如果有弱引用浮禾,還需要清理weak指針表
            weak_clear_no_lock(&table->weak_table, (id)this);
        }
        #mark 清理引用計(jì)數(shù)記錄
        table->refcnts.erase(it);
    }
    spinlock_unlock(&table->slock);
}

釋放weak指針
獲取weak的hash表交胚,根據(jù)對(duì)象地址獲取所有指向該對(duì)象的weak指針數(shù)組,將數(shù)組內(nèi)元素置為nil盈电。

weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
    
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    #mark 遍歷數(shù)組承绸,將指向?qū)ο蟮膚eak指針置為nil。
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    

weak指針:

初始化weak指針的時(shí)候挣轨,會(huì)調(diào)用objc_initWeak函數(shù)。
當(dāng)weak指針指向一個(gè)對(duì)象時(shí)轩猩,會(huì)調(diào)用objc_storeWeak函數(shù)卷扮。
當(dāng)weak指針不再指向任何對(duì)象時(shí),銷(xiāo)毀會(huì)調(diào)用objc_destroyWeak函數(shù)均践。

//objc_initWeak
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

//objc_storeWeak:
objc_storeWeak(id *location, id newObj)
{
    return storeWeak<DoHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object *)newObj);
}

//objc_destroyWeak
objc_destroyWeak(id *location)
{
    (void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
        (location, nil);
}

可以看出晤锹,三個(gè)方法的最終走向都是storeWeak方法,storeWeak的內(nèi)部實(shí)現(xiàn):
storeWeak內(nèi)部邏輯主要是由 haveOld 和haveNew 判斷彤委,
haveOld 代表weak指針是否已經(jīng)指向其他老對(duì)象了鞭铆,haveNew 代表需要指向新的對(duì)象了 。所以正常情況下是:
objc_initWeak: haveOld = NO,haveNew = YES车遂。
objc_storeWeak: haveOld = YES封断,haveNew = YES。
objc_destroyWeak:haveOld = YES舶担,haveNew = NO坡疼。

storeWeak(id *location, objc_object *newObj)
{
    ASSERT(haveOld  ||  haveNew);
    if (!haveNew) ASSERT(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
   #mark有老對(duì)象則有oldTable 
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];  
    } else {
        oldTable = nil;
    }
  #mark有新對(duì)象則有newTable 
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    #mark 異常處理
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
     #mark 異常處理
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any.
     #mark 從老對(duì)象的weak表里移除記錄
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    #mark 綁定新對(duì)象的weak表
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;
}

自動(dòng)釋放池autoreleasepool:

經(jīng)常看到這樣的代碼:

    for (int i = 0 ; i< 555500; i++) {
        @autoreleasepool {
            TestARCObject *object = [[TestARCObject alloc]init];
            [object doSomeThing ];
        }
    }

在創(chuàng)建大量的臨時(shí)變量時(shí)衣陶,需要手動(dòng)添加自動(dòng)釋放池柄瑰,如果不添加自動(dòng)釋放池的時(shí)候,就會(huì)導(dǎo)致內(nèi)存突然暴漲剪况。因?yàn)锳RC跟MRC不同教沾,MRC是程序員可以手動(dòng)釋放每一個(gè)指針,而ARC下指針的釋放是由autoreleasepool管理译断,然后批量進(jìn)行release處理的授翻。
如果我們自己不添加autoreleasepool,那么將會(huì)由系統(tǒng)自動(dòng)創(chuàng)建的autoreleasepool管理镐作,就是這個(gè) :

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {   #mark 包裹在最外面的autoreleasepool
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);

那么這個(gè)主線程的自動(dòng)釋放池什么時(shí)候?qū)?nèi)部的指針進(jìn)行release呢藏姐,雖然官方只是輕描淡寫(xiě)的一句“將會(huì)在合適的時(shí)候釋放”,但是我們都能猜出其實(shí)是和RunLoop相關(guān)该贾。
通過(guò)控制臺(tái)打印出主線程RunLoop相關(guān)信息:

image.png

搜索autoRelease相關(guān)數(shù)據(jù)羔杨,可以找到_wrapRunLoopWithAutoreleasePoolHandler方法:

<CFRunLoopObserver 0x600001fcc640 [0x7fff8062ce40]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler

<CFRunLoopObserver 0x600001fcc6e0 [0x7fff8062ce40]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler

可以看到activities有兩個(gè)值,也就是說(shuō)兩種情況下會(huì)觸發(fā)AutoreleasePool相關(guān)的回調(diào)方法杨蛋,再查看RunLoop的狀態(tài)枚舉:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), 1
    kCFRunLoopBeforeTimers = (1UL << 1), 2
    kCFRunLoopBeforeSources = (1UL << 2),  4
    kCFRunLoopBeforeWaiting = (1UL << 5),     32
    kCFRunLoopAfterWaiting = (1UL << 6),        64
    kCFRunLoopExit = (1UL << 7),      128 兜材,
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

activities = 0x1(1)對(duì)應(yīng)kCFRunLoopEntry,也就是進(jìn)入runLoop狀態(tài)逞力,執(zhí)行autoreleasepool相關(guān)初始化操作曙寡,
activities = 0xa0(160 )對(duì)應(yīng)kCFRunLoopExit | kCFRunLoopBeforeWaiting(32+128),也就是退出Runloop和即將進(jìn)入休眠的狀態(tài)寇荧,執(zhí)行autoreleasepool相關(guān)釋放操作举庶。

使用clang命令行轉(zhuǎn)換后可以看到:

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ {
        __AtAutoreleasePool __autoreleasepool;
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}


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

可以看到內(nèi)部主要是構(gòu)造函數(shù)push和析構(gòu)函數(shù)pop方法,內(nèi)部是由AutoreleasePoolPage管理揩抡。

objc_autoreleasePoolPush(void)
{
    if (UseGC) return NULL;
    return AutoreleasePoolPage::push();
}

objc_autoreleasePoolPop(void *ctxt)
{
    if (UseGC) return;
    if (!ctxt) return;
    AutoreleasePoolPage::pop(ctxt);
}

AutoreleasePool自身沒(méi)有具體的結(jié)構(gòu)體數(shù)據(jù)户侥,真正管理對(duì)象指針的是AutoreleasePoolPage,整體的數(shù)據(jù)結(jié)構(gòu)是以AutoreleasePoolPage為單位的雙向鏈表峦嗤,AutoreleasePoolPage內(nèi)部結(jié)構(gòu)不細(xì)說(shuō)了蕊唐,除了保存一部分鏈表的信息數(shù)據(jù)外,其他剩余空間都是用來(lái)保存AutoreleasePool管理的對(duì)象指針烁设。

 static inline void *push() 
    {
        if (!hotPage()) {  
# hotPage代表當(dāng)前的Page替梨,如果沒(méi)有會(huì)New一個(gè),設(shè)為當(dāng)前Page
            setHotPage(new AutoreleasePoolPage(NULL));
        } 
        id *dest = autoreleaseFast(POOL_SENTINEL);
        assert(*dest == POOL_SENTINEL);
        return dest;
    }

    static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;
        if (token) {
           #mark 通過(guò)token獲取對(duì)應(yīng)的page
            page = pageForPointer(token);
            stop = (id *)token;
            assert(*stop == POOL_SENTINEL);
        } else {
            // Token 0 is top-level pool
            page = coldPage();
            assert(page);
            stop = page->begin();
        }
        #mark 釋放Page內(nèi)部指針直到碰到臨界值
        page->releaseUntil(stop);

…………(太多了,省略一下)

這里的push是AutoReleasePool初始化第一次調(diào)用的時(shí)候副瀑,所以傳入的autoreleaseFast的參數(shù)是POOL_SENTINEL弓熏,POOL_SENTINEL是邊界對(duì)象,用于分隔的作用俗扇。
Pop內(nèi)部會(huì)獲取相應(yīng)的Page硝烂,然后調(diào)用releaseUntil方法,就開(kāi)始批量釋放內(nèi)部指針了铜幽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末滞谢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子除抛,更是在濱河造成了極大的恐慌狮杨,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件到忽,死亡現(xiàn)場(chǎng)離奇詭異橄教,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)喘漏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)护蝶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人翩迈,你說(shuō)我怎么就攤上這事持灰。” “怎么了负饲?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵堤魁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我返十,道長(zhǎng)妥泉,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任洞坑,我火速辦了婚禮盲链,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘迟杂。我一直安慰自己匈仗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布逢慌。 她就那樣靜靜地躺著,像睡著了一般间狂。 火紅的嫁衣襯著肌膚如雪攻泼。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音忙菠,去河邊找鬼何鸡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛牛欢,可吹牛的內(nèi)容都是我干的骡男。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼傍睹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼隔盛!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起拾稳,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吮炕,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后访得,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體龙亲,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年悍抑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鳄炉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搜骡,死狀恐怖拂盯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情浆兰,我是刑警寧澤磕仅,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站簸呈,受9級(jí)特大地震影響榕订,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜕便,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一劫恒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧轿腺,春花似錦两嘴、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仿荆,卻和暖如春贰您,著一層夾襖步出監(jiān)牢的瞬間坏平,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工锦亦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舶替,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓杠园,卻偏偏與公主長(zhǎng)得像顾瞪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子抛蚁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355