JVM后端編譯優(yōu)化 - 筆記

0. 概述

本文對后端編譯器:即時編譯器(JIT編譯器)和提前編譯器(AOT編譯器)進行分析整理饼问。

兩者都不是JVM必需的組成部分芯肤。但是树肃,后端編譯器編譯性能的好壞甸赃、代碼優(yōu)化質(zhì)量的高低,去我是衡量商用JVM優(yōu)秀與否的關(guān)鍵指標之一趟据,也是其核心所在券犁,最能提心技術(shù)水平與價值的功能。

1. 即時編譯器

目前主流的兩款商用 JVM(HotSpot汹碱、OpenJ9)中粘衬,Java 程序最初都是通過「解釋器(Interpreter)」解釋執(zhí)行的,當 JVM 發(fā)現(xiàn)某個方法或代碼塊的執(zhí)行特別頻繁咳促,就會認為它們是“熱點代碼(Hot Spot Code)”稚新。

為了提高熱點代碼的執(zhí)行效率,JVM 會在「運行時」把這部分代碼編譯成本地機器碼跪腹,并用各種手段去優(yōu)化代碼褂删。運行時完成這個任務(wù)的后端編譯器被稱為「即時編譯器」。

HotSpot VM 內(nèi)置了3個即時編譯器冲茸,分別為:

  • 客戶端編譯器(Client Compiler)屯阀,簡稱C1編譯器。
  • 服務(wù)端編譯器(Server Compiler)轴术,簡稱C2編譯器难衰,或Opto編譯器。
  • Graal 編譯器(JDK 10 出現(xiàn)逗栽,長期目標是替代 C2 編譯器)盖袭。

1.1 解釋器與編譯器

1.1.1 執(zhí)行流程

編譯器的執(zhí)行流程大致如下:

輸入的代碼 -> [ 解釋器 解釋執(zhí)行 ] -> 執(zhí)行結(jié)果

即時編譯器的執(zhí)行流程大致如下:

輸入的代碼 -> [ 編譯器 編譯 ] -> 編譯后的代碼 -> [ 執(zhí)行 ] -> 執(zhí)行結(jié)果

1.1.2 對比分析

目前主流的商用 JVM 內(nèi)部都同時包含解釋器與編譯器,二者各有優(yōu)勢:

  • 程序需要迅速啟動和執(zhí)行時彼宠,解釋器可以省去編譯時間鳄虱,立即執(zhí)行。
  • 程序啟動后凭峡,編譯器逐漸發(fā)揮作用醇蝴,把越來越多的代碼編譯成本地代碼,可以減少解釋器的中間消耗想罕,提高執(zhí)行效率悠栓。
  • 若運行環(huán)境的內(nèi)存資源限制較大霉涨,可使用解釋器執(zhí)行節(jié)約內(nèi)存;反之可使用編譯執(zhí)行來提升效率惭适。

總結(jié)起來就是:

  1. 解釋器啟動較快笙瑟,占用內(nèi)存較小,但是執(zhí)行效率稍低癞志。
  2. 編譯器啟動較慢往枷,占用內(nèi)存較大,但執(zhí)行效率較高凄杯。

此外错洁,解釋器還可以作為編譯器激進優(yōu)化時后備的“逃生門”,也就是給編譯器來“兜底”戒突,反之則不行屯碴。

凡事有利弊。這里仍以查詢接口為例做類比:

  • 解釋執(zhí)行可以理解為直接查詢數(shù)據(jù)庫膊存,也就是不使用緩存导而。程序啟動起來比較快(無需連接緩存服務(wù)器),但后面運行的時候由于每次都要去查數(shù)據(jù)庫隔崎,會有磁盤 IO 開銷今艺,會相對慢一些。
  • 而編譯執(zhí)行就相當于使用了緩存爵卒。雖然啟動會稍慢一些(需要連接緩存服務(wù)器虚缎,初次查詢時既要查詢數(shù)據(jù)庫,又要存入緩存)钓株,而且需要額外的開銷(需要緩存服務(wù)器)遥巴,但是后續(xù)的查詢效率會提高很多,因為可以直接從緩存獲取享幽,不必再查詢數(shù)據(jù)庫。

因此拾弃,使用緩存其實就是“空間換時間”值桩,編譯器與解釋器也可以類比來理解犯建。

1.1.3 運行模式

解釋器與編譯器配合使用的方式在虛擬機中被稱為“混合模式(Mixed Mode)”糠爬,比如我們查看 JDK 版本時:

$ java -version
java version "11.0.3" 2019-04-14 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.3+12-LTS)
Java HotSpot(TM) 64-Bit Server VM  18.9 (build 11.0.3+12-LTS,  mixed mode)

最后面的 mixed mode 就表示混合模式吞琐。

1.2 分層編譯

JIT 編譯器的編譯過程是在「運行期」脖含,這就不可避免會占用應(yīng)用程序的資源宾舅。而且懊昨,想要把代碼優(yōu)化得更好茅撞,就要花費更多的時間秉颗。而且可能還需要解釋器幫忙收集一些性能監(jiān)控信息鸯隅,又降低了解釋器的效率澜建。這可怎么辦向挖?

那找個折衷的方案?其實就是分層編譯(Tiered Compilation)炕舵。

分了哪幾個層次呢何之?主要包括:

0 程序純解釋執(zhí)行,且解釋器不開啟性能監(jiān)控功能咽筋。
1 使用 C1 編譯器將字節(jié)碼編譯為本地代碼來執(zhí)行溶推,進行簡單可靠的穩(wěn)定優(yōu)化,不開啟性能監(jiān)控功能奸攻。
2 使用 C1 編譯器執(zhí)行蒜危,僅開啟一部分性能監(jiān)控功能(方法及回邊次數(shù)統(tǒng)計等)。
3 使用 C1 編譯器執(zhí)行睹耐,開啟全部性能監(jiān)控(在第二層之外辐赞,還會收集如分支跳轉(zhuǎn)、虛方法調(diào)用版本等全部的統(tǒng)計信息)疏橄。
4 使用 C2 編譯器將字節(jié)碼編譯為本地代碼(相比 C1 編譯器占拍,C2 編譯器會啟用更多編譯耗時更長的優(yōu)化,還會根據(jù)性能監(jiān)控信息進行一些不可靠的激進優(yōu)化)捎迫。

這幾個層次并非固定不變晃酒,可以根據(jù)不同的運行參數(shù)靈活使用。

1.3 熱點代碼

運行時會被即時編譯器編譯的目標是“熱點代碼”窄绒,主要包括下面兩類:

  • 被多次調(diào)用的方法贝次。

  • 被多次執(zhí)行的循環(huán)體。

前者比較容易理解:一個方法被調(diào)用的次數(shù)多了彰导,自然就成了熱點代碼蛔翅。

后者是什么場景呢?當一個方法被調(diào)用的次數(shù)雖然不多位谋,但方法體內(nèi)部存在循環(huán)次數(shù)較多的循環(huán)體山析。這種代碼也是“熱點代碼”(可以理解為方法的一部分是熱點代碼)。比如:

public void test(){
    //一些其他代碼...

    //即便test()方法被調(diào)用的次數(shù)不多掏父,但是當N足夠大時笋轨,該部分代碼都會成為“熱點代碼”
    for(int i=0;i<N;i++){
        //執(zhí)行一些操作
    }

    //一些其他代碼
}

前者是JVM標準的及時編譯。

至于后者赊淑,雖然熱點代碼只是方法的一部分爵政,但編譯器仍會把「整個方法」作為編譯對象,只是入口不同(并非從方法的第一行代碼開始)陶缺。由于該情況發(fā)生在方法執(zhí)行的過程中钾挟,也被稱為棧上替換(On Stack Replacement,OSR)饱岸。也就是方法的棧幀還在棧上掺出,但方法已經(jīng)被替換了徽千。

PS: 每個方法被執(zhí)行時,虛擬機棧都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表蛛砰、操作數(shù)棧等信息罐栈。每個方法從被調(diào)用直至執(zhí)行完畢的過程,就對應(yīng)著一個棧幀在虛擬機棧中從入棧到出棧的過程泥畅。

1.4 熱點探測

關(guān)于熱點代碼的判定荠诬,前面一直提的都是“多次”,到底多少次才叫“多”呢位仁?這個問題不僅要“定性”柑贞,還要“定量”。

要判定一段代碼是不是熱點代碼聂抢、是否觸發(fā)即時編譯的行為稱為“熱點探測(Hot Spot Code Detection)”钧嘶。

1.4.1 定量方法

熱點探測的主流方法有以下兩種:

  • 基于采樣的熱點探測(Sample Based Hot Spot Code Detection)

就是每隔一段時間去檢查一下所有線程的調(diào)用棧頂,若發(fā)現(xiàn)某個(或某些)方法經(jīng)常出現(xiàn)在棧頂琳疏,該方法就會被認為是“熱點代碼”有决。J9 虛擬機使用過該方法。

這種方法的優(yōu)缺點如下:

  1. 優(yōu)點:實現(xiàn)簡單高效空盼,而且可以通過堆棧信息獲取到方法之間的調(diào)用關(guān)系书幕;
  2. 缺點:難以精確的確定方法熱度,容易受到線程阻塞的干擾(即方法阻塞時可能長時間處于棧頂揽趾,可能產(chǎn)生誤判)台汇。
  • 基于計數(shù)器的熱點探測(Counter Based Hot Spot Code Detection)

為每個方法(或代碼塊)建立計數(shù)器來統(tǒng)計方法的執(zhí)行次數(shù),當次數(shù)超過一定的閾值就認為是“熱點代碼”篱瞎。HotSpot 虛擬機就是使用該方法進行探測的苟呐。

該方法的同樣也有優(yōu)缺點:

1.優(yōu)點:統(tǒng)計結(jié)果更加精確嚴謹;
2.缺點:統(tǒng)計起來稍麻煩(要為每個方法建立并維護計數(shù)器)俐筋,而且不能直接獲取到方法的調(diào)用關(guān)系牵素。

1.4.2 兩種計數(shù)器

1.4.2.1 方法調(diào)用計數(shù)器

方法調(diào)用計數(shù)器(Invocation Counter)用來統(tǒng)計方法被調(diào)用的次數(shù)。它在客戶端和服務(wù)端模式下的默認閾值分別為 1500 次和 10000 次澄者。

該計數(shù)器觸發(fā)即時編譯的流程圖如下:


image.png

PS: 方法調(diào)用計數(shù)器統(tǒng)計的并非方法被調(diào)用的絕對次數(shù)笆呆,而是是一個相對的執(zhí)行頻率。

什么意思呢闷哆?

也就是在一段時間內(nèi),如果方法的調(diào)用次數(shù)未到達閾值单起,計數(shù)器就會減少為原先的一半抱怔。該過程被稱為熱度衰減(Counter Decay),這段時間則被稱為半衰周期(Counter Half Life Time)嘀倒。

比如屈留,若閾值是 10000局冰,半衰周期是 1 小時。如果在 1 小時內(nèi)灌危,某個方法被調(diào)用了 8000 次(未達到即時編譯的條件)康二,計數(shù)器就會認為該方法沒那么“熱”,就要給它“潑冷水”勇蝙,把次數(shù)降為 4000 (純屬個人理解)沫勿。

當然,有 JVM 參數(shù)可以對此進行調(diào)整味混,如下:

# 指定計數(shù)器的閾值
-XX:CompileThreshold

# 關(guān)閉熱度衰減
-XX:-UseCounterDecay

# 設(shè)置半衰期時間(秒)
-XX:CounterHalfLifeTime
1.4.2.2 回邊計數(shù)器

回邊計數(shù)器(Back Edge Counter)用來統(tǒng)計方法中循環(huán)體代碼執(zhí)行的次數(shù)(字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令稱為“回邊”)产雹,目的是為了觸發(fā)棧上替換。

回邊計數(shù)器觸發(fā)即時編譯的流程如下:


image.png

與此相關(guān)的幾個 JVM 參數(shù):

# OSR 比率翁锡,默認 933
-XX:OnStackReplacePercentage

# 解釋器監(jiān)控比率蔓挖,默認 33
-XX:InterpreterProfilePercentage

2. 提前編譯器

對提前編譯的研究主要有兩個分支。

2.1 靜態(tài)翻譯

一條就是在程序運行之前馆衔,把程序代碼“翻譯”成機器碼瘟判。

JIT 編譯器的主要缺點在于:它是在「運行期」進行編譯的。這就不可避免地要占用應(yīng)用程序的運行資源(CPU角溃、內(nèi)存等)拷获,進而影響程序的執(zhí)行性能。

而這種提前編譯就是把這個編譯階段放到程序的「運行期」之前开镣,這樣就可以不占用應(yīng)用程序的資源刀诬。

2.2 即時編譯緩存

其實就是把 JIT 編譯器要做的編譯工作先做好,并保存下來邪财,當觸發(fā) JIT 編譯時陕壹,直接調(diào)用這里的代碼就好了。本質(zhì)上就是給 JIT 編譯做緩存树埠。

這種方式也被稱為動態(tài)提前編譯(Dynamic AOT)或者即時編譯緩存(JIT Caching)糠馆。

2.3 即時編譯器與提前編譯器

從上面對提前編譯器的分析來看,似乎提前編譯比 JIT 編譯運行效率更高怎憋。那它就沒缺點了嗎又碌?當然不是,否則還要 JIT 編譯器干嘛绊袋。

相比提前編譯器毕匀,JIT 編譯器的優(yōu)勢在哪里呢?

  • 性能分析制導(dǎo)優(yōu)化

解釋器或客戶端編譯器在運行的過程中癌别,會不斷收集性能監(jiān)控信息(方法版本選擇皂岔、條件判斷等),這些信息可以幫助 JIT 編譯器對代碼進行集中優(yōu)化展姐。

這一點在靜態(tài)分析時是很難做到的躁垛。

  • 激進預(yù)測性優(yōu)化

也就是 JIT 編譯器可以進行一些稍微“激進”的優(yōu)化行為剖毯,即便這些行為失敗了,也有解釋器可以“兜底”教馆。而靜態(tài)優(yōu)化就做不到了逊谋。

此外,提前編譯還會破壞 Java 平臺中立性土铺、產(chǎn)生字節(jié)膨脹等問題胶滋。

3. 編譯優(yōu)化技術(shù)

前面分析了 JIT 編譯器和提前編譯器,它們做的都是“翻譯”工作舒憾。但關(guān)鍵問題不在于“能不能”翻譯镀钓,而是翻譯的“好不好”。也就是編譯出來的代碼質(zhì)量高不高镀迂。

那么丁溅,它們用什么手段來提升“翻譯”的質(zhì)量呢?

HotSpot VM 的 JIT 編譯器使用了不少優(yōu)化技術(shù)(可參考:https://wiki.openjdk.java.net/display/HotSpot/PerformanceTacticIndex)探遵,下面介紹幾個非常重要的窟赏。

PS: JIT 編譯器對代碼的優(yōu)化,這里的“代碼”并非我們編寫的源代碼箱季,而是被編譯后的字節(jié)碼或者機器碼涯穷。畢竟已經(jīng)通過類加載器把 Class 文件加載到 JVM 了。

3.1 方法內(nèi)聯(lián)

方法內(nèi)聯(lián)是編譯器最重要的優(yōu)化手段藏雏,業(yè)內(nèi)戲稱為“優(yōu)化之母”拷况。是其他優(yōu)化手段的基礎(chǔ)。

它的行為理解起來其實很簡單:就是在方法調(diào)用中掘殴,把目標方法的代碼“復(fù)制”到調(diào)用的方法之中赚瘦,避免發(fā)生真實的方法調(diào)用。示例代碼如下:

public static void foo() {
  if (obj != null) {
    System.out.println("hello");
  }
}

public static void testInline() {
  Object obj = null;
  foo(obj);
}

該段代碼實際是無用代碼(Dead Code)奏寨,經(jīng)過方法內(nèi)聯(lián)(把 foo 方法的代碼代入到 testInline 方法中)之后可以發(fā)現(xiàn)起意。

但若不做內(nèi)聯(lián),后續(xù)即便進行了無用代碼消除的優(yōu)化病瞳,也無法發(fā)現(xiàn)該無用代碼揽咕。

3.2 逃逸分析

逃逸分析(Escape Analysis)是目前 JVM 中比較前沿的優(yōu)化技術(shù)。但它并不直接優(yōu)化代碼套菜,而是一種為其他優(yōu)化措施提供依據(jù)的分析技術(shù)亲善。

它的基本原理是分析對象的動態(tài)作用域,當一個對象在方法中被定義后逗柴,按照逃逸程度從低到高可分為:

  • 不逃逸:對象只能在本方法內(nèi)使用蛹头。
  • 方法逃逸:對象可能被外部方法引用(例如作為調(diào)用參數(shù)傳遞到其他方法)。
  • 線程逃逸:對象可能被外部線程訪問到(例如賦值給線程共享的變量)。

若一個對象未發(fā)生逃逸掘而,或者逃逸程度較低,可以為這個對象采取不同程度的優(yōu)化于购。

3.2.1 棧上分配

JVM 中袍睡,對象的內(nèi)存空間分配在堆上似乎是一個常識。當對象不再使用時肋僧,垃圾收集器會將其內(nèi)存空間回收斑胜,這個過程其實是要消耗大量資源的。

假如……把對象的內(nèi)存空間分配到棧上呢嫌吠?

What 止潘??辫诅?這簡直是顛覆認知凭戴!

但是,不妨沿著這個思路考慮一下:如果這樣做了有什么好處呢炕矮?

這樣一來對象占用的內(nèi)存空間就會隨著棧幀出棧而銷毀么夫,不必再由垃圾收集器費時費力地去回收了,可以節(jié)省不少資源肤视。這樣一想似乎也是不是不可以档痪。

這就是所謂的棧上分配(Stack Allocations),它可以支持「方法逃逸」邢滑,但不支持線程逃逸腐螟。

PS:由于復(fù)雜度等原因,HotSpot 目前暫未做這項優(yōu)化困后,但有些 JVM(例如 Excelsior JET)已經(jīng)在使用了乐纸。

3.2.2 標量替換

先看一下標量(Scalar)和聚合量(Aggregate)的概念:

  • 標量:無法再分解為更小數(shù)據(jù)的數(shù)據(jù),例如 JVM 中的原始數(shù)據(jù)類型(int操灿、long锯仪、reference 等)。
  • 聚合量:可以繼續(xù)分解的數(shù)據(jù)趾盐,例如 Java 中的對象庶喜。

所謂「標量替換(Scalar Replacement)」,就是根據(jù)實際訪問情況救鲤,將一個對象“拆解”開久窟,把用到的成員變量恢復(fù)為原始類型來訪問。

簡單來說本缠,就是把聚合量替換為標量斥扛。

若一個對象不會逃逸出「方法」,且可以被拆散,那么程序真正執(zhí)行時就可能不去創(chuàng)建這個對象稀颁,而是直接創(chuàng)建它的若干個被該方法使用的成員變量代替芬失。

還有這操作?

其實細想一下匾灶,這個操作跟前面的「棧上分配」還是有些類似的:棧上分配的是對象棱烂,而標量替換則是在棧上分配對象的一部分成員變量,連對象都懶得創(chuàng)建了阶女。

3.2.3 同步消除

線程同步本身相對耗時颊糜,如果逃逸分析能夠確定一個變量不會逃逸出線程,則該變量的讀寫就不會有線程安全問題秃踩,對該變量的同步措施就可以安全的消除了衬鱼。

換句話說,如果對線程安全的數(shù)據(jù)加了鎖憔杨,JVM 就可以把它優(yōu)化消除鸟赫。示例代碼如下:

public void t1() {
    // 變量 o 不會逃逸出線程。因此消别,對它加的鎖就可以被消除
    Object o = new Object();
    synchronized (o) {
        System.out.println(o.toString());
    }
}

3.3 公共子表達式消除

如果一個表達式惯疙,在兩次計算過程中,其內(nèi)所有變量的值并沒有發(fā)生變化妖啥,那么則將其稱為公共子表達式霉颠。

舉個栗子:

int a = (b*c)*4+(c*b+d)+d

上面這段代碼在計算 b*c 的兩次中并沒有變化,因此可以將其簡寫為int a = E * 4 + (E + d) + d荆虱,再進一步還可以進行 代數(shù)化簡 優(yōu)化,將其優(yōu)化為:

int a = E * 5 + 2 * d;

3.4 數(shù)組范圍檢查消除

假如有一個數(shù)組 array蒿偎,當我們訪問數(shù)組下標在 [0, array.length) 范圍之外的元素時,就會拋出 java.lang.ArrayIndexOutOfBoundsException 異常怀读,也就是數(shù)組越界了诉位,例如:

public void test1() {
  String[] array = new String[]{"a", "b", "c"};
  // 數(shù)組越界
  String s = array[3];
}

其實是 JVM 在執(zhí)行的時候隱含了一次邊界判斷(運行期)。當這樣的判斷很多時菜枷,肯定對性能有一定的影響苍糠。

但這個判斷看起來似乎又是必要的,就不能優(yōu)化了嗎啤誊?

實際上也并非不能岳瞭,如果把這些判斷放在編譯期呢?代碼在編譯的時候蚊锹,就根據(jù)控制流分析(可參考前文的前端編譯)是否會產(chǎn)生數(shù)組越界瞳筏,那么在運行期間不是就不用判斷了嗎?

參考:

https://mp.weixin.qq.com/s?__biz=MzU4NzYyMDE4MQ==&mid=2247484211&idx=1&sn=bf8c2196f147430001daa6b1e540ffdc

https://www.zhihu.com/question/37389356/answer/73820511

http://www.reibang.com/p/eede776beeb7

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末牡昆,一起剝皮案震驚了整個濱河市姚炕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖柱宦,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件些椒,死亡現(xiàn)場離奇詭異,居然都是意外死亡掸刊,警方通過查閱死者的電腦和手機摊沉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痒给,“玉大人,你說我怎么就攤上這事骏全〔园兀” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵姜贡,是天一觀的道長试吁。 經(jīng)常有香客問我,道長楼咳,這世上最難降的妖魔是什么熄捍? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮母怜,結(jié)果婚禮上余耽,老公的妹妹穿的比我還像新娘。我一直安慰自己苹熏,他們只是感情好碟贾,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著轨域,像睡著了一般袱耽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上干发,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天朱巨,我揣著相機與錄音,去河邊找鬼枉长。 笑死冀续,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的必峰。 我是一名探鬼主播沥阳,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼自点!你這毒婦竟也來了桐罕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎功炮,沒想到半個月后溅潜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡薪伏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年滚澜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫁怀。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡设捐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出塘淑,到底是詐尸還是另有隱情萝招,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布存捺,位于F島的核電站槐沼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捌治。R本人自食惡果不足惜岗钩,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肖油。 院中可真熱鬧兼吓,春花似錦、人聲如沸森枪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疲恢。三九已至凶朗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間显拳,已是汗流浹背棚愤。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留杂数,地道東北人宛畦。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像揍移,于是被迫代替她去往敵國和親次和。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345