Instrument API介紹

1. Instrumentation介紹

?JVMTI(JVM Tool Interface)是 Java 虛擬機(jī)所提供的 native 編程接口,是 JVMPI(Java Virtual Machine Profiler Interface)和 JVMDI(Java Virtual Machine Debug Interface)的更新版本。JVMTI 提供了一套“代理”程序機(jī)制,可以支持第三方工具程序以代理的方式連接和訪問 JVM嫂粟,并利用 JVMTI 提供的豐富的編程接口线梗,完成很多跟 JVM 相關(guān)的功能。

?Agent 即 JVMTI 的客戶端勤婚,它和執(zhí)行 Java 程序的虛擬機(jī)運(yùn)行在同一個(gè)進(jìn)程上哲鸳。他們通常由另一個(gè)獨(dú)立的進(jìn)程控制臣疑,充當(dāng)這個(gè)獨(dú)立進(jìn)程和當(dāng)前虛擬機(jī)之間的中介,通過調(diào)用 JVMTI 提供的接口和虛擬機(jī)交互徙菠,負(fù)責(zé)獲取并返回當(dāng)前虛擬機(jī)的狀態(tài)或者轉(zhuǎn)發(fā)控制命令讯沈。java.lang.instrument 包的實(shí)現(xiàn),也是基于這種機(jī)制的婿奔。在 Instrumentation 的實(shí)現(xiàn)當(dāng)中缺狠,存在一個(gè) JVMTI 的代理程序,通過調(diào)用 JVMTI 當(dāng)中于 Java 類相關(guān)的函數(shù)來完成Java 類的動(dòng)態(tài)操作萍摊。

?利用 java.lang.instrument 做動(dòng)態(tài) Instrumentation 是 Java SE 5 的新特性挤茄,它把 Java 的 instrument 功能從本地代碼中解放出來,使之可以用 Java 代碼的方式解決問題冰木。使用 Instrumentation驮樊,開發(fā)者可以構(gòu)建一個(gè)獨(dú)立于應(yīng)用程序的代理程序(Agent),用來監(jiān)測(cè)和協(xié)助運(yùn)行在 JVM 上的程序片酝,甚至能夠替換和修改某些類的定義囚衔。有了這樣的功能,開發(fā)者就可以實(shí)現(xiàn)更為靈活的運(yùn)行時(shí)虛擬機(jī)監(jiān)控和 Java 類操作了雕沿,這樣的特性實(shí)際上提供了 一種虛擬機(jī)級(jí)別支持的 AOP 實(shí)現(xiàn)方式练湿,使得開發(fā)者無需對(duì) JDK 做任何升級(jí)和改動(dòng),就可以實(shí)現(xiàn)某些 AOP 的功能了审轮。

?在 Java SE6 里面肥哎,最大的改變是運(yùn)行時(shí)的 Instrumentation 成為可能。在 Java SE 5 中疾渣,Instrument 要求在運(yùn)行前利用命令行參數(shù)或者系統(tǒng)參數(shù)來設(shè)置代理類篡诽,在實(shí)際的運(yùn)行之中,虛擬機(jī)在初始化之時(shí)(在絕大多數(shù)的 Java 類庫被載入之前)榴捡,instrumentation 的設(shè)置已經(jīng)啟動(dòng)杈女,并在虛擬機(jī)中設(shè)置了回調(diào)函數(shù),檢測(cè)特定類的加載情況吊圾,并完成實(shí)際工作达椰。但是在實(shí)際的很多的情況下,我們沒有辦法在虛擬機(jī)啟動(dòng)之時(shí)就為其設(shè)定代理项乒,這樣實(shí)際上限制了 instrument 的應(yīng)用啰劲。而 Java SE 6 的新特性改變了這種情況,通過 Java Tool API 中的 attach 方式檀何,我們可以很方便地 在運(yùn)行過程中動(dòng)態(tài)地設(shè)置加載代理類蝇裤,以達(dá)到 instrumentation 的目的。

?Instrumentation 的最大作用频鉴,就是類定義動(dòng)態(tài)改變和操作栓辜。在 Java SE 5 及其后續(xù)版本當(dāng)中,開發(fā)者可以在一個(gè)普通 Java 程序(帶有 main 函數(shù)的 Java 類)運(yùn)行時(shí)砚殿,通過 -javaagent參數(shù)指定一個(gè)特定的 jar 文件(包含 Instrumentation 代理)來啟動(dòng) Instrumentation 的代理程序啃憎。

2. Transformer

?Transformer是字節(jié)碼轉(zhuǎn)換的接口,Instrumentation是管理Transformer似炎、調(diào)度Transformer進(jìn)行字節(jié)碼轉(zhuǎn)換的門面辛萍。 當(dāng)執(zhí)行Instrumentation的addTransformer、removeTransformer方法時(shí)羡藐,最終是調(diào)用了TransformerManager的addTransformer贩毕、removeTransformer,以此來管理Transformer仆嗦。

?Instrumentation的retransformClasses辉阶、redefineClasses是用于通知TransformerManager調(diào)度字節(jié)碼轉(zhuǎn)換的。除此之外,在調(diào)用ClassLoader.defineClass1()這個(gè)native方法用于進(jìn)行類的定義時(shí)谆甜,也會(huì)通知TransformerManager調(diào)度Transformer來進(jìn)行字節(jié)碼轉(zhuǎn)換垃僚。這三個(gè)字節(jié)碼轉(zhuǎn)換通知時(shí)機(jī)分別稱為:

  • 加載類時(shí)(1)
  • 重定義類時(shí)(2)
  • 重轉(zhuǎn)換類時(shí)(3)

?<b>Transformer可以分為兩類:可重轉(zhuǎn)換的Transformer、不可重轉(zhuǎn)換的Transformer规辱。任何一個(gè)Transformer都可以用于加載類時(shí)谆棺、重定義類時(shí)進(jìn)行轉(zhuǎn)換。如果是可重轉(zhuǎn)換的Transformer罕袋,也可以在重轉(zhuǎn)換時(shí)進(jìn)行轉(zhuǎn)換改淑。對(duì)于所有的注冊(cè)轉(zhuǎn)換器,在發(fā)生類加載時(shí)(1)或者重定義類時(shí)(2)浴讯,會(huì)觸發(fā)轉(zhuǎn)換器的執(zhí)行朵夏。重轉(zhuǎn)換類時(shí)只有可中轉(zhuǎn)換的Transformer會(huì)觸發(fā)。</b>

?當(dāng)存在多個(gè)轉(zhuǎn)換器時(shí)榆纽,轉(zhuǎn)換將由transform調(diào)用鏈組成仰猖。也就是說,一個(gè)transform調(diào)用返回的byte數(shù)組將成為下一個(gè)調(diào)用的輸入掠河。 轉(zhuǎn)換將按以下順序進(jìn)行:

  • 不可重轉(zhuǎn)換轉(zhuǎn)換器
  • 不可重轉(zhuǎn)換本地(native)轉(zhuǎn)換器
  • 可重轉(zhuǎn)換轉(zhuǎn)換器
  • 可重轉(zhuǎn)換本地(native)轉(zhuǎn)換器

同樣亮元,在重轉(zhuǎn)換時(shí)(3),不會(huì)調(diào)用不可重轉(zhuǎn)換轉(zhuǎn)換器唠摹,而是重用前一個(gè)轉(zhuǎn)換的結(jié)果爆捞。對(duì)于所有其他情況,調(diào)用此方法勾拉。在每個(gè)這種調(diào)用組中煮甥,轉(zhuǎn)換器將按照注冊(cè)的順序調(diào)用。

?ClassFileTransformer接口只有一個(gè)方法:

byte[] transform(  ClassLoader         loader,
                String              className,
                Class<?>            classBeingRedefined,
                ProtectionDomain    protectionDomain,
                byte[]              classfileBuffer)
        throws IllegalClassFormatException;

其中classfileBuffer字段為加載的class內(nèi)容的byte數(shù)組藕赞,返回結(jié)果未待初始化的class內(nèi)容的byte數(shù)組成肘。即可以通過該方法修改原class內(nèi)容,返回修改后的內(nèi)容來修改類的行為斧蜕。如果不做任何轉(zhuǎn)換双霍,則要返回null。 如果轉(zhuǎn)換器拋出異常(未捕獲的異常)批销,后續(xù)轉(zhuǎn)換器仍然將被調(diào)用并加載洒闸,仍然將嘗試重定義或重轉(zhuǎn)換。因此均芽,拋出異常與返回 null 的效果相同丘逸。

?請(qǐng)參考https://docs.oracle.com/javase/7/docs/api/java/lang/instrument/ClassFileTransformer.html

2.1. redefineClasses

?使用提供的類文件重新定義提供的一組類。

?該方法用于替換類的定義掀宋,而不引用現(xiàn)有的類文件字節(jié)深纲,就像從源頭進(jìn)行重新編譯以進(jìn)行修復(fù)和繼續(xù)調(diào)試時(shí)一樣仲锄。 在現(xiàn)有的類文件字節(jié)要轉(zhuǎn)換的地方應(yīng)該使用retransformClasses。

?該方法對(duì)一組class進(jìn)行操作湃鹊,以便同時(shí)允許多個(gè)相互依賴的類的更改儒喊,如A類的重新定義可能需要重新定義B類。

?如果重新定義的方法具有活動(dòng)堆棧幀涛舍,則這些活動(dòng)幀將繼續(xù)運(yùn)行原始方法的字節(jié)碼澄惊。 重新定義的方法將做用于新的調(diào)用。

?該方法不會(huì)導(dǎo)致任何初始化富雅,除了在常規(guī)JVM語義下會(huì)發(fā)生。 換句話說肛搬,重新定義一個(gè)類并不會(huì)導(dǎo)致它的初始化器被運(yùn)行没佑。 靜態(tài)變量的值將保持在調(diào)用之前。重新定義的類的實(shí)例不受影響温赔。

?重新定義可能會(huì)改變方法體蛤奢,常量池和屬性。 重定義不能添加陶贼,刪除或重命名字段或方法啤贩,更改方法的簽名或更改繼承。 這些限制可能在將來的版本中解除拜秧。 類文件字節(jié)不會(huì)被檢查痹屹,驗(yàn)證和安裝,直到應(yīng)用轉(zhuǎn)換為止枉氮,如果結(jié)果字節(jié)錯(cuò)誤志衍,則此方法將拋出異常。如果此方法拋出異常聊替,則不會(huì)重新定義任何類楼肪。

?該方法的定義如下:

void redefineClasses(ClassDefinition... definitions)  throws ClassNotFoundException,UnmodifiableClassException

public ClassDefinition(Class<?> theClass,byte[]  theClassFile) {
    if (theClass == null || theClassFile == null) {
        throw new NullPointerException();
    }
    mClass      = theClass;
    mClassFile  = theClassFile;
}

如上所述,該方法需要指定需要替換的Class以及提供自定義類文件的字節(jié)碼內(nèi)容惹悄,請(qǐng)參考https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#redefineClasses-java.lang.instrument.ClassDefinition...-

2.2. retransformClasses

?重新轉(zhuǎn)換提供的一組類春叫。

?該方法主要作用于已經(jīng)加載過的class∑郏可以用ClassFileTransformer對(duì)初始化過或者redifine過的class進(jìn)行重新處理暂殖, 無論以前是否發(fā)生轉(zhuǎn)換,此函數(shù)都將重新運(yùn)行轉(zhuǎn)換過程爷速。 轉(zhuǎn)換過程遵循以下步驟:

  • 從初始類文件字節(jié)開始

  • 對(duì)于將canRetransform設(shè)置為false的每個(gè)轉(zhuǎn)換器央星,在上一個(gè)類加載或重定義期間由轉(zhuǎn)換器返回的字節(jié)將被重新用作當(dāng)前轉(zhuǎn)換的輸出,相當(dāng)于當(dāng)前轉(zhuǎn)換器不生效

  • 對(duì)于將canRetransform設(shè)置為true的每個(gè)轉(zhuǎn)換器惫东,將會(huì)在當(dāng)前調(diào)用該轉(zhuǎn)換器

  • 轉(zhuǎn)換后的類文件字節(jié)作為類的新定義安裝

?該方法對(duì)一組class進(jìn)行操作莉给,以便同時(shí)允許多個(gè)相互依賴的類的更改毙石,如A類的重新定義可能需要重新定義B類。

?如果重新定義的方法具有活動(dòng)堆棧幀颓遏,則這些活動(dòng)幀將繼續(xù)運(yùn)行原始方法的字節(jié)碼徐矩。 重新定義的方法將做用于新的調(diào)用。

?該方法不會(huì)導(dǎo)致任何初始化叁幢,除了在常規(guī)JVM語義下會(huì)發(fā)生滤灯。 換句話說,重新定義一個(gè)類并不會(huì)導(dǎo)致它的初始化器被運(yùn)行曼玩。 靜態(tài)變量的值將保持在調(diào)用之前鳞骤。重新定義的類的實(shí)例不受影響

?重新轉(zhuǎn)換可能會(huì)改變方法體,常量池和屬性黍判。 重新傳輸不能添加豫尽,刪除或重命名字段或方法,更改方法的簽名或更改繼承顷帖。 這些限制可能在將來的版本中解除美旧。 類文件字節(jié)不會(huì)被檢查,驗(yàn)證和安裝贬墩,直到應(yīng)用轉(zhuǎn)換為止榴嗅,如果結(jié)果字節(jié)錯(cuò)誤,則此方法將拋出異常陶舞。如果此方法拋出異常嗽测,則不會(huì)重新創(chuàng)建任何類。

?該方法的內(nèi)容如下:

void retransformClasses(Class<?>... classes)  throws UnmodifiableClassException

該方法要傳入需要進(jìn)行重轉(zhuǎn)換的類吊说,請(qǐng)參考https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#retransformClasses-java.lang.Class...-

?需要注意的是论咏,為Agent開啟redefine功能需要在javaagent的MANIFEST.MF里設(shè)置Can-Redefine-Classes:true。為Agent開啟retransform功能需要在javaagent的MANIFEST.MF文件里定義了Can-Retransform-Classes:true颁井。

?介紹完了相關(guān)的內(nèi)容厅贪,下面介紹如何實(shí)現(xiàn)。

3. JDK5 premain方式

?使用premain方式進(jìn)行處理需要如下幾個(gè)步驟

3.1. 提供一個(gè)公共的靜態(tài)方法premain:
//<1>
public static void premain(String agentArgs, Instrumentation inst);
//<2>
public static void premain(String agentArgs); 

其中雅宾,<1>的優(yōu)先級(jí)比 <2> 高养涮,將會(huì)被優(yōu)先執(zhí)行(<1>和<2>同時(shí)存在時(shí),<2>被忽略)眉抬。

?正如這個(gè)方法名贯吓,該方法會(huì)先于main方法被執(zhí)行。一般會(huì)在這個(gè)方法中創(chuàng)建一個(gè)代理對(duì)象蜀变,通過參數(shù) inst 的 addTransformer() 方法悄谐,將創(chuàng)建的代理對(duì)象再傳遞給虛擬機(jī)。agentArgs 是 premain 函數(shù)得到的程序參數(shù)库北,隨同 “– javaagent”一起傳入爬舰。與 main 函數(shù)不同的是们陆,這個(gè)參數(shù)是一個(gè)字符串而不是一個(gè)字符串?dāng)?shù)組,如果程序參數(shù)有多個(gè)情屹,程序?qū)⒆孕薪馕鲞@個(gè)字符串坪仇。

3.2. 提供一個(gè)或者多個(gè)ClassFileTransformer實(shí)現(xiàn)類

?上面說過,會(huì)在premain中調(diào)用inst的addTransformer()方法垃你,該方法的入?yún)⒕褪荂lassFileTransformer對(duì)象椅文。

?對(duì)于字節(jié)碼的修改在上一節(jié)已經(jīng)介紹過了,可以有多種方式惜颇。這里使用上一節(jié)的例子皆刺,對(duì)CoreActionImpl類進(jìn)行修改以達(dá)到AOP的效果。代碼如下:

public class PreMainProxyAction implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
        byte[] classfileBuffer) throws IllegalClassFormatException {
        if (!className.equals("demo/CoreActionImpl")) {
            return classfileBuffer;
        }
        ASMProxyAction proxyAction = new ASMProxyAction();
        byte[] bytes = proxyAction.aop(classfileBuffer);
        //這里可以將bytes寫入到文件官还,輸出處理后的calss內(nèi)容
        return bytes;
    }


    public static void premain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException {
        inst.addTransformer(new PreMainProxyAction());
    }
}

其中ASMProxyAction的內(nèi)容為上一節(jié)ASM例子的內(nèi)容芹橡,只是重新組織了代碼以進(jìn)行復(fù)用,核心內(nèi)容如下:

public byte[] aop(byte[] bytes) {
        ClassReader cr = new ClassReader(bytes);
        return aop(cr);
    }

    public byte[] aop(ClassReader cr) {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        cr.accept(new ClassVisitor(Opcodes.ASM6, cw) {
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                if (!"say".equals(name)) {
                    return mv;
                }
                MethodVisitor aopMV = new MethodVisitor(super.api, mv) {
                    @Override
                    public void visitCode() {
                        super.visitCode();
                        mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                        mv.visitLdcInsn("before core action");
                        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                    }

                    @Override
                    public void visitInsn(int opcode) {
                        if (Opcodes.RETURN == opcode) {
                            mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                            mv.visitLdcInsn("after core action");
                            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                        }
                        super.visitInsn(opcode);
                    }
                };
                return aopMV;
            }
        }, ClassReader.SKIP_DEBUG);
        return cw.toByteArray();
    }
3.3. jar 文件打包

?將這個(gè) Java 類打包成一個(gè) jar 文件望伦,并在其中的 manifest 屬性當(dāng)中加入” Premain-Class”來指定步驟3.1當(dāng)中編寫的那個(gè)帶有 premain 的 Java 類。

3.4. 運(yùn)行

?用如下方式運(yùn)行帶有 Instrumentation 的 Java 程序:

java -javaagent:jar 文件的位置 [= 傳入 premain 的參數(shù) ]

?按照上面示例代碼注釋的內(nèi)容煎殷,輸出處理過后的字節(jié)碼如下:

public class demo/CoreActionImpl implements demo/Action  {


  // access flags 0x1
  public <init>()V
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public say()V
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "before core action"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "hello world"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "after core action"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 1
}

其實(shí)就是上節(jié)ASM處理過后的結(jié)果屯伞,實(shí)現(xiàn)了AOP。即Instrument提供的premain方法豪直,提供了一個(gè)入口劣摇,可以在main方法執(zhí)行前,修改原class的內(nèi)容弓乙,增加自定義邏輯末融。

?需要指出的是,addTransformer 方法并沒有指明要轉(zhuǎn)換哪個(gè)類暇韧,因而在 transform(Transformer 類中)方法中勾习,程序需要自己判斷當(dāng)前的類是否需要轉(zhuǎn)換,如上面的示例懈玻。

4. JDK6 agentmain方式

?JDK5提供的premain方式只能在應(yīng)用啟動(dòng)前對(duì)class進(jìn)行處理巧婶,JDK6 在此基礎(chǔ)上進(jìn)行了改進(jìn),開發(fā)者可以在 main 函數(shù)開始執(zhí)行以后涂乌,再啟動(dòng)自己的處理程序艺栈。

?使用agentmain方式進(jìn)行處理需要如下幾個(gè)步驟

4.1. 提供一個(gè)公共的靜態(tài)方法agentmain:
//<1>
public static void agentmain (String agentArgs, Instrumentation inst);
//<2>
public static void agentmain (String agentArgs);

其中,<1>的優(yōu)先級(jí)比 <2> 高湾盒,將會(huì)被優(yōu)先執(zhí)行(<1>和<2>同時(shí)存在時(shí)湿右,<2>被忽略)。

4.2. 提供一個(gè)或者多個(gè)ClassFileTransformer實(shí)現(xiàn)類

?方法同3.2一致罚勾。不同的是毅人,由于agentmain方式是在虛擬機(jī)啟動(dòng)后進(jìn)行處理的吭狡,這時(shí)候目標(biāo)class可能已經(jīng)被加載過了,需要重新對(duì)目標(biāo)class進(jìn)行處理堰塌,根據(jù)上面的介紹赵刑,可以調(diào)用retransformClasses方法對(duì)類進(jìn)行重新處理。

4.3. jar 文件打包

?將這個(gè) Java 類打包成一個(gè) jar 文件场刑,并在其中的 manifest 屬性當(dāng)中加入” Agent-Class”來指定步驟4.1當(dāng)中編寫的那個(gè)帶有 agentmain 的 Java 類般此。

4.4. 加載jar包

?同premain不一致的是,agentmain的接入需要外部應(yīng)用顯示觸發(fā)牵现。Java SE 6 當(dāng)中提供的 Attach API铐懊,用來向目標(biāo) JVM attach代理工具程序。需要注意的是瞎疼,Attach API 不是 Java 的標(biāo)準(zhǔn) API科乎,而是 Sun 公司提供的一套擴(kuò)展 API。

?Attach API 很簡(jiǎn)單贼急,只有 2 個(gè)主要的類茅茂,都在 com.sun.tools.attach 包里面: VirtualMachine 代表一個(gè) Java 虛擬機(jī),也就是程序需要監(jiān)控的目標(biāo)虛擬機(jī)太抓,提供了 JVM 枚舉空闲,attach 動(dòng)作和 detach 動(dòng)作(Attach 動(dòng)作的相反行為,從 JVM 上面解除一個(gè)代理)等等 ; VirtualMachineDescriptor 則是一個(gè)描述虛擬機(jī)的容器類走敌,配合 VirtualMachine 類完成各種功能碴倾。

?可用如下的方式將一個(gè)jar包attach到一個(gè)運(yùn)行的虛擬機(jī)上去:

public void start(String processId,String agentArgs, String agentJarPath) throws Exception {
    VirtualMachine virtualMachine = null;
    try {
        virtualMachine = VirtualMachine.attach(processId);
        virtualMachine.loadAgent(agentJarPath,agentArgs);
    } finally {
        if (virtualMachine != null) {
            virtualMachine.detach();
        }
    }
}

其中需要的參數(shù)為:

  • processId:目標(biāo)應(yīng)用pid
  • agentArgs:傳給agentmain的參數(shù)
  • agentJarPath:待加載的jar包

?

更多原創(chuàng)內(nèi)容請(qǐng)搜索微信公眾號(hào):啊駝(doubaotaizi)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市掉丽,隨后出現(xiàn)的幾起案子跌榔,更是在濱河造成了極大的恐慌,老刑警劉巖捶障,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件僧须,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡残邀,警方通過查閱死者的電腦和手機(jī)皆辽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芥挣,“玉大人驱闷,你說我怎么就攤上這事】彰猓” “怎么了空另?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蹋砚。 經(jīng)常有香客問我扼菠,道長(zhǎng)摄杂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任循榆,我火速辦了婚禮析恢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘秧饮。我一直安慰自己映挂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布盗尸。 她就那樣靜靜地躺著柑船,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泼各。 梳的紋絲不亂的頭發(fā)上鞍时,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音扣蜻,去河邊找鬼逆巍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛莽使,可吹牛的內(nèi)容都是我干的蒸苇。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼吮旅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了味咳?” 一聲冷哼從身側(cè)響起庇勃,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎槽驶,沒想到半個(gè)月后责嚷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掂铐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年罕拂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片全陨。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡爆班,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辱姨,到底是詐尸還是另有隱情柿菩,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布雨涛,位于F島的核電站枢舶,受9級(jí)特大地震影響懦胞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜凉泄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一躏尉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧后众,春花似錦胀糜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拗盒,卻和暖如春怖竭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背陡蝇。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來泰國打工痊臭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人登夫。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓广匙,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親恼策。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鸦致,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

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