classloader詳解

本文主要包含下面幾個內(nèi)容:

  1. classloader雙親委派機制以及classloader加載class的流程
  2. classloader的其他特性
  3. 自定義classloader以及如何打破雙親委派機制
  4. context classloader作用

classloader雙親委派機制以及classloader加載class的流程

java類加載流程

JVM啟動時鸠珠,有三個classloader負(fù)責(zé)加載class,如下:

  • bootstrap classloader
  • extension classloader
  • system classloader
  1. bootstrap classloader:采用native code實現(xiàn)拍柒,是JVM的一部分,主要加載JVM自身工作需要的類屈暗; 這些類位于$JAVA_HOME/jre/lib/下面斤儿。當(dāng)JVM啟動后,Bootstrap ClassLoader也隨著啟動恐锦,負(fù)責(zé)加載完核心類庫后,并構(gòu)造Extension ClassLoader和App ClassLoader類加載器疆液。
  2. extension classloader:擴展的class loader一铅,加載位于$JAVA_HOME/jre/lib/ext目錄下的擴展jar。
  3. system classloader: 系統(tǒng)class loader堕油,父類是ExtClassLoader潘飘,加載$CLASSPATH下的目錄和jar;它負(fù)責(zé)加載應(yīng)用程序主函數(shù)類掉缺。

為了更好的理解卜录,直接查看源碼,省略了非關(guān)鍵代碼眶明。sun.misc.Launcher, 它是java程序的入口艰毒。

public class Launcher {
    private static URLStreamHandlerFactory factory = new Launcher.Factory();
    private static Launcher launcher = new Launcher();
    private static String bootClassPath = System.getProperty("sun.boot.class.path");
    private ClassLoader loader;
    private static URLStreamHandler fileHandler;

    public static Launcher getLauncher() {
        return launcher;
    }

    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }

        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }

        Thread.currentThread().setContextClassLoader(this.loader);
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }

    static class ExtClassLoader extends URLClassLoader {
      //...
    }


    static class AppClassLoader extends URLClassLoader {
      //...
    }

  //...
}

bootstrap classloader負(fù)責(zé)加載Launcher類,其中代碼里面的bootClassPath為bootstrap classloader的加載路徑搜囱,獲取sun.boot.class.path屬性為$JAVA_HOME/jre/lib/下面jar拼接成的丑瞧,如下:

D:\java_tools\java\jdk8\jre\lib\resources.jar
D:\java_tools\java\jdk8\jre\lib\rt.jar
D:\java_tools\java\jdk8\jre\lib\sunrsasign.jar
D:\java_tools\java\jdk8\jre\lib\jsse.jar
D:\java_tools\java\jdk8\jre\lib\jce.jar
D:\java_tools\java\jdk8\jre\lib\charsets.jar
D:\java_tools\java\jdk8\jre\lib\jfr.jar
D:\java_tools\java\jdk8\jre\classes

同時在代碼里面構(gòu)造了ExtClassLoader和AppClassLoader,兩者都繼承了URLClassLoader蜀肘,其中ExtClassLoader的parent為null(其中為null表示parent為bootstrap classloader)绊汹,URLs為System.getProperty("java.ext.dirs"), 值為$JAVA_HOME/jre/lib/ext,具體的代碼在var1 = Launcher.ExtClassLoader.getExtClassLoader();

另外AppClassLoader的parent為ExtClassLoader扮宠,URLs為System.getProperty("java.class.path")獲取的值西乖,值為-classpath傳遞的值, 具體的代碼在this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
所以classloader的繼承關(guān)系如下:

+-BootstrapClassLoader [bootstrap classloader]                                                                                                                                                                                  
  +-sun.misc.Launcher$ExtClassLoader@7bf2dede [extension classloader]                                                                                                                            
    +-sun.misc.Launcher$AppClassLoader@18b4aac2  [system classloader]       

雙親委派機制

ExtClassLoader和AppClassLoader都繼承了URLClassLoader, URLClassLoader又繼承了ClassLoader類,類加載器在加載類的時候坛增,最終會調(diào)用ClassLoader類的loadClass方法获雕,正是該方法決定了類的加載機制是雙親委派,源碼如下:

public abstract class ClassLoader {
//...

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

//...
}

可以看到主要分為幾步:

  1. 根據(jù)類名嘗試從本地緩存里面獲取已經(jīng)加載的class收捣,如果沒有轉(zhuǎn)2典鸡,如果有轉(zhuǎn)最后一步。
  2. 判斷parent是否為null:不為null坏晦,直接使用parent的classloader加載萝玷;為null嫁乘,相當(dāng)于parent是bootstrap classloader,使用bootstrap classloader加載球碉。如果沒有轉(zhuǎn)3蜓斧,如果有轉(zhuǎn)最后一步。
  3. 調(diào)用findClass方法睁冬,根據(jù)一定的路徑策略獲取class挎春,沒有找到的話返回null,找到轉(zhuǎn)最后一步豆拨。
  4. 解析class直奋。

上面的幾個步驟可以看到,優(yōu)先由parent的classloader加載施禾,這就是雙親委派機制脚线。其中第3步可以覆蓋 findClass方法,實現(xiàn)自己的加載策略:比如可以從遠(yuǎn)程網(wǎng)絡(luò)class文件弥搞,從本地壓縮包里面獲取class文件等邮绿。

classloader的其他特性

除了雙親委派特性,classloader還有隱式加載攀例,隔離等特性船逮。

隱式加載

JVM加載class文件到內(nèi)存有兩種方式。

  1. 隱式加載:所謂隱式加載就是不通過在代碼里調(diào)用classloader來加載需要的類粤铭,而是通過JVM來自動加載需要的類到內(nèi)存的方式挖胃。例如,當(dāng)我們在類中繼承或者引用某個類時梆惯,JVM在解析當(dāng)前這個類時發(fā)現(xiàn)引用的類不在內(nèi)存中冠骄,那么就會自動將這些類加載到內(nèi)存中。
  2. 顯式加載:相反的顯式加載就是我們在代碼中通過調(diào)用classloader類來加載一個類的方式加袋,調(diào)用this.getClass.getClassLoader().loadClass()或者Class.forName()凛辣,或者我們自己實現(xiàn)的ClassLoader的findClass()方法等。

其實這兩種方式是混合使用的职烧,例如扁誓,我們通過自定義的classloader顯式加載一個類時,這個類中又引用了其他類蚀之,那么這些類就是隱式加載的蝗敢。正如所有程序都有一個main函數(shù)一樣,所有的應(yīng)用都有一個或多個入口的類足删,這個類是被最先加載的寿谴,并且隨后的所有類都像樹枝一樣以此類為根被加載。

舉兩個例子:

  1. java程序運行的時候失受,都會首先從擁有main方法的入口類運行讶泰,該類由AppClassLoader加載咏瑟,從而其他被它應(yīng)用的類都會由AppClassLoader來加載。
  2. springboot應(yīng)用的啟動方式痪署,基于springboot的應(yīng)用码泞,最終會被打成一個jar包的形式運行,jar包的META-INF/MANIFEST.MF文件里面指定Main-Class以及其他相關(guān)關(guān)鍵信息如下:
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.dada.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/

Main-Class為org.springframework.boot.loader.JarLauncher狼犯, jar包啟動的時候會首先執(zhí)行JarLauncher的main方法余寥。大致的邏輯是:在JarLauncher里面使用自定義的LaunchedURLClassLoader(parent為system ClassLoader)加載真實的Main-Class,對應(yīng)上面的Start-Class悯森,關(guān)鍵源碼如下:

package org.springframework.boot.loader;

import java.lang.reflect.*;

public class MainMethodRunner
{
    // 省略非關(guān)鍵代碼
    public void run() throws Exception {
        // this.mainClassName 對應(yīng)的就是上面META-INF/MANIFEST.MF里面的Start-Class屬性
        final Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
        final Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
        mainMethod.invoke(null, this.args);
    }
}

其中Thread.currentThread().getContextClassLoader()獲取的就是LaunchedURLClassLoader(在前面設(shè)置宋舷,具體可以參考org.springframework.boot.loader.Launcher#launch方法),通過顯示加載的方式加載Start-Class:com.dada.Application (即真正的應(yīng)用Main-Class)瓢姻,也就是應(yīng)用的入口類祝蝠,該入口類會讓其他被它引用的類使用LaunchedURLClassLoader進行加載。

隔離性

為了理解隔離性汹来,需要先理解下面幾個概念

  1. 不同的classloader加載的同一個class文件,會被jvm認(rèn)為是不同的class改艇。如果把一個ClassLoader創(chuàng)建的實例收班,賦值給另一個ClassLoader加載的類,會導(dǎo)致ClassCastException異常谒兄。
  2. class沖突摔桦,同一個classloader只能加載一個class name(包括package)的class,如果存在多個class name相同的類承疲,會出現(xiàn)隨機加載class邻耕,從而導(dǎo)致NoSuchMethodError等異常。
  1. 兩個平級的classloader加載的兩個類燕鸽,不能相互訪問兄世,比如在下面的場景:
+-BootstrapClassLoader [bootstrap classloader]                                                                                                                                                                                  
  +-sun.misc.Launcher$ExtClassLoader@7bf2dede [extension classloader]                                                                                                                            
    +-sun.misc.Launcher$AppClassLoader@18b4aac2  [system classloader] 
       +-自定義的classloader1
       +-自定義的classloader2

其中classloader1加載的class不能訪問classloader2加載的class。

  1. 一個classloader可以訪問父classloader加載的class啊研,比如自定義的classloader1可以訪問AppClassLoader加載的類御滩。這是雙親委派機制決定的。
  2. 父classloader加載的class不能訪問子classloader加載的class党远,比如AppClassLoader不能訪問自定義的classloader1加載的類削解。這也是雙親委派機制決定的。

舉個例子:
一個tomcat可以同時啟動多個不同的webapp(基于springmvc)沟娱,多個不同的webapp可能擁有完全相同的類氛驮,那么是如何保證不會出現(xiàn)class沖突?正是使用了不同classloader的隔離特性济似。每個webapp使用自定義的WebappClassLoader(parent為shared classloader)來加載org.springframework.web.servlet.DispatcherServlet(繼承了Servlet接口)矫废,這邊的DispatcherServlet類相當(dāng)于入口類盏缤,根據(jù)上面的隱式加載,會繼續(xù)使用該classloader加載相關(guān)聯(lián)的類磷脯。每個webappClassLoader是同級關(guān)系蛾找,不會存在相互訪問的問題,從而達(dá)到不同webapp應(yīng)用隔離的目的赵誓。

自定義classloader以及如何打破雙親委派機制

正是因為classloader有著上面的特性:雙親委派打毛,隱式加載,隔離性俩功,所以經(jīng)常會有自定義classloader的需求幻枉。
自定義classloader之后,可以與原有classloader加載的類隔離開來诡蜓,從而可以避免對原有classloader加載的類造成干擾熬甫。同時可以覆蓋loadClass方法和findClass方法,打破雙親委派機制蔓罚,實現(xiàn)自定義的class路徑加載椿肩。下面舉幾個例子:

  1. OSGI不同bundle之間的隔離
    OSGI是Java動態(tài)化模塊化系統(tǒng),會有多個部署單元豺谈,每個部署單元稱為一個bundle郑象。每個bundle有自己獨立的classloader,同時一個bundle又可以使用其他bundle導(dǎo)出的package茬末,相當(dāng)于委托另外一個bundle的classloader進行類的加載厂榛。
  2. 螞蟻金服開源的sofa-ark框架
    sofa-ark是一款基于Java實現(xiàn)的輕量級類隔離加載容器,sofa-ark包含三個概念:
    sofa-ark模板圖

    ark plugin和ark biz都是以jar包的形式存在丽惭,其中每個ark plugin使用自定義的PluginClassLoader來加載击奶,每個ark biz也使用自定義的BizClassLoader來加載。這樣可以使不同的ark plugin和不同的ark biz隔離開來责掏,以PluginClassLoader為例:
public class PluginClassLoader extends AbstractClasspathClassloader {
   ...
    @Override
    protected Class<?> loadClassInternal(String name, boolean resolve) throws ArkLoaderException {

        // 1. sun reflect related class throw exception directly
        if (classloaderService.isSunReflectClass(name)) {
            throw new ArkLoaderException(
                String
                    .format(
                        "[ArkPlugin Loader] %s : can not load class: %s, this class can only be loaded by sun.reflect.DelegatingClassLoader",
                        pluginName, name));
        }

        // 2. findLoadedClass
        Class<?> clazz = findLoadedClass(name);

        // 3. JDK related class
        if (clazz == null) {
            clazz = resolveJDKClass(name);
        }

        // 4. Ark Spi class
        if (clazz == null) {
            clazz = resolveArkClass(name);
        }

        // 5. Import class export by other plugins
        if (clazz == null) {
            clazz = resolveExportClass(name);
        }

        // 6. Plugin classpath class
        if (clazz == null) {
            clazz = resolveLocalClass(name);
        }

        // 7. Java Agent ClassLoader for agent problem
        if (clazz == null) {
            clazz = resolveJavaAgentClass(name);
        }

        if (clazz != null) {
            if (resolve) {
                super.resolveClass(clazz);
            }
            return clazz;
        }

        throw new ArkLoaderException(String.format(
            "[ArkPlugin Loader] %s : can not load class: %s", pluginName, name));
    }
  ...
}

loadClass方法會調(diào)用loadClassInternal方法柜砾,當(dāng)Plugin在運行時發(fā)現(xiàn)一個類需要被加載時,會按照如下步驟搜索:

  1. 如果已加載過换衬,那就返回已加載好的那個類局义。
  2. 如果這個類是JDK自己的,那么就用JDKClassLoader去加載冗疮。
  3. 如果這個類是屬于Ark容器的萄唇,那么就用ArkClassLoader去加載。
  4. 如果這個類是某個插件export的术幔,那么就用ExportClassLoader去加載另萤。
  5. 如果這個類是插件自身的,那么就用當(dāng)前的ClassLoader直接loadClass就好。
  6. 最后使用某個java agent嘗試加載四敞。
  7. 實在找不到就報錯泛源。

可以看到該步驟并沒有使用雙親委派的機制,而是自定義的加載策略忿危。

context classloader作用

context classloader概念

經(jīng)常會在代碼里面看到這樣的代碼:

ClassLoader cl= Thread.currentThread().getContextClassLoader();
Class<?> clazz= cl.loadClass(getClassName());

每一個Thread都有一個相關(guān)聯(lián)的context classloader达箍,可以通過Thread.setContextClassLoader()方法設(shè)置。如果沒有主動設(shè)置铺厨,Thread默認(rèn)繼承Parent Thread的 context classloader缎玫。如果你整個應(yīng)用中都沒有對此作任何處理,那么 所有的Thread都會以system classLoader作為context Classloader解滓。

context classloader場景

可以自定義classloader赃磨,并設(shè)置到線程中,這樣在當(dāng)前線程的任何地方都可以使用該classloader進行顯示加載洼裤,即調(diào)用loadClass或者forName方法邻辉。從而可以靈活的使用自定義的classloader,而不被java自帶的classloader所限制腮鞍。

最常見的是在Java的SPI場景中使用值骇,比如JDBC。這些 SPI 的接口由 Java 核心庫來提供移国,而這些 SPI 的實現(xiàn)代碼則是作為 Java 應(yīng)用所依賴的 jar 包被包含進classpath里面吱瘩。SPI的接口是Java核心庫的一部分,是由bootstrap classloader來加載的桥狡,SPI的實現(xiàn)類一般由system classloader來加載搅裙。
以JDBC為例皱卓,直接看源碼裹芝,省略了非關(guān)鍵代碼:

public class DriverManager {
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    ...
    private static void loadInitialDrivers() {
  
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                ...
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
       ...
}

DriverManager類是JDK核心類,會被bootstrap classloader加載娜汁,在加載的時候會調(diào)用static代碼塊加載JDBC驅(qū)動嫂易,在loadInitialDrivers方法里面調(diào)用ServiceLoader.load(Driver.class),ServiceLoader是SPI的是一種實現(xiàn)掐禁,所謂SPI怜械,即Service Provider Interface,用于一些服務(wù)提供給第三方實現(xiàn)或者擴展傅事,可以增強框架的擴展或者替換一些組件缕允。 繼續(xù)看load方法。

public final class ServiceLoader<S>, implements Iterable<S> {
    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
        return new ServiceLoader<>(service, loader);
    }

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

   private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }
}

可以看到會將context classloader傳遞給ServiceLoader蹭越,并最終賦值給loader屬性障本,在調(diào)用driversIterator.next()遍歷時,最終會調(diào)用nextService方法,可以看到nextService方法里面調(diào)用Class.forName(cn, false, loader)進行類的隱式加載驾霜。其中cn為所有通過spi方式注冊的driver案训,比如mysql驅(qū)動的類名為com.mysql.jdbc.Driver,配置在mysql-connector-java-5.1.41.jar里的META-INF/services/java.sql.Driver文件中:

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

通過這種方式保證了bootstrap classloader加載的DriverManager類可以訪問由system classloader加載的具體SPI實現(xiàn)類com.mysql.jdbc.Driver粪糙。

參考文檔

理解Java ClassLoader機制
springboot應(yīng)用啟動原理(二) 擴展URLClassLoader實現(xiàn)嵌套jar加載
淺議tomcat與classloader
通過tomcat源碼查看其如何實現(xiàn)應(yīng)用相互隔離
sofa-ark官方文檔
真正理解ContextClassLoader

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末强霎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蓉冈,更是在濱河造成了極大的恐慌城舞,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洒擦,死亡現(xiàn)場離奇詭異椿争,居然都是意外死亡,警方通過查閱死者的電腦和手機熟嫩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門秦踪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人掸茅,你說我怎么就攤上這事椅邓。” “怎么了昧狮?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵景馁,是天一觀的道長。 經(jīng)常有香客問我逗鸣,道長合住,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任撒璧,我火速辦了婚禮透葛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘卿樱。我一直安慰自己僚害,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布繁调。 她就那樣靜靜地躺著萨蚕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蹄胰。 梳的紋絲不亂的頭發(fā)上岳遥,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音裕寨,去河邊找鬼浩蓉。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的妻往。 我是一名探鬼主播互艾,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼讯泣!你這毒婦竟也來了纫普?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤好渠,失蹤者是張志新(化名)和其女友劉穎昨稼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拳锚,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡假栓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了霍掺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匾荆。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖杆烁,靈堂內(nèi)的尸體忽然破棺而出牙丽,到底是詐尸還是另有隱情,我是刑警寧澤兔魂,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布烤芦,位于F島的核電站,受9級特大地震影響析校,放射性物質(zhì)發(fā)生泄漏构罗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一智玻、第九天 我趴在偏房一處隱蔽的房頂上張望遂唧。 院中可真熱鬧,春花似錦尚困、人聲如沸蠢箩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至滔韵,卻和暖如春逻谦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背陪蜻。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工邦马, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓滋将,卻偏偏與公主長得像邻悬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子随闽,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354