深入理解Java虛擬機六

1.注解處理器

? ? ? ?注解處理器主要有三個用途乓旗。一是定義編譯規(guī)則,并檢查被編譯的源文件刹前。二是修改已有源代碼泳赋。三是生成新的源代碼。其中喇喉,第二種涉及了Java編譯器的內(nèi)部API祖今,因此并不推薦。第三種較為常見,是OpenJDK工具jcstress千诬,以及JMH生成測試代碼的方式撒踪。Java源代碼的編譯過程可分為三個步驟:
? ? ? ?1. 將源文件解析為抽象語法樹;
? ? ? ?2. 調(diào)用已注冊的注解處理器大渤;
? ? ? ?3. 生成字節(jié)碼制妄。
? ? ? ?如果在第2步調(diào)用注解處理器過程中生成了新的源文件,那么編譯器將重復(fù)第1泵三、2步耕捞,解析并且處理新生成的源文件。每次重復(fù)我們稱之為一輪(Round)烫幕。也就是說俺抽,第一輪解析、處理的是輸入至編譯器中的已有源文件较曼。如果注解處理器生成了新的源文件磷斧,則開始第二輪、第三輪捷犹,解析并且處理這些新生成的源文件弛饭。當注解處理器不再生成新的源文件,編譯進入最后一輪萍歉,并最終進入生成字節(jié)碼的第3步侣颂。所有的注解處理器類都需要實現(xiàn)接口Processor。該接口主要有四個重要方法枪孩。其中憔晒,init方法用來存放注解處理器的初始化代碼。之所以不用構(gòu)造器蔑舞,是因為在Java編譯器中拒担,注解處理器的實例是通過反射API生成的。也正是因為使用反射API攻询,每個注解處理器類都需要定義一個無參數(shù)構(gòu)造器从撼。通常來說,當編寫注解處理器時蜕窿,我們不聲明任何構(gòu)造器谋逻,并依賴于Java編譯器,為之插入一個無參數(shù)構(gòu)造器桐经。而具體的初始化代碼毁兆,則放入init方法之中。在剩下的三個方法中阴挣,getSupportedAnnotationTypes方法將返回注解處理器所支持的注解類型气堕,這些注解類型只需用字符串形式表示即可。getSupportedSourceVersion方法將返回該處理器所支持的Java版本,通常茎芭,這個版本需要與你的Java編譯器版本保持一致揖膜;而process方法則是最為關(guān)鍵的注解處理方法。JDK提供了一個實現(xiàn)Processor接口的抽象類AbstractProcessor梅桩。該抽象類實現(xiàn)了init壹粟、getSupportedAnnotationTypesgetSupportedSourceVersion方法。它的子類可以通過@SupportedAnnotationTypes@SupportedSourceVersion注解來聲明所支持的注解類型以及Java版本宿百。

2.基準測試框架JMH

? ? ? ?Java程序的性能測試存在著許多深坑趁仙,有來自Java虛擬機的,有來自操作系統(tǒng)的垦页,甚至有來自硬件系統(tǒng)的雀费。如果沒有足夠的知識,那么性能測試的結(jié)果很有可能是有偏差的痊焊。性能基準測試框架JMH是OpenJDK中的其中一個開源項目盏袄。它內(nèi)置了許多功能,來規(guī)避由Java虛擬機中的即時編譯器或者其他優(yōu)化對性能測試造成的影響薄啥。此外辕羽,它還提供了不少策略來降低來自操作系統(tǒng)以及硬件系統(tǒng)的影響。開發(fā)人員僅需將所要測試的業(yè)務(wù)邏輯通過@Benchmark注解罪佳,便可以讓JMH的注解處理器自動生成真正的性能測試代碼逛漫,以及相應(yīng)的性能測試配置文件。

  • @Fork允許開發(fā)人員指定所要Fork出的Java虛擬機的數(shù)目赘艳。
  • @BenchmarkMode允許指定性能數(shù)據(jù)的格式。
  • @Warmup@Measurement允許配置預(yù)熱迭代或者測試迭代的數(shù)目克握,每個迭代的時間以及每個操作包含多少次對測試方法的調(diào)用蕾管。
  • @State允許配置測試程序的狀態(tài)。測試前對程序狀態(tài)的初始化以及測試后對程序狀態(tài)的恢復(fù)或者校驗可分別通過@Setup@TearDown來實現(xiàn)菩暗。

3.Java虛擬機監(jiān)控診斷工具

3.1JPS
? ? ? ?JDK中的jps命令(幫助文檔)沿用了同樣的概念:它將打印所有正在運行的Java進程的相關(guān)信息掰曾。在默認情況下,jps的輸出信息包括Java進程的進程ID以及主類名停团。我們還可以通過追加參數(shù)旷坦,來打印額外的信息。例如佑稠,-l將打印模塊名以及包名秒梅;-v將打印傳遞給Java虛擬機的參數(shù)(如-XX:+UnlockExperimental-VMOptions -XX:+UseZGC);-m將打印傳遞給主類的參數(shù)舌胶。具體的示例如下所示:

$ jps -mlv
    18331 org.example.Foo Hello World
    18332 jdk.jcmd/sun.tools.jps.Jps -mlv 
    -Dapplication.home=/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home 
    -Xms8m -Djdk.module.main=jdk.jcmd

? ? ? ?注意:如果某Java進程關(guān)閉了默認開啟的UsePerfData參數(shù)(即使用參數(shù)-XX:-UsePerfData)捆蜀,那么jps命令(以及下面介紹的jstat)將無法探知該Java進程。當獲得Java進程的進程ID之后,我們便可以調(diào)用接下來介紹的各項監(jiān)控及診斷工具了辆它。
3.2JSTAT
? ? ? ?jstat命令(幫助文檔)可用來打印目標Java進程的性能數(shù)據(jù)誊薄。它包括多條子命令,如下所示:

$ jstat -options
-class
-compiler
-gc
-gccapacity
-gccause
-gcmetacapacity
-gcnew
-gcnewcapacity
-gcold
-gcoldcapacity
-gcutil
-printcompilation

? ? ? ?在這些子命令中锰茉,-class將打印類加載相關(guān)的數(shù)據(jù)呢蔫,-compiler-printcompilation將打印即時編譯相關(guān)的數(shù)據(jù)。剩下的都是以-gc為前綴的子命令飒筑,它們將打印垃圾回收相關(guān)的數(shù)據(jù)片吊。默認情況下,jstat只會打印一次性能數(shù)據(jù)扬霜。我們可以將它配置為每隔一段時間打印一次定鸟,直至目標Java進程終止,或者達到我們所配置的最大打印次數(shù)著瓶。具體示例如下所示:

# Usage: jstat -outputOptions [-t] [-hlines] VMID [interval [count]]
$ jstat -gc 22126 1s 4
S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT    CGC    CGCT     GCT   
17472,0 17472,0  0,0    0,0   139904,0 47146,4   349568,0   21321,0   30020,0 28001,8 4864,0 4673,4     22    0,080   3      0,270   0      0,000    0,350
17472,0 17472,0 420,6   0,0   139904,0 11178,4   349568,0   21321,0   30020,0 28090,1 4864,0 4674,2     28    0,084   3      0,270   0      0,000    0,354
17472,0 17472,0  0,0   403,9  139904,0 139538,4  349568,0   21323,4   30020,0 28137,2 4864,0 4674,2     34    0,088   4      0,359   0      0,000    0,446
17472,0 17472,0  0,0    0,0   139904,0   0,0     349568,0   21326,1   30020,0 28093,6 4864,0 4673,4     38    0,091   5      0,445   0      0,000    0,536

當監(jiān)控本地環(huán)境的Java進程時联予,VMID可以簡單理解為PID。如果需要監(jiān)控遠程環(huán)境的Java進程材原,你可以參考jstat的幫助文檔沸久。

? ? ? ?在上面這個示例中,22126進程是一個使用了CMS垃圾回收器的Java進程余蟹。我們利用jstat-gc子命令卷胯,來打印該進程垃圾回收相關(guān)的數(shù)據(jù)。命令最后的1s 4表示每隔1秒打印一次威酒,共打印4次窑睁。在-gc子命令的輸出中,前四列分別為兩個Survivor區(qū)的容量(Capacity)和已使用量(Utility)葵孤。我們可以看到担钮,這兩個Survivor區(qū)的容量相等,而且始終有一個Survivor區(qū)的內(nèi)存使用量為0尤仍。當使用默認的G1 GC時箫津,輸出結(jié)果則有另一些特征:

$ jstat -gc 22208 1s
S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT    CGC    CGCT     GCT   
0,0   16384,0  0,0   16384,0 210944,0 192512,0  133120,0    5332,5   28848,0 26886,4 4864,0 4620,5     19    0,067   1      0,016   2      0,002    0,084
0,0   16384,0  0,0   16384,0 210944,0 83968,0   133120,0    5749,9   29104,0 27132,8 4864,0 4621,0     21    0,078   1      0,016   2      0,002    0,095
0,0    0,0    0,0    0,0   71680,0  18432,0   45056,0    20285,1   29872,0 27952,4 4864,0 4671,6     23    0,089   2      0,063   2      0,002    0,153
0,0   2048,0  0,0   2048,0 69632,0  28672,0   45056,0    18608,1   30128,0 28030,4 4864,0 4672,4     32    0,093   2      0,063   2      0,002    0,158
...

? ? ? ?在上面這個示例中,jstat每隔1s便會打印垃圾回收的信息宰啦,并且不斷重復(fù)下去苏遥。你可能已經(jīng)留意到,S0CS0U始終為0赡模,而且另一個Survivor區(qū)的容量(S1C)可能會下降至0田炭。這是因為,當使用G1 GC時纺裁,Java虛擬機不再設(shè)置Eden區(qū)诫肠、Survivor區(qū)司澎,老年代區(qū)的內(nèi)存邊界,而是將堆劃分為若干個等長內(nèi)存區(qū)域栋豫。每個內(nèi)存區(qū)域都可以作為Eden區(qū)挤安、Survivor區(qū)以及老年代區(qū)中的任一種,并且可以在不同區(qū)域類型之間來回切換丧鸯。(參考鏈接)換句話說蛤铜,邏輯上我們只有一個Survivor區(qū)。當需要遷移Survivor區(qū)中的數(shù)據(jù)時(即Copying GC)丛肢,我們只需另外申請一個或多個內(nèi)存區(qū)域围肥,作為新的Survivor區(qū)。因此蜂怎,Java虛擬機決定在使用G1 GC時穆刻,將所有Survivor內(nèi)存區(qū)域的總?cè)萘恳约耙咽褂昧看娣胖罶1C和S1U中,而S0C和S0U則被設(shè)置為0杠步。當發(fā)生垃圾回收時氢伟,Java虛擬機可能出現(xiàn)Survivor內(nèi)存區(qū)域內(nèi)的對象 被回收或晉升的現(xiàn)象。在這種情況下幽歼,Java虛擬機會將這塊內(nèi)存區(qū)域回收朵锣,并標記為可分配的狀態(tài)。這樣子做的結(jié)果是甸私,堆中可能完全沒有Survivor內(nèi)存區(qū)域诚些,因而相應(yīng)的S1C和S1U將會是0。jstat還有一個非常有用的參數(shù)-t皇型,它將在每行數(shù)據(jù)之前打印目標Java進程的啟動時間诬烹。例如,在下面這個示例中弃鸦,第一列代表該Java進程已經(jīng)啟動了10.7秒椅您。

$ jstat -gc -t 22407
Timestamp        S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT    CGC    CGCT     GCT   
           10,7  0,0    0,0    0,0    0,0   55296,0  45056,0   34816,0    20267,8   30128,0 27975,3 4864,0 4671,6     33    0,086   3      0,111   2      0,001    0,198

? ? ? ?我們可以比較Java進程的啟動時間以及總GC時間(GCT列),或者兩次測量的間隔時間以及總GC時間的增量寡键,來得出GC時間占運行時間的比例。如果該比例超過20%雪隧,則說明目前堆的壓力較大西轩;如果該比例超過90%,則說明堆里幾乎沒有可用空間脑沿,隨時都可能拋出OOM異常藕畔。jstat還可以用來判斷是否出現(xiàn)內(nèi)存泄漏。在長時間運行的Java程序中庄拇,我們可以運行jstat命令連續(xù)獲取多行性能數(shù)據(jù)注服,并取這幾行數(shù)據(jù)中OU列(即已占用的老年代內(nèi)存)的最小值韭邓。然后,我們每隔一段較長的時間重復(fù)一次上述操作溶弟,來獲得多組OU最小值女淑。如果這些值呈上漲趨勢,則說明該Java程序的老年代內(nèi)存已使用量在不斷上漲辜御,這意味著無法回收的對象在不斷增加鸭你,因此很有可能存在內(nèi)存泄漏。

上面沒有涉及的列(或者其他子命令的輸出)擒权,你可以查閱幫助文檔了解具體含義袱巨。至于文檔中漏掉的CGC和CGCT,它們分別代表并發(fā)GC Stop-The-World的次數(shù)和時間碳抄。

3.3JMAP
? ? ? ?在這種情況下愉老,我們便可以請jmap命令(幫助文檔)出馬,分析Java虛擬機堆中的對象剖效。jmap同樣包括多條子命令嫉入。
? ? ? ?1. -clstats,該子命令將打印被加載類的信息贱鄙。
? ? ? ?2. -finalizerinfo劝贸,該子命令將打印所有待finalize的對象。
? ? ? ?3. -histo逗宁,該子命令將統(tǒng)計各個類的實例數(shù)目以及占用內(nèi)存映九,并按照內(nèi)存使用量從多至少的順序排列。此外瞎颗,-histo:live只統(tǒng)計堆中的存活對象件甥。
? ? ? ?4. -dump,該子命令將導(dǎo)出Java虛擬機堆的快照哼拔。同樣引有,-dump:live只保存堆中的存活對象。
? ? ? ?我們通常會利用jmap -dump:live,format=b,file=filename.bin命令倦逐,將堆中所有存活對象導(dǎo)出至一個文件之中譬正。這里format=b將使jmap導(dǎo)出與hprof(在Java9中已被移除)、-XX:+HeapDumpAfterFullGC檬姥、-XX:+HeapDumpOnOutOfMemory-Error格式一致的文件曾我。這種格式的文件可以被其他GUI工具查看,具體我會在下一篇中進行演示健民。下面我貼了一段-histo子命令的輸出:

$ jmap -histo 22574
 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:        500004       20000160  org.python.core.PyComplex
   2:        570866       18267712  org.python.core.PyFloat
   3:        360295       18027024  [B (java.base@11)
   4:        339394       11429680  [Lorg.python.core.PyObject;
   5:        308637       11194264  [Ljava.lang.Object; (java.base@11)
   6:        301378        9291664  [I (java.base@11)
   7:        225103        9004120  java.math.BigInteger (java.base@11)
   8:        507362        8117792  org.python.core.PySequence$1
   9:        285009        6840216  org.python.core.PyLong
  10:        282908        6789792  java.lang.String (java.base@11)
  ...
2281:             1             16  traceback$py
2282:             1             16  unicodedata$py
Total       5151277      167944400

? ? ? ?由于jmap將訪問堆中的所有對象抒巢,為了保證在此過程中不被應(yīng)用線程干擾,jmap需要借助安全點機制秉犹,讓所有線程停留在不改變堆中數(shù)據(jù)的狀態(tài)蛉谜。也就是說稚晚,由jmap導(dǎo)出的堆快照必定是安全點位置的。這可能導(dǎo)致基于該堆快照的分析結(jié)果存在偏差型诚。舉個例子客燕,假設(shè)在編譯生成的機器碼中,某些對象的生命周期在兩個安全點之間俺驶,那么:live選項將無法探知到這些對象幸逆。另外,如果某個線程長時間無法跑到安全點暮现,jmap將一直等下去还绘。上一小節(jié)的jstat則不同。這是因為垃圾回收器會主動將jstat所需要的摘要數(shù)據(jù)保存至固定位置之中栖袋,而jstat只需直接讀取即可拍顷。關(guān)于這種長時間等待的情況,你可以通過下面這段程序來復(fù)現(xiàn):

// 暫停時間較長塘幅,約為二三十秒昔案,可酌情調(diào)整。
// CTRL+C的SIGINT信號無法停止电媳,需要SIGKILL踏揣。
static double sum = 0;

public static void main(String[] args) {
  for (int i = 0; i < 0x77777777; i++) { // counted loop
    sum += Math.log(i); // Math.log is an intrinsic
  }
}

? ? ? ?jmap(以及接下來的jinfojstackjcmd)依賴于Java虛擬機的Attach API匾乓,因此只能監(jiān)控本地Java進程捞稿。一旦開啟Java虛擬機參數(shù)DisableAttachMechanism(即使用參數(shù)-XX:+DisableAttachMechanism),基于Attach API的命令將無法執(zhí)行拼缝。反過來說娱局,如果你不想被其他進程監(jiān)控,那么你需要開啟該參數(shù)咧七。
3.4JINFO
? ? ? ?jinfo命令(幫助文檔)可用來查看目標Java進程的參數(shù)衰齐,如傳遞給Java虛擬機的-X(即輸出中的jvm_args)、-XX參數(shù)(即輸出中的VM Flags)继阻,以及可在Java層面通過System.getProperty獲取的-D參數(shù)(即輸出中的System Properties)耻涛。具體的示例如下所示:

$ jinfo 31185
Java System Properties:

gopherProxySet=false
awt.toolkit=sun.lwawt.macosx.LWCToolkit
java.specification.version=11
sun.cpu.isalist=
sun.jnu.encoding=UTF-8
...

VM Flags:
-XX:CICompilerCount=4 -XX:ConcGCThreads=3 -XX:G1ConcRefinementThreads=10 -XX:G1HeapRegionSize=2097152 -XX:GCDrainStackTargetSize=64 -XX:InitialHeapSize=536870912 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=8589934592 -XX:MaxNewSize=5152702464 -XX:MinHeapDeltaBytes=2097152 -XX:NonNMethodCodeHeapSize=5835340 -XX:NonProfiledCodeHeapSize=122911450 -XX:ProfiledCodeHeapSize=122911450 -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC

VM Arguments:
jvm_args: -Xlog:gc -Xmx1024m
java_command: org.example.Foo
java_class_path (initial): .
Launcher Type: SUN_STANDARD

? ? ? ?jinfo還可以用來修改目標Java進程的“manageable”虛擬機參數(shù)。舉個例子瘟檩,我們可以使用jinfo -flag +HeapDumpAfterFullGC <PID>命令犬第,開啟<PID>所指定的Java進程的HeapDumpAfterFullGC參數(shù)。你可以通過下述命令查看其他"manageable"虛擬機參數(shù):

$ java -XX:+PrintFlagsFinal -version | grep manageable   
     intx CMSAbortablePrecleanWaitMillis           = 100                                    {manageable} {default}
     intx CMSTriggerInterval                       = -1                                     {manageable} {default}
     intx CMSWaitDuration                          = 2000                                   {manageable} {default}
     bool HeapDumpAfterFullGC                      = false                                  {manageable} {default}
     bool HeapDumpBeforeFullGC                     = false                                  {manageable} {default}
     bool HeapDumpOnOutOfMemoryError               = false                                  {manageable} {default}
    ccstr HeapDumpPath                             =                                        {manageable} {default}
    uintx MaxHeapFreeRatio                         = 70                                     {manageable} {default}
    uintx MinHeapFreeRatio                         = 40                                     {manageable} {default}
     bool PrintClassHistogram                      = false                                  {manageable} {default}
     bool PrintConcurrentLocks                     = false                                  {manageable} {default}
java version "11" 2018-09-25
Java(TM) SE Runtime Environment 18.9 (build 11+28)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11+28, mixed mode)

3.5JSTACK
? ? ? ?jstack命令(幫助文檔)可以用來打印目標Java進程中各個線程的棧軌跡芒帕,以及這些線程所持有的鎖。jstack的其中一個應(yīng)用場景便是死鎖檢測丰介。這里我用jstack獲取一個已經(jīng)死鎖了的Java程序的棧信息背蟆。具體輸出如下所示:

$ jstack 31634
...

"Thread-0" #12 prio=5 os_prio=31 cpu=1.32ms elapsed=34.24s tid=0x00007fb08601c800 nid=0x5d03 waiting for monitor entry  [0x000070000bc7e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
 at DeadLock.foo(DeadLock.java:18)
 - waiting to lock <0x000000061ff904c0> (a java.lang.Object)
 - locked <0x000000061ff904b0> (a java.lang.Object)
 at DeadLock$$Lambda$1/0x0000000800060840.run(Unknown Source)
 at java.lang.Thread.run(java.base@11/Thread.java:834)

"Thread-1" #13 prio=5 os_prio=31 cpu=1.43ms elapsed=34.24s tid=0x00007fb08601f800 nid=0x5f03 waiting for monitor entry  [0x000070000bd81000]
   java.lang.Thread.State: BLOCKED (on object monitor)
 at DeadLock.bar(DeadLock.java:33)
 - waiting to lock <0x000000061ff904b0> (a java.lang.Object)
 - locked <0x000000061ff904c0> (a java.lang.Object)
 at DeadLock$$Lambda$2/0x0000000800063040.run(Unknown Source)
 at java.lang.Thread.run(java.base@11/Thread.java:834)

...

JNI global refs: 6, weak refs: 0


Found one Java-level deadlock:
=============================
"Thread-0":
  waiting to lock monitor 0x00007fb083015900 (object 0x000000061ff904c0, a java.lang.Object),
  which is held by "Thread-1"
"Thread-1":
  waiting to lock monitor 0x00007fb083015800 (object 0x000000061ff904b0, a java.lang.Object),
  which is held by "Thread-0"

Java stack information for the threads listed above:
===================================================
"Thread-0":
 at DeadLock.foo(DeadLock.java:18)
 - waiting to lock <0x000000061ff904c0> (a java.lang.Object)
 - locked <0x000000061ff904b0> (a java.lang.Object)
 at DeadLock$$Lambda$1/0x0000000800060840.run(Unknown Source)
 at java.lang.Thread.run(java.base@11/Thread.java:834)
"Thread-1":
 at DeadLock.bar(DeadLock.java:33)
 - waiting to lock <0x000000061ff904b0> (a java.lang.Object)
 - locked <0x000000061ff904c0> (a java.lang.Object)
 at DeadLock$$Lambda$2/0x0000000800063040.run(Unknown Source)
 at java.lang.Thread.run(java.base@11/Thread.java:834)

Found 1 deadlock.

? ? ? ?我們可以看到鉴分,jstack不僅會打印線程的棧軌跡、線程狀態(tài)(BLOCKED)带膀、持有的鎖(locked …)以及正在請求的鎖(waiting to lock …)志珍,而且還會分析出具體的死鎖。
3.5JCMD
? ? ? ?還可以直接使用jcmd命令(幫助文檔)垛叨,來替代前面除了jstat之外的所有命令伦糯。具體的替換規(guī)則你可以參考下表。至于jstat的功能嗽元,雖然jcmd復(fù)制了jstat的部分代碼敛纲,并支持通過PerfCounter.print子命令來打印所有的Performance Counter,但是它沒有保留jstat的輸出格式剂癌,也沒有重復(fù)打印的功能淤翔。因此,感興趣的同學(xué)可以自行整理佩谷。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末旁壮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子谐檀,更是在濱河造成了極大的恐慌抡谐,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桐猬,死亡現(xiàn)場離奇詭異麦撵,居然都是意外死亡,警方通過查閱死者的電腦和手機课幕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門厦坛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人乍惊,你說我怎么就攤上這事杜秸。” “怎么了润绎?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵撬碟,是天一觀的道長。 經(jīng)常有香客問我莉撇,道長呢蛤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任棍郎,我火速辦了婚禮其障,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涂佃。我一直安慰自己励翼,他們只是感情好蜈敢,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著汽抚,像睡著了一般抓狭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上造烁,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天否过,我揣著相機與錄音,去河邊找鬼惭蟋。 笑死苗桂,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的敞葛。 我是一名探鬼主播誉察,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼惹谐!你這毒婦竟也來了持偏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤氨肌,失蹤者是張志新(化名)和其女友劉穎鸿秆,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怎囚,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡卿叽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了恳守。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片考婴。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖催烘,靈堂內(nèi)的尸體忽然破棺而出沥阱,到底是詐尸還是另有隱情,我是刑警寧澤伊群,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布考杉,位于F島的核電站,受9級特大地震影響舰始,放射性物質(zhì)發(fā)生泄漏崇棠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一丸卷、第九天 我趴在偏房一處隱蔽的房頂上張望枕稀。 院中可真熱鬧,春花似錦、人聲如沸抽莱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽食铐。三九已至,卻和暖如春僧鲁,著一層夾襖步出監(jiān)牢的瞬間虐呻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工寞秃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留斟叼,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓春寿,卻偏偏與公主長得像朗涩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绑改,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

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