Java 引用對象

這篇文章整理自一位外國大神的英文博客彤枢,我在保存文章的結構下,增加了一些自己的見解茬贵,并做了一個文章的腦圖摘完。原文鏈接為 http://www.kdgregory.com/index.php?page=java.refobj

腦圖如下


基礎

Java堆和對象生命周期

在 Java 中,隨著函數的調用羹令,局部變量和函數會被壓入棧幀鲤屡,而 new 操作符生成出來的實際對象保存在堆中,當然福侈,如果這時堆中沒有合適足夠的空間生成新對象酒来,在報出 OutOfMemoryError之前,就會嘗試進行一次垃圾收集來獲取空間肪凛。

垃圾收集

Java 語言給我們提供了 new 操作符來在堆中分配一塊內存堰汉,但是卻沒有給我們提供一個delete操作符來釋放這些空間辽社,如果僅僅是這樣,那么我們的堆內存空間很快就會占滿翘鸭,程序久無法繼續(xù)執(zhí)行了滴铅。

幸運的是 Java 給我們提供了垃圾收集器。在我們 new 一個對象時就乓,如果堆內存空間不足汉匙,調用 new 操作符的線程就會被掛起,等待垃圾收集器掃描一遍堆內存并釋放空間生蚁,如果收集后仍然沒有足夠的空間噩翠,就會報出 OOM 了。

標記-清除算法

標記清除算法可以概括為:所有不可達的對象都是垃圾邦投,并且可以被收集清除伤锚。

標記清除算法有如下步驟:

步驟一:標記

垃圾收集器從根引用開始,遍歷對象關系圖并把遍歷過的對象標記為可達對象尼摹。

步驟二:清除

所有在步驟一中沒有被標記到的對象见芹,如果有定義 finalizer,則會被加入到 finalization queue 中執(zhí)行 finalize 方法蠢涝,否則會被清除玄呛。

步驟三:壓縮(可選)

有些垃圾收集器會有第三個步驟:壓縮整理堆內存,即把第二步執(zhí)行結束后和二,零碎的堆內存重新對齊徘铝,整理出大片的連續(xù)堆內存空間。

比如惯吕,在1.6 和 1.7 server 模式下的 Hotspot JVM惕它,就會將年輕代的空間壓縮整理,但是不會壓縮整理老年代的空間废登。

Finalizer

雖然 Java 替我們提供了垃圾收集機制去釋放堆內存淹魄,但是內存不是我們唯一要清理的資源。比如堡距,FileOutputStream 在不可達之后甲锡,被垃圾收集器收集之前,應該釋放它關聯的文件和系統(tǒng)的連接羽戒、把緩沖區(qū)的數據刷入文件等缤沦。為此,Java 為我們提供了 Finalizer 機制易稠,我們只要實現 finalize() 方法即可缸废。

雖然 Finalizer 看起來簡單好用,但是我們不應該依賴它,因為如果垃圾收集一直不執(zhí)行企量,那么它將一直不被調用测萎。如果有過多的 Finalizer 拖住了內存空間的清理,那么也可能導致不能及時釋放出足夠的空間而出現 OOM梁钾。

Java 對象生命周期(沒有 Reference 的情況下)

創(chuàng)建---->構造---->使用---->不可達---->Finalizer

引用關系

Reference 對象

三個生命周期中的新狀態(tài)

從 JDK 1.2 起绳泉,Java 提供了三個新的狀態(tài)在 Java 生命周期中:分別是軟可達(softly-reachable)、弱可達(weakly-reachable)姆泻、虛幻可達(phantom-reachable)零酪。這些狀態(tài)都是應用在對象滿足垃圾收集狀態(tài)時,換句話說拇勃,就是這個對象已經沒有任何強引用了四苇。

  • 軟可達: 當對象被一個 SoftReference 關聯,并且沒有強引用時方咆,這個對象就進入了軟可達狀態(tài)月腋。在這個狀態(tài)下,垃圾收集器會嘗試不去收集這個對象瓣赂,直到如果不收集這個對象就會引發(fā) OOM 才會嘗試收集它榆骚。
  • 弱可達: 當對象被一個 WeakReference 關聯,并且沒有強或者軟引用時煌集,這個對象就進入了弱可達狀態(tài)妓肢。在這個狀態(tài)下,垃圾收集器可以自由收集這個對象而不受約束苫纤。不過實際上碉钠,只有在 full collect 時會收集清除弱可達對象,小收集是不會清除的卷拘。
  • 虛幻可達: 當對象被一個 PhantomReference 關聯時喊废,并且對象已經被垃圾收集器盯上且 finalize() 方法已經執(zhí)行時,這個對象就進入了虛幻可達狀態(tài)栗弟。換句話說污筷,已經沒有任何方式可以挽回這個對象了~

這里需要注意的兩個點是:

  • 對象可以跳過其中某些生命周期。比如沒有軟引用乍赫,只有弱引用颓屑。那么對象可以直接從強可達進入弱可達狀態(tài)。
  • 只有極少對象需要用到這些引用關系耿焊。

引用關系和被引用對象

Reference 引用關系是我們程序和具體對象之間的一個中間層,其中被引用的對象是在 Reference 構造時指定的遍搞,并且不可修改罗侯。下面是一個例子:

SoftReference<List<Foo>> ref = new SoftRerence<>(new LinkedList<Foo>);

List<Foo> list = ref.get();
if (list != null){
    list.add(foo);
}else {
    // somthing else
}

其中要注意的點是:

  1. 每次使用對象前必須確認對象是否已經被清理(null)
  2. 必須先拿到對象的強引用再使用對象。不然直接使用 ref.get().add(foo)溪猿,如果這時在執(zhí)行到 ref.get() 時觸發(fā)了一次垃圾收集钩杰,將會報 NPE纫塌。
  3. 比如給這個 Reference 指定一個強引用。如果這個引用關系被垃圾收集清理了讲弄。那我們講這么多都沒用了……

軟引用

在 JDK 文檔中講了措左,軟引用關系適合用于內存敏感的緩存:每個被緩存的對象通過一個 SoftReference 連接,然后 JVM 會在不需要這部分被引用對象的空間時避除,不去清理它怎披,在內存空間不足時再清理。也因此瓶摆,對于正在使用的緩存對象凉逛,我們應該加上一個強引用指向被引用對象,防止它被清理群井。當然状飞,如果要使用的對象已經被清理了,我們就刷新一下緩存再加它進去即可书斜。

需要注意的是诬辈,不建議緩存很小的對象,應該緩存大文件荐吉、大對象焙糟、層層嵌套的對象圖的根對象之類的。因為稍坯,如果緩存小文件酬荞,那么需要清理很多很多對象才能釋放出看起來有起色的內存空間,并且這個引用關系也會占用很多空間瞧哟。

使用軟引用來觸發(fā)循環(huán)的終止

這時軟引用的一種典型的用途混巧,可以在循環(huán)繼續(xù)運行時會觸發(fā) OOM 的情況下,終止循環(huán)勤揩,避免 OOM 的出現咧党。

來看下面一段代碼:

public List<Object> getBigObjectListByIdList(List<String> ids){
    List<Object> list = new LinkedList<>();
    for (String id : ids){
        list.add(getBigObjectFromDisk(id));
    }
    return list;
}

顯然如果這時內存空間不足,經過垃圾收集后仍然不夠的話陨亡。程序將會發(fā)出 OOM 然后崩潰傍衡。如果這時我們給 list 對象套上一層軟引用,并判斷 list 對象的狀態(tài)是否為 null 來決定是否終止循環(huán)负蠕。那么當內存不足時蛙埂,list 將會清理,循環(huán)將終止遮糖,OOM 就可以被避免绣的,程序的魯棒性就能得到增強。當然,依舊提供代碼示例:

public List<Object> getBigObjectListByIdList(List<String> ids){
    SoftReference<List<Object>> ref =  new SoftReference<>(new LinkedList<>());
    for (String id : ids){
        List<Object> list = ref.get();
        if (list == null) 
            return null;
        else
            list.add(getBigObjectFromDisk(id));
        list = null;
    }
    return list;
}

需要注意的是屡江,我在循環(huán)末尾把 list 顯式聲明為 null芭概,因為這里避免了一種特殊情況,雖然我們在循環(huán)結束時失去 list 這個對象惩嘉,但是垃圾收集器可能還沒發(fā)現它已經是不可達狀態(tài)罢洲,因為 list 的引用還存在 JVM 的棧中,是處于一種不明顯文黎、不易被察覺的強引用狀態(tài)惹苗。

軟引用不是銀彈

雖然軟引用可以幫我們避免很多內存溢出的情況,但是卻不能避免所有情況臊诊。問題在于:當我們實際使用一個軟引用來連接對象時鸽粉,比如上面的 getBigObjectListByIdList(List<String> ids) 函數,當我們要添加一行新數據到結果里抓艳,我們必須先拿到被引用對象 list 的強引用触机。在我們拿到 list 強引用的這段時間,我們就處在 OOM 的風險中玷或。

這樣看來儡首,使用軟引用作為循環(huán)的終止,只是最小化了我們觸發(fā) OOM 的風險偏友,并沒有完全解決了 OOM 的問題蔬胯。

弱引用

弱引用,如同它的名字一樣位他,在 gc 時它不會做任何反抗氛濒,只要被引用對象沒有存在強引用關系,即使保留了弱引用關系鹅髓,仍會被清理舞竿。

弱引用關系,存在肯定不會一無是處啦窿冯。它也有適合的應用場景:

  • 連接那些沒有天生存在關聯的對象
  • 通過一個調度 map骗奖,來減少重復數據。(緩存)

連接那些沒有天生存在關聯的對象

比如 ObjectOutputStream 使用了一個 WeakClassKey 來保存最近輸出的對象的 ObjectStreamClass醒串。避免反復對同一個Class創(chuàng)建ObjectStreamClass對象执桌。

從被序列化的對象的角度來看,它跟 ObjectOutputStream 沒有天生的關聯芜赌,從 ObjectOutputStream 的角度來看仰挣,它跟被序列化的對象的 ObjectStreamClass 只是存在使用時要用到的關系,也不是天然有關聯的缠沈。

假設我們寫了一個程序椎木,這個程序直接強引用 ObjectStreamClass 作為 socket 中發(fā)送消息的協(xié)議殴瘦,那么這里就存在一個問題:每個消息一瞬間就發(fā)送完了叉钥,但是消息對象的 ObjectStreamClass 仍然存在內存中一直占有這部分資源,那么這部分內存就廢了抗斤,慢慢程序的內存也會被耗盡禽篱。(除非我們顯式釋放掉這部分內存)

這樣看來畜伐,弱引用提供了這樣一種方式去維持對象的引用關系:當對象正在使用被引用對象時,就顯式持有一個被引用對象的強引用躺率,當使用完被引用對象后玛界,就釋放掉強引用關系,只留下弱引用關系悼吱。這個弱引用關系會維持住跟被引用對象的連接慎框,以期待下次程序再次調用到被引用對象時,將其取出后添,或者直到被引用對象被垃圾收集器清理笨枯。

通過一個調度 map 來減少重復數據

這個功能跟 String.intern() 極其相似,假設我們手動實現一個 String.intern() 方法遇西,就可以通過一個 WeakHashMap 和 WeakReference 配合實現:

private Map<String,WeakReference<String>> _map
    = new WeakHashMap<String,WeakReference<String>>();

public synchronized String intern(String str)
{
    WeakReference<String> ref = _map.get(str);
    String s2 = (ref != null) ? ref.get() : null;
    if (s2 != null)
        return s2;

    _map.put(str, new WeakReference(str));
    return str;
}

當存在大量的相同的 String 對象時馅精,這個做法就可以節(jié)省大量的內存,使它們都引用到同一個 String 對象的地址粱檀;當一個 String 不再被使用時洲敢,就可以被垃圾收集器自由清理掉,不再占用空間茄蚯。推廣到其他對象压彭,也可以用這種方法來減少重復對象。這其實也是一種緩存渗常。

引用隊列 Reference Quences

當我們在創(chuàng)建一個引用關系時壮不,把這個引用關系關聯到一個隊列,并且這個引用在對象被清理時被入隊凳谦。當我們們要尋找哪個對象被清理掉時忆畅,就來隊列中尋找。那這就是引用隊列的作用了尸执。

下面提供一個使用 Reference Queue 的例子

public static void main(String[] argv) throws Exception
{
    Set<WeakReference<byte[]>> refs = new HashSet<WeakReference<byte[]>>();
    ReferenceQueue<byte[]> queue = new ReferenceQueue<byte[]>();
    
    for (int ii = 0 ; ii < 1000 ; ii++)
    {
        WeakReference<byte[]> ref = new WeakReference<byte[]>(new byte[1000000], queue);
        System.err.println(ii + ": created " + ref);
        refs.add(ref);
        
        Reference<? extends byte[]> r2;
        while ((r2 = queue.poll()) != null)
        {
            System.err.println("cleared " + r2);
            refs.remove(r2);
        }
    }
}

通過這個例子我們可以看出引用隊列的兩個要點:

  1. 一旦入隊了家凯,那么這個對象就已經被清理了,回不來了如失。
  2. 引用關系知道引用隊列的存在绊诲,引用隊列不知道引用關系的存在。所以我們必須持有一個引用關系的強引用褪贵。同時我們又要在做完我們對入隊對象的操作后掂之,清理掉這個引用關系的強引用抗俄,否則,這就會觸發(fā)內存泄漏了世舰。

虛幻引用

虛幻引用不同于軟引用和弱引用的是动雹,不能通過虛幻引用關系獲得被引用對象(它的 get() 方法始終返回 null)。所以跟压,虛幻引用唯一的作用應該就是告訴程序被引用對象被垃圾回收了(通過 ReferenceQueue)胰蝠。

雖然虛幻引用表面上看起來沒什么用,但是它可以在資源回收方面做得比 Finalizer 好一些震蒋。(但并沒有完全解決 Finalizer 的問題)

Finalizer 存在的問題

  • finalize() 方法可能會一直沒有被調用

如果我們一直沒有耗盡可用的內存茸塞,那么垃圾回收可能會一直不被執(zhí)行,finalize() 也就會一直不被調用查剖。

雖然有辦法在程序退出之前通知 JVM 調用 Finalizer钾虐,但這個方法不太可靠,而且還可能和其他 JVM 退出時的 hook 沖突笋庄。

  • Finalizer 機制制造了另一個強引用

在垃圾回收時效扫,如果一個對象即將被清理,但是它實現了 FInalizer无切,那它就暫時不會被立刻清理荡短,而是加入到另個獨立于垃圾收集的線程去執(zhí)行 Finalizer。假如我們所有對象都實現了 Finalizer哆键,那那么垃圾收集將沒有任何成果掘托,OOM 也將出現。

要多說一點的是籍嘹,不建議使用 finalizer 去釋放資源闪盔,但也不建議使用虛幻引用去清理資源。最好還是手動在 try/catch/finally 或 try-resources 去釋放資源辱士。

關于虛幻引用不得不知道的知識

虛幻引用允許程序去清理那些不再被使用的對象泪掀,因此程序可以借此清理已經不在內存中的資源。不像 finalizers颂碘,我們使用虛幻引用來清理對象時异赫,對象已經不再內存了。

虛幻引用還有一點跟 Finalizer 不一樣的是头岔,清理是在程序調用的時候進行的塔拳,而不是在垃圾收集的時候觸發(fā)的。我們可以根據我們需要峡竣,開一個或者多個線程來清理對象靠抑。一個可選的方式就是,我們通過一個對象工廠來生產我們需要的資源适掰,然后工廠在生產一個新的資源出來之前颂碧,先進行一次清理荠列,把已經被垃圾收集的資源做一次清理。

理解虛幻引用最關鍵的點就是:我們不能通過這個引用關系 reference 去訪問對象: get() 一直返回 null载城,即使是這個被引用的對象是強可達的肌似。這也意味著我們這個虛幻引用不能幫我們拿到被引用對象,我們也無法通過虛幻引用知道對象是否被清理诉瓦。所以我們必須自己另外對被引用對象做一個強引用保存起來锈嫩,并用一個引用隊列 ReferenceQueue 來標記那些已經被垃圾收集的對象。

下圖是虛幻引用典型的使用方式垦搬,看不懂的可以配合后面的虛幻引用實現連接池的例子來理解。

使用虛幻引用實現一個連接池

數據庫連接是應用中最寶貴的資源之一:它需要花一定的時間來建立連接艳汽,并且數據庫服務器會嚴格限制并發(fā)連接的數量猴贰。也因此,程序員們應該非常謹慎地使用數據庫連接河狐。但還是有時會有為了查詢打開連接米绕,然后忘記手動清理或者忘記在 finally 塊中清理。

比起在應用中直接使用數據庫連接馋艺,大多數應用還是會選擇使用數據庫連接池來管理連接:這個連接池會維持一定地數據庫連接栅干,并且在程序需要使用到數據庫連接的使用從提供可用的連接【桁簦可靠的連接池會提供幾種功能來防止連接泄漏碱鳞,包括超時(連接查詢太長時間),還有從垃圾回收中恢復可用的連接踱蛀。

后面這個功能窿给,就可以用虛幻引用來實現了。為了達到目的率拒,連接池提供的連接 Connection 必須在真實的數據庫連接上做一層包裝崩泡。這樣做的好處是,被包裝的連接對象可以被系統(tǒng)垃圾回收猬膨,但是底層真實的數據庫連接仍會保留下來繼續(xù)被后續(xù)使用角撞。這樣看來,數據庫連接池通過虛幻引用來關聯包裝的連接勃痴,并且在虛幻引用進入引用隊列時谒所,回收真實的連接到連接池中。

這個池還有一個點要關注召耘,那就是 PooledConnection 類百炬,代碼在下面。如同上面說的污它,這是一個包裝過的類剖踊,它將請求委派給真正的連接庶弃。其中,我用了動態(tài)代理來實現這個類德澈。每個 Java 版本的 JDBC 接口都在改進歇攻,也因此,如果是根據某個 JDK 寫出來的代碼梆造,那么前一個版本的 JDK 或者后面版本的 JDK 都可能跑不動下面的連接池代碼缴守。這里使用了動態(tài)代理就解決了這個問題,而且也使得代碼簡潔了一些镇辉。

public class PooledConnection
implements InvocationHandler
{
    private ConnectionPool _pool;
    private Connection _cxt;

    public PooledConnection(ConnectionPool pool, Connection cxt)
    {
        _pool = pool;
        _cxt = cxt;
    }

    private Connection getConnection()
    {
        try
        {
            if ((_cxt == null) || _cxt.isClosed())
                throw new RuntimeException("Connection is closed");
        }
        catch (SQLException ex)
        {
            throw new RuntimeException("unable to determine if underlying connection is open", ex);
        }

        return _cxt;
    }

    public static Connection newInstance(ConnectionPool pool, Connection cxt)
    {
        return (Connection)Proxy.newProxyInstance(
                   PooledConnection.class.getClassLoader(),
                   new Class[] { Connection.class },
                   new PooledConnection(pool, cxt));
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable
    {
        // if calling close() or isClosed(), invoke our implementation
        // otherwise, invoke the passed method on the delegate
    }

    private void close() throws SQLException
    {
        if (_cxt != null)
        {
            _pool.releaseConnection(_cxt);
            _cxt = null;
        }
    }

    private boolean isClosed() throws SQLException
    {
        return (_cxt == null) || (_cxt.isClosed());
    }
}

要關注的最重要的地方是屡穗,PooledConnection 關聯了底層的數據庫連接還有我們的連接池。后一個點用來讓程序關閉包裝的連接:我們通知連接池我們已經用完了連接忽肛,然后連接池就可以回收底層真正的連接來重用村砂。

還要提及一下 getConnection() 方法,它檢查了一種特殊情況:程序是否嘗試使用一個已經關閉的連接屹逛。如果沒有這個檢查础废,然后直接使用一個已經重新分配給其他地方使用的連接,那么會造成相當惡劣的結果罕模∑老伲總結起來就是, close() 顯示地關閉包裝的連接淑掌,getConnection() 檢查連接是否被關閉的特殊情況蒿讥,然后動態(tài)代理委派請求給真實的底層連接。

接下來看看連接池的代碼

private Queue<Connection> _pool = new LinkedList<Connection>();

private ReferenceQueue<Object> _refQueue = new ReferenceQueue<Object>();

private IdentityHashMap<Object,Connection> _ref2Cxt = new IdentityHashMap<Object,Connection>();
private IdentityHashMap<Connection,Object> _cxt2Ref = new IdentityHashMap<Connection,Object>();

我們構建完底層可用的連接后將它存儲在 _pool锋拖,然后使用一個引用隊列來標記那些已經被關閉的包裝連接诈悍。最后,我們使用兩個 Map 來構成底層連接和包裝連接的虛幻引用的雙向 Map兽埃,用來釋放已經用完的連接侥钳。

如同我們上面說的,真實的底層數據庫連接會被包裝起來柄错,這里我們用了 wrapConnection() 方法來做這件事舷夺,在這個方法里我們還創(chuàng)建了虛幻引用,并做了連接-引用雙向映射售貌。

private synchronized Connection wrapConnection(Connection cxt)
{
    Connection wrapped = PooledConnection.newInstance(this, cxt);
    PhantomReference<Connection> ref = new PhantomReference<Connection>(wrapped, _refQueue);
    _cxt2Ref.put(cxt, ref);
    _ref2Cxt.put(ref, cxt);
    System.err.println("Acquired connection " + cxt );
    return wrapped;
}

wrapConnection 相反的是 releaseConnection()给猾,這個方法有兩種處理情況:一種是連接被顯式關閉釋放。

synchronized void releaseConnection(Connection cxt)
{
    Object ref = _cxt2Ref.remove(cxt);
    _ref2Cxt.remove(ref);
    _pool.offer(cxt);
    System.err.println("Released connection " + cxt);
}

另一種是連接沒有被手動釋放颂跨,而是被垃圾回收后敢伸,我們通過相應的虛幻引用,來解放底層連接恒削。

private synchronized void releaseConnection(Reference<?> ref)
{
    Connection cxt = _ref2Cxt.remove(ref);
    if (cxt != null)
        releaseConnection(cxt);
}

另外池颈,有一種邊緣情況我們要考慮的是:如果我們程序并發(fā)調用了 getConncetion()close() 會怎么樣尾序?這也是為什么我在上面的 releaseConnection() 中添加了一個 synchronized 關鍵字,接下來我們再改造下 getConnection() 方法躯砰,加上 synchronized每币,就避免了這種邊緣情況。

public Connection getConnection() throws SQLException
{
    while (true)
    {
        synchronized (this) 
        {
            if (_pool.size() > 0)
                return wrapConnection(_pool.remove());
        }    

        tryWaitingForGarbageCollector();
    }
}

可以想到琢歇,理想的情況是我們每次請求 getConnection() 都會返回一個可用的連接兰怠,但是我們必須考慮沒有現成的可用連接的情況,這里我們就用了 tryWaitingForGarbageCollector() 方法來檢查有沒有廢棄的連接沒有被顯式清理掉李茫,并解放底層的連接揭保。

private void tryWaitingForGarbageCollector()
{
    try
    {
        Reference<?> ref = _refQueue.remove(100);
        if (ref != null)
            releaseConnection(ref);
    }
    catch (InterruptedException ignored)
    {
        // we have to catch this exception, but it provides no information here
        // a production-quality pool might use it as part of an orderly shutdown
    }
}

相關代碼我已經整理到了 github 上:https://github.com/wean2016/ConnectionPool

虛幻引用存在的問題

如同 Finalizer,虛幻引用也存在如果垃圾回收一直不執(zhí)行魄宏,那么它相關的代碼就一直不會運行的問題掖举。如果在上面的例子中,我們初始化了 5 個連接娜庇,并且一直向連接池申請連接,那么可用連接很快就會耗盡方篮,垃圾回收不會執(zhí)行名秀,我們將一直陷入等待。

解決這個問題最簡單的方法是藕溅,在 tryWaitingForGarbageCollector 手動調用 System.gc()匕得。這個解決方案也同樣適用于 Finalizer。

但這不意味著我們可以只關注 Finalizer 而忽視虛幻引用巾表。實際上汁掠,如果這個連接池用 Finalizer 來處理,我們需要關閉連接池的話集币,在 Fianlizer 中我們要顯式手動關閉連接池和相關連接考阱,代碼相當長。而使用虛幻引用來做這件事鞠苟,那就很簡潔了乞榨,只要關聯一下虛幻引用就可以在合適的時候清理掉了。

一個最后的思考:有時候我們也許只是需要更大的內存

有時候引用對象確實是我們管理內存相當有用的工具当娱,但是它們并不是萬能的吃既。如果我們要維持一個超大的對象連接圖,但是我們只有極少內存跨细,那么我們再怎么秀鹦倚,也秀不起來是吧。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末冀惭,一起剝皮案震驚了整個濱河市震叙,隨后出現的幾起案子掀鹅,更是在濱河造成了極大的恐慌,老刑警劉巖捐友,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淫半,死亡現場離奇詭異,居然都是意外死亡匣砖,警方通過查閱死者的電腦和手機科吭,發(fā)現死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猴鲫,“玉大人对人,你說我怎么就攤上這事》鞴玻” “怎么了牺弄?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宜狐。 經常有香客問我势告,道長,這世上最難降的妖魔是什么抚恒? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任咱台,我火速辦了婚禮,結果婚禮上俭驮,老公的妹妹穿的比我還像新娘回溺。我一直安慰自己,他們只是感情好混萝,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布遗遵。 她就那樣靜靜地躺著,像睡著了一般逸嘀。 火紅的嫁衣襯著肌膚如雪车要。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天崭倘,我揣著相機與錄音屯蹦,去河邊找鬼。 笑死绳姨,一個胖子當著我的面吹牛登澜,可吹牛的內容都是我干的。 我是一名探鬼主播飘庄,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼脑蠕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起谴仙,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤迂求,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后晃跺,有當地人在樹林里發(fā)現了一具尸體揩局,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年掀虎,在試婚紗的時候發(fā)現自己被綠了凌盯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡烹玉,死狀恐怖驰怎,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情二打,我是刑警寧澤县忌,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站继效,受9級特大地震影響症杏,放射性物質發(fā)生泄漏。R本人自食惡果不足惜瑞信,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一鸳慈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧喧伞,春花似錦、人聲如沸绩郎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肋杖。三九已至溉仑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間状植,已是汗流浹背浊竟。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留津畸,地道東北人振定。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像肉拓,于是被迫代替她去往敵國和親后频。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345