Java動態(tài)追蹤技術(shù)探究,從JSP到Arthas

引子

在遙遠(yuǎn)的希艾斯星球爪哇國塞沃城中番刊,兩名年輕的程序員正在為一件事情苦惱含鳞,程序出問題了,一時(shí)看不出問題出在哪里芹务,于是有了以下對話:

“Debug一下吧蝉绷。”

“線上機(jī)器枣抱,沒開Debug端口熔吗。”

“看日志佳晶,看看請求值和返回值分別是什么桅狠?”

“那段代碼沒打印日志〗窝恚”

“改代碼中跌,加日志,重新發(fā)布一次菇篡′龇”

“懷疑是線程池的問題,重啟會破壞現(xiàn)場驱还∈缺”

長達(dá)幾十秒的沉默之后:“據(jù)說津滞,排查問題的最高境界,就是只通過Review代碼來發(fā)現(xiàn)問題灼伤〈バ欤”

比幾十秒長幾十倍的沉默之后:“我輪詢了那段代碼一十七遍之后,終于得出一個(gè)結(jié)論狐赡∽拆模”

“結(jié)論是?”

“我還沒到達(dá)只通過Review代碼就能發(fā)現(xiàn)問題的至高境界颖侄∧癯”

從JSP說起

對于大多數(shù)Java程序員來說,早期的時(shí)候览祖,都會接觸到一個(gè)叫做JSP(Java Server Pages)的技術(shù)孝鹊。雖然這種技術(shù),在前后端代碼分離展蒂、前后端邏輯分離又活、前后端組織架構(gòu)分離的今天來看,已經(jīng)過時(shí)了锰悼,但是其中還是有一些有意思的東西柳骄,值得拿出來說一說。

當(dāng)時(shí)剛剛處于Java入門時(shí)期的我們箕般,大多數(shù)精力似乎都放在了JSP的頁面展示效果上了:

“這個(gè)表格顯示的行數(shù)不對”

“原來是for循環(huán)寫的有問題耐薯,改一下,刷新頁面再試一遍”

“嗯丝里,好了曲初,表格顯示沒問題了,但是杯聚,登錄人的姓名沒取到啊臼婆,是不是Sesstion獲取有問題?”

“有可能械媒,我再改一下目锭,一會兒再刷新試試”

……

在一遍一遍修改代碼刷新瀏覽器頁面重試的時(shí)候,我們自己也許并沒有注意到一件很酷的事情:我們修改完代碼纷捞,居然只是簡單地刷新一遍瀏覽器頁面痢虹,修改就生效了,整個(gè)過程并沒有重啟JVM主儡。按照我們的常識奖唯,Java程序一般都是在啟動時(shí)加載類文件,如果都像JSP這樣修改完代碼糜值,不用重啟就生效的話丰捷,那文章開頭的問題就可以解決了芭髂:Java文件中加一段日志打印的代碼,不重啟就生效病往,既不破壞現(xiàn)場捣染,又可以定位問題。忍不住試一試:修改停巷、編譯耍攘、替換class文件。額畔勤,不行蕾各,新改的代碼并沒有生效。那為什么偏偏JSP可以呢庆揪?讓我們先來看看JSP的運(yùn)行原理式曲。

當(dāng)我們打開瀏覽器,請求訪問一個(gè)JSP文件的時(shí)候缸榛,整個(gè)過程是這樣的:

[圖片上傳失敗...(image-ad91d8-1560497185049)]

JSP文件處理過程

JSP文件修改過后吝羞,之所以能及時(shí)生效,是因?yàn)閃eb容器(Tomcat)會檢查請求的JSP文件是否被更改過仔掸。如果發(fā)生過更改脆贵,那么就將JSP文件重新解析翻譯成一個(gè)新的Sevlet類闪盔,并加載到JVM中兽间。之后的請求茫叭,都會由這個(gè)新的Servet來處理。這里有個(gè)問題负懦,根據(jù)Java的類加載機(jī)制,在同一個(gè)ClassLoader中柏腻,類是不允許重復(fù)的纸厉。為了繞開這個(gè)限制,Web容器每次都會創(chuàng)建一個(gè)新的ClassLoader實(shí)例五嫂,來加載新編譯的Servlet類颗品。之后的請求都會由這個(gè)新的Servlet來處理,這樣就實(shí)現(xiàn)了新舊JSP的切換沃缘。

HTTP服務(wù)是無狀態(tài)的躯枢,所以JSP的場景基本上都是一次性消費(fèi),這種通過創(chuàng)建新的ClassLoader來“替換”class的做法行得通槐臀,但是對于其他應(yīng)用锄蹂,比如Spring框架,即便這樣做了水慨,對象多數(shù)是單例得糜,對于內(nèi)存中已經(jīng)創(chuàng)建好的對象敬扛,我們無法通過這種創(chuàng)建新的ClassLoader實(shí)例的方法來修改對象行為。

我就是想不重啟應(yīng)用加個(gè)日志打印朝抖,就這么難嗎啥箭?

Java對象行為

既然JSP的辦法行不通,那我們來看看還有沒有其他的辦法治宣。仔細(xì)想想捉蚤,我們會發(fā)現(xiàn),文章開頭的問題本質(zhì)上是動態(tài)改變內(nèi)存中已存在對象的行為的問題炼七。所以缆巧,我們得先弄清楚JVM中和對象行為有關(guān)的地方在哪里,有沒有更改的可能性豌拙。

我們都知道陕悬,對象使用兩種東西來描述事物:行為和屬性。舉個(gè)例子:

public class Person{

  private int age;

  private String name;

  public void speak(String str) {

    System.out.println(str);

 }

 public Person(int age, String name) {

    this.age = age;

    this.name = name;

 }

}
復(fù)制代碼

上面Person類中age和name是屬性按傅,speak是行為捉超。對象是類的事例,每個(gè)對象的屬性都屬于對象本身唯绍,但是每個(gè)對象的行為卻是公共的拼岳。舉個(gè)例子,比如我們現(xiàn)在基于Person類創(chuàng)建了兩個(gè)對象况芒,personA和personB:

Person personA = new Person(43, "lixunhuan");

personA.speak("我是李尋歡");

Person personB = new Person(23, "afei");

personB.speak("我是阿飛");
復(fù)制代碼

personA和personB有各自的姓名和年齡惜纸,但是有共同的行為:speak。想象一下绝骚,如果我們是Java語言的設(shè)計(jì)者耐版,我們會怎么存儲對象的行為和屬性呢?

“很簡單压汪,屬性跟著對象走粪牲,每個(gè)對象都存一份。行為是公共的東西止剖,抽離出來腺阳,單獨(dú)放到一個(gè)地方〈┫悖”

“咦亭引?抽離出公共的部分,跟代碼復(fù)用好像啊扔水⊥词蹋”

“大道至簡,很多東西本來都是殊途同歸≈鹘欤”

也就是說赵哲,第一步我們首先得找到存儲對象行為的這個(gè)公共的地方。一番搜索之后君丁,我們發(fā)現(xiàn)這樣一段描述:

Method area is created on virtual machine startup, shared among all Java virtual machine threads and it is logically part of heap area. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors.

Java的對象行為(方法枫夺、函數(shù))是存儲在方法區(qū)的。

“方法區(qū)中的數(shù)據(jù)從哪來绘闷?”

“方法區(qū)中的數(shù)據(jù)是類加載時(shí)從class文件中提取出來的橡庞。”

“class文件從哪來印蔗?”

“從Java或者其他符合JVM規(guī)范的源代碼中編譯而來扒最。”

“源代碼從哪來华嘹?”

“廢話吧趣,當(dāng)然是手寫!”

“倒著推耙厚,手寫沒問題强挫,編譯沒問題,至于加載……有沒有辦法加載一個(gè)已經(jīng)加載過的類呢薛躬?如果有的話俯渤,我們就能修改字節(jié)碼中目標(biāo)方法所在的區(qū)域,然后重新加載這個(gè)類型宝,這樣方法區(qū)中的對象行為(方法)就被改變了八匠,而且不改變對象的屬性,也不影響已經(jīng)存在對象的狀態(tài)诡曙,那么就可以搞定這個(gè)問題了臀叙。可是价卤,這豈不是違背了JVM的類加載原理?畢竟我們不想改變ClassLoader渊涝∩麒担”

“少年,可以去看看java.lang.instrument.Instrumentation跨释⌒厮剑”

java.lang.instrument.Instrumentation

看完文檔之后,我們發(fā)現(xiàn)這么兩個(gè)接口:redefineClasses和retransformClasses鳖谈。一個(gè)是重新定義class岁疼,一個(gè)是修改class。這兩個(gè)大同小異,看reDefineClasses的說明:

This method is used to replace the definition of a class without reference to the existing class file bytes, as one might do when recompiling from source for fix-and-continue debugging. Where the existing class file bytes are to be transformed (for example in bytecode instrumentation) retransformClasses should be used.

都是替換已經(jīng)存在的class文件捷绒,redefineClasses是自己提供字節(jié)碼文件替換掉已存在的class文件瑰排,retransformClasses是在已存在的字節(jié)碼文件上修改后再替換之。

當(dāng)然暖侨,運(yùn)行時(shí)直接替換類很不安全椭住。比如新的class文件引用了一個(gè)不存在的類,或者把某個(gè)類的一個(gè)field給刪除了等等字逗,這些情況都會引發(fā)異常京郑。所以如文檔中所言,instrument存在諸多的限制:

The redefinition may change method bodies, the constant pool and attributes. The redefinition must not add, remove or rename fields or methods, change the signatures of methods, or change inheritance. These restrictions maybe be lifted in future versions. The class file bytes are not checked, verified and installed until after the transformations have been applied, if the resultant bytes are in error this method will throw an exception.

我們能做的基本上也就是簡單修改方法內(nèi)的一些行為葫掉,這對于我們開頭的問題些举,打印一段日志來說,已經(jīng)足夠了俭厚。當(dāng)然户魏,我們除了通過reTransform來打印日志,還能做很多其他非常有用的事情套腹,這個(gè)下文會進(jìn)行介紹绪抛。

那怎么得到我們需要的class文件呢?一個(gè)最簡單的方法电禀,是把修改后的Java文件重新編譯一遍得到class文件幢码,然后調(diào)用redefineClasses替換。但是對于沒有(或者拿不到尖飞,或者不方便修改)源碼的文件我們應(yīng)該怎么辦呢症副?其實(shí)對于JVM來說,不管是Java也好政基,Scala也好贞铣,任何一種符合JVM規(guī)范的語言的源代碼,都可以編譯成class文件沮明。JVM的操作對象是class文件辕坝,而不是源碼。所以荐健,從這種意義上來講酱畅,我們可以說“JVM跟語言無關(guān)”。既然如此江场,不管有沒有源碼纺酸,其實(shí)我們只需要修改class文件就行了。

直接操作字節(jié)碼

Java是軟件開發(fā)人員能讀懂的語言址否,class字節(jié)碼是JVM能讀懂的語言餐蔬,class字節(jié)碼最終會被JVM解釋成機(jī)器能讀懂的語言。無論哪種語言,都是人創(chuàng)造的樊诺。所以仗考,理論上(實(shí)際上也確實(shí)如此)人能讀懂上述任何一種語言,既然能讀懂啄骇,自然能修改痴鳄。只要我們愿意,我們完全可以跳過Java編譯器缸夹,直接寫字節(jié)碼文件痪寻,只不過這并不符合時(shí)代的發(fā)展罷了,畢竟高級語言設(shè)計(jì)之始就是為我們?nèi)祟愃?wù)虽惭,其開發(fā)效率也比機(jī)器語言高很多橡类。

對于人類來說,字節(jié)碼文件的可讀性遠(yuǎn)遠(yuǎn)沒有Java代碼高芽唇。盡管如此顾画,還是有一些杰出的程序員們創(chuàng)造出了可以用來直接編輯字節(jié)碼的框架,提供接口可以讓我們方便地操作字節(jié)碼文件匆笤,進(jìn)行注入修改類的方法研侣,動態(tài)創(chuàng)造一個(gè)新的類等等操作。其中最著名的框架應(yīng)該就是ASM了炮捧,cglib庶诡、Spring等框架中對于字節(jié)碼的操作就建立在ASM之上。

我們都知道咆课,Spring的AOP是基于動態(tài)代理實(shí)現(xiàn)的末誓,Spring會在運(yùn)行時(shí)動態(tài)創(chuàng)建代理類,代理類中引用被代理類书蚪,在被代理的方法執(zhí)行前后進(jìn)行一些神秘的操作喇澡。那么,Spring是怎么在運(yùn)行時(shí)創(chuàng)建代理類的呢殊校?動態(tài)代理的美妙之處晴玖,就在于我們不必手動為每個(gè)需要被代理的類寫代理類代碼,Spring在運(yùn)行時(shí)會根據(jù)需要?jiǎng)討B(tài)地創(chuàng)造出一個(gè)類为流,這里創(chuàng)造的過程并非通過字符串寫Java文件窜醉,然后編譯成class文件,然后加載艺谆。Spring會直接“創(chuàng)造”一個(gè)class文件,然后加載拜英,創(chuàng)造class文件的工具静汤,就是ASM了。

到這里,我們知道了用ASM框架直接操作class文件虫给,在類中加一段打印日志的代碼藤抡,然后調(diào)用retransformClasses就可以了抹估。

BTrace

截止到目前,我們都是停留在理論描述的層面药蜻。那么如何進(jìn)行實(shí)現(xiàn)呢?先來看幾個(gè)問題:

  1. 在我們的工程中语泽,誰來做這個(gè)尋找字節(jié)碼贸典,修改字節(jié)碼踱卵,然后reTransform的動作呢?我們并非先知惋砂,不可能知道未來有沒有可能遇到文章開頭的這種問題妒挎。考慮到性價(jià)比西饵,我們也不可能在每個(gè)工程中都開發(fā)一段專門做這些修改字節(jié)碼、重新加載字節(jié)碼的代碼罗标。
  2. 如果JVM不在本地,在遠(yuǎn)程呢彻消?
  3. 如果連ASM都不會用呢宙拉?能不能更通用一些,更“傻瓜”一些煌贴。

幸運(yùn)的是锥忿,因?yàn)橛蠦Trace的存在,我們不必自己寫一套這樣的工具了淹朋。什么是BTrace呢?BTrace已經(jīng)開源础芍,項(xiàng)目描述極其簡短:

A safe, dynamic tracing tool for the Java platform.

BTrace是基于Java語言的一個(gè)安全的仑性、可提供動態(tài)追蹤服務(wù)的工具。BTrace基于ASM歼捐、Java Attach Api刽辙、Instruments開發(fā)窥岩,為用戶提供了很多注解宰缤。依靠這些注解慨灭,我們可以編寫B(tài)Trace腳本(簡單的Java代碼)達(dá)到我們想要的效果,而不必深陷于ASM對字節(jié)碼的操作中不可自拔呻疹。

看BTrace官方提供的一個(gè)簡單例子:攔截所有java.io包中所有類中以read開頭的方法筹陵,打印類名、方法名和參數(shù)名并思。當(dāng)程序IO負(fù)載比較高的時(shí)候语稠,就可以從輸出的信息中看到是哪些類所引起,是不是很方便输涕?

package com.sun.btrace.samples;

import com.sun.btrace.annotations.*;
import com.sun.btrace.AnyType;
import static com.sun.btrace.BTraceUtils.*;

/**
 * This sample demonstrates regular expression
 * probe matching and getting input arguments
 * as an array - so that any overload variant
 * can be traced in "one place". This example
 * traces any "readXX" method on any class in
 * java.io package. Probed class, method and arg
 * array is printed in the action.
 */
@BTrace public class ArgArray {
    @OnMethod(
        clazz="/java\\.io\\..*/",
        method="/read.*/"
    )
    public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {
        println(pcn);
        println(pmn);
        printArray(args);
    }
}
復(fù)制代碼

再來看另一個(gè)例子:每隔2秒打印截止到當(dāng)前創(chuàng)建過的線程數(shù)慨畸。

package com.sun.btrace.samples;

import com.sun.btrace.annotations.*;
import static com.sun.btrace.BTraceUtils.*;
import com.sun.btrace.annotations.Export;

/**
 * This sample creates a jvmstat counter and
 * increments it everytime Thread.start() is
 * called. This thread count may be accessed
 * from outside the process. The @Export annotated
 * fields are mapped to jvmstat counters. The counter
 * name is "btrace." + <className> + "." + <fieldName>
 */ 
@BTrace public class ThreadCounter {

    // create a jvmstat counter using @Export
    @Export private static long count;

    @OnMethod(
        clazz="java.lang.Thread",
        method="start"
    ) 
    public static void onnewThread(@Self Thread t) {
        // updating counter is easy. Just assign to
        // the static field!
        count++;
    }

    @OnTimer(2000) 
    public static void ontimer() {
        // we can access counter as "count" as well
        // as from jvmstat counter directly.
        println(count);
        // or equivalently ...
        println(Counters.perfLong("btrace.com.sun.btrace.samples.ThreadCounter.count"));
    }
}
復(fù)制代碼

看了上面的用法是不是有所啟發(fā)寸士?忍不住冒出來許多想法。比如查看HashMap什么時(shí)候會觸發(fā)rehash厢汹,以及此時(shí)容器中有多少元素等等谐宙。

有了BTrace凡蜻,文章開頭的問題可以得到完美的解決。至于BTrace具體有哪些功能划栓,腳本怎么寫忠荞,這些Git上BTrace工程中有大量的說明和舉例,網(wǎng)上介紹BTrace用法的文章更是恒河沙數(shù)堂油,這里就不再贅述了碧绞。

我們明白了原理,又有好用的工具支持迫靖,剩下的就是發(fā)揮我們的創(chuàng)造力了兴使,只需在合適的場景下合理地進(jìn)行使用即可。

既然BTrace能解決上面我們提到的所有問題蜈首,那么BTrace的架構(gòu)是怎樣的呢欠母?

BTrace主要有下面幾個(gè)模塊:

  1. BTrace腳本:利用BTrace定義的注解,我們可以很方便地根據(jù)需要進(jìn)行腳本的開發(fā)踩寇。
  2. Compiler:將BTrace腳本編譯成BTrace class文件六水。
  3. Client:將class文件發(fā)送到Agent辣卒。
  4. Agent:基于Java的Attach Api荣茫,Agent可以動態(tài)附著到一個(gè)運(yùn)行的JVM上场靴,然后開啟一個(gè)BTrace Server,接收client發(fā)過來的BTrace腳本咧欣;解析腳本轨帜,然后根據(jù)腳本中的規(guī)則找到要修改的類;修改字節(jié)碼后哮兰,調(diào)用Java Instrument的reTransform接口梢什,完成對對象行為的修改并使之生效。

整個(gè)BTrace的架構(gòu)大致如下:

[圖片上傳失敗...(image-5cb505-1560497185048)]

BTrace工作流程

BTrace最終借Instruments實(shí)現(xiàn)class的替換囤躁。如上文所說荔睹,出于安全考慮僻他,Instruments在使用上存在諸多的限制,BTrace也不例外满哪。BTrace對JVM來說是“只讀的”劝篷,因此BTrace腳本的限制如下:

  1. 不允許創(chuàng)建對象
  2. 不允許創(chuàng)建數(shù)組
  3. 不允許拋異常
  4. 不允許catch異常
  5. 不允許隨意調(diào)用其他對象或者類的方法,只允許調(diào)用com.sun.btrace.BTraceUtils中提供的靜態(tài)方法(一些數(shù)據(jù)處理和信息輸出工具)
  6. 不允許改變類的屬性
  7. 不允許有成員變量和方法像鸡,只允許存在static public void方法
  8. 不允許有內(nèi)部類哈恰、嵌套類
  9. 不允許有同步方法和同步塊
  10. 不允許有循環(huán)
  11. 不允許隨意繼承其他類(當(dāng)然志群,java.lang.Object除外)
  12. 不允許實(shí)現(xiàn)接口
  13. 不允許使用assert
  14. 不允許使用Class對象

如此多的限制锌云,其實(shí)可以理解夸楣。BTrace要做的是,雖然修改了字節(jié)碼,但是除了輸出需要的信息外紧显,對整個(gè)程序的正常運(yùn)行并沒有影響缕棵。

Arthas

BTrace腳本在使用上有一定的學(xué)習(xí)成本,如果能把一些常用的功能封裝起來篙程,對外直接提供簡單的命令即可操作的話别厘,那就再好不過了。阿里的工程師們早已想到這一點(diǎn)氮发,就在去年(2018年9月份)冗懦,阿里巴巴開源了自己的Java診斷工具——Arthas。Arthas提供簡單的命令行操作颈畸,功能強(qiáng)大没讲。究其背后的技術(shù)原理食零,和本文中提到的大致無二贰谣。Arthas的文檔很全面,想詳細(xì)了解的話可以戳這里考廉。

本文旨在說明Java動態(tài)追蹤技術(shù)的來龍去脈昌粤,掌握技術(shù)背后的原理之后啄刹,只要愿意,各位讀者也可以開發(fā)出自己的“冰封王座”出來袱讹。

尾聲:三生萬物

歡迎大家關(guān)注我的主頁捷雕,每天都有技術(shù)干貨更新哦~

現(xiàn)在壹甥,讓我們試著站在更高的地方“俯瞰”這些問題。

Java的Instruments給運(yùn)行時(shí)的動態(tài)追蹤留下了希望浦译,Attach API則給運(yùn)行時(shí)動態(tài)追蹤提供了“出入口”俄占,ASM則大大方便了“人類”操作Java字節(jié)碼的操作缸榄。

基于Instruments和Attach API前輩們創(chuàng)造出了諸如JProfiler、Jvisualvm她肯、BTrace鹰贵、Arthas這樣的工具。以ASM為基礎(chǔ)發(fā)展出了cglib籽前、動態(tài)代理,繼而是應(yīng)用廣泛的Spring AOP肄梨。

Java是靜態(tài)語言挠锥,運(yùn)行時(shí)不允許改變數(shù)據(jù)結(jié)構(gòu)蓖租。然而,Java 5引入Instruments齐婴,Java 6引入Attach API之后稠茂,事情開始變得不一樣了。雖然存在諸多限制,然而鲫售,在前輩們的努力下情竹,僅僅是利用預(yù)留的近似于“只讀”的這一點(diǎn)點(diǎn)狹小的空間,仍然創(chuàng)造出了各種大放異彩的技術(shù)雏蛮,極大地提高了軟件開發(fā)人員定位問題的效率阱州。

計(jì)算機(jī)應(yīng)該是人類有史以來最偉大的發(fā)明之一苔货,從電磁感應(yīng)磁生電,到高低電壓模擬0和1的比特姻灶,再到二進(jìn)制表示出幾種基本類型诈茧,再到基本類型表示出無窮的對象,最后無窮的對象組合交互模擬現(xiàn)實(shí)生活乃至整個(gè)宇宙曾沈。

兩千五百年前,《道德經(jīng)》有言:“道生一疤苹,一生二敛腌,二生三像樊,三生萬物〔”

兩千五百年后涂滴,計(jì)算機(jī)的發(fā)展過程也大抵如此吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市搁料,隨后出現(xiàn)的幾起案子郭计,更是在濱河造成了極大的恐慌,老刑警劉巖梧乘,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宋下,死亡現(xiàn)場離奇詭異辑莫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)枝笨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門横浑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人洒缀,你說我怎么就攤上這事欺冀∫” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵瘫俊,是天一觀的道長扛芽。 經(jīng)常有香客問我积瞒,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任银酬,我火速辦了婚禮筐钟,結(jié)果婚禮上篓冲,老公的妹妹穿的比我還像新娘。我一直安慰自己嗤攻,他們只是感情好诽俯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著辛臊,像睡著了一般房交。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上刃唤,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天透揣,我揣著相機(jī)與錄音川抡,去河邊找鬼崖堤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛密幔,可吹牛的內(nèi)容都是我干的胯甩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼木柬,長吁一口氣:“原來是場噩夢啊……” “哼眉枕!你這毒婦竟也來了怜森?” 一聲冷哼從身側(cè)響起副硅,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎伶授,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體违诗,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诸迟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年阵苇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了感论。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片比肄。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芳绩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出搪花,到底是詐尸還是另有隱情嘹害,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布倚聚,位于F島的核電站,受9級特大地震影響授账,放射性物質(zhì)發(fā)生泄漏白热。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一纳击、第九天 我趴在偏房一處隱蔽的房頂上張望焕数。 院中可真熱鬧,春花似錦识脆、人聲如沸善已。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽的猛。三九已至辑甜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間猫牡,已是汗流浹背邓线。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工骇陈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留你雌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓拨拓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親氓栈。 傳聞我的和親對象是個(gè)殘疾皇子渣磷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

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

  • 轉(zhuǎn)自陳明乾的博客,可能有一定更新授瘦。 轉(zhuǎn)原文聲明:原創(chuàng)作品醋界,允許轉(zhuǎn)載竟宋,轉(zhuǎn)載時(shí)請務(wù)必以超鏈接形式標(biāo)明文章 原始出處 、...
    C86guli閱讀 4,684評論 6 72
  • JAVA面試題 1形纺、作用域public,private,protected,以及不寫時(shí)的區(qū)別答:區(qū)別如下:作用域 ...
    JA尐白閱讀 1,152評論 1 0
  • 整理來自互聯(lián)網(wǎng) 1丘侠,JDK:Java Development Kit,java的開發(fā)和運(yùn)行環(huán)境挡篓,java的開發(fā)工具...
    Ncompass閱讀 1,537評論 0 6
  • 從奶奶那里聽來的故事,稍作改動官研,添加了一些情節(jié)秽澳。 算是一份記憶。 1. 李家是廟子村的地主戏羽,家里向上數(shù)三輩都靠種花...
    Toomm閱讀 422評論 0 3
  • 活著活著發(fā)現(xiàn)忘了最初想要什么始花,欲望太大容易讓我們不自知甚至于狂妄自大妄讯,我們還是應(yīng)該追求自己最初想要的東西不要讓...
    合肥李風(fēng)麗閱讀 131評論 0 0