深入淺出 JIT 編譯器

JIT 簡介

JIT 是 just in time 的縮寫, 也就是即時編譯編譯器畔况。使用即時編譯器技術(shù),能夠加速 Java 程序的執(zhí)行速度。下面植兰,就對該編譯器技術(shù)做個簡單的講解玻蝌。

首先蟹肘,我們大家都知道,通常通過 javac 將程序源代碼編譯俯树,轉(zhuǎn)換成 java 字節(jié)碼帘腹,JVM 通過解釋字節(jié)碼將其翻譯成對應(yīng)的機器指令,逐條讀入许饿,逐條解釋翻譯阳欲。很顯然,經(jīng)過解釋執(zhí)行陋率,其執(zhí)行速度必然會比可執(zhí)行的二進(jìn)制字節(jié)碼程序慢很多球化。為了提高執(zhí)行速度,引入了 JIT 技術(shù)瓦糟。

在運行時 JIT 會把翻譯過的機器碼保存起來筒愚,以備下次使用,因此從理論上來說菩浙,采用該 JIT 技術(shù)可以接近以前純編譯技術(shù)锨能。下面我們看看,JIT 的工作過程芍耘。

JIT 編譯過程

當(dāng) JIT 編譯啟用時(默認(rèn)是啟用的)址遇,JVM 讀入.class 文件解釋后,將其發(fā)給 JIT 編譯器斋竞。JIT 編譯器將字節(jié)碼編譯成本機機器代碼倔约,下圖展示了該過程。

圖 1. JIT 工作原理圖
圖 1. JIT 工作原理圖

Hot Spot 編譯

當(dāng) JVM 執(zhí)行代碼時坝初,它并不立即開始編譯代碼浸剩。這主要有兩個原因:

首先钾军,如果這段代碼本身在將來只會被執(zhí)行一次,那么從本質(zhì)上看绢要,編譯就是在浪費精力吏恭。因為將代碼翻譯成 java 字節(jié)碼相對于編譯這段代碼并執(zhí)行代碼來說,要快很多重罪。

當(dāng)然樱哼,如果一段代碼頻繁的調(diào)用方法,或是一個循環(huán)剿配,也就是這段代碼被多次執(zhí)行搅幅,那么編譯就非常值得了。因此呼胚,編譯器具有的這種權(quán)衡能力會首先執(zhí)行解釋后的代碼茄唐,然后再去分辨哪些方法會被頻繁調(diào)用來保證其本身的編譯。其實說簡單點蝇更,就是 JIT 在起作用沪编,我們知道,對于 Java 代碼年扩,剛開始都是被編譯器編譯成字節(jié)碼文件漾抬,然后字節(jié)碼文件會被交由 JVM 解釋執(zhí)行,所以可以說 Java 本身是一種半編譯半解釋執(zhí)行的語言常遂。Hot Spot VM 采用了 JIT compile 技術(shù)纳令,將運行頻率很高的字節(jié)碼直接編譯為機器指令執(zhí)行以提高性能,所以當(dāng)字節(jié)碼被 JIT 編譯為機器碼的時候克胳,要說它是編譯執(zhí)行的也可以平绩。也就是說,運行時漠另,部分代碼可能由 JIT 翻譯為目標(biāo)機器指令(以 method 為翻譯單位捏雌,還會保存起來,第二次執(zhí)行就不用翻譯了)直接執(zhí)行笆搓。

第二個原因是最優(yōu)化性湿,當(dāng) JVM 執(zhí)行某一方法或遍歷循環(huán)的次數(shù)越多,就會更加了解代碼結(jié)構(gòu)满败,那么 JVM 在編譯代碼的時候就做出相應(yīng)的優(yōu)化肤频。

我們將在后面講解這些優(yōu)化策略,這里算墨,先舉一個簡單的例子:我們知道 equals() 這個方法存在于每一個 Java Object 中(因為是從 Object class 繼承而來)而且經(jīng)常被覆寫宵荒。當(dāng)解釋器遇到 b = obj1.equals(obj2) 這樣一句代碼,它則會查詢 obj1 的類型從而得知到底運行哪一個 equals() 方法。而這個動態(tài)查詢的過程從某種程度上說是很耗時的报咳。

寄存器和主存

其中一個最重要的優(yōu)化策略是編譯器可以決定何時從主存取值侠讯,何時向寄存器存值∈钊校考慮下面這段代碼:

清單 1. 主存 or 寄存器測試代碼
public class RegisterTest {
 private int sum;

 public void calculateSum(int n) {
 for (int i = 0; i < n; ++i) {
 sum += i;
 }
 }
}

在某些時刻厢漩,sum 變量居于主存之中,但是從主存中檢索值是開銷很大的操作岩臣,需要多次循環(huán)才可以完成操作溜嗜。正如上面的例子,如果循環(huán)的每一次都是從主存取值婿脸,性能是非常低的粱胜。相反柄驻,編譯器加載一個寄存器給 sum 并賦予其初始值狐树,利用寄存器里的值來執(zhí)行循環(huán),并將最終的結(jié)果從寄存器返回給主存鸿脓。這樣的優(yōu)化策略則是非常高效的抑钟。但是線程的同步對于這種操作來說是至關(guān)重要的,因為一個線程無法得知另一個線程所使用的寄存器里變量的值野哭,線程同步可以很好的解決這一問題在塔,有關(guān)于線程同步的知識,我們將在后續(xù)文章中進(jìn)行講解拨黔。

寄存器的使用是編譯器的一個非常普遍的優(yōu)化蛔溃。

回到之前的例子,JVM 注意到每次運行代碼時篱蝇,obj1 都是 java.lang.String 這種類型贺待,那么 JVM 生成的被編譯后的代碼則是直接調(diào)用 String.equals() 方法。這樣代碼的執(zhí)行將變得非沉憬兀快麸塞,因為不僅它是被編譯過的,而且它會跳過查找該調(diào)用哪個方法的步驟涧衙。

當(dāng)然過程并不是上面所述這樣簡單哪工,如果下次執(zhí)行代碼時,obj1 不再是 String 類型了弧哎,JVM 將不得不再生成新的字節(jié)碼雁比。盡管如此,之后執(zhí)行的過程中撤嫩,還是會變的更快章贞,因為同樣會跳過查找該調(diào)用哪個方法的步驟。這種優(yōu)化只會在代碼被運行和觀察一段時間之后發(fā)生。這也就是為什么 JIT 編譯器不會理解編譯代碼而是選擇等待然后再去編譯某些代碼片段的第二個原因鸭限。

初級調(diào)優(yōu):客戶模式或服務(wù)器模式

JIT 編譯器在運行程序時有兩種編譯模式可以選擇蜕径,并且其會在運行時決定使用哪一種以達(dá)到最優(yōu)性能。這兩種編譯模式的命名源自于命令行參數(shù)(eg: -client 或者 -server)败京。JVM Server 模式與 client 模式啟動兜喻,最主要的差別在于:-server 模式啟動時,速度較慢赡麦,但是一旦運行起來后朴皆,性能將會有很大的提升。原因是:當(dāng)虛擬機運行在-client 模式的時候泛粹,使用的是一個代號為 C1 的輕量級編譯器遂铡,而-server 模式啟動的虛擬機采用相對重量級代號為 C2 的編譯器。C2 比 C1 編譯器編譯的相對徹底晶姊,服務(wù)起來之后扒接,性能更高。

通過 java -version 命令行可以直接查看當(dāng)前系統(tǒng)使用的是 client 還是 server 模式们衙。例如:

圖 2. 查看編譯模式
圖 2. 查看編譯模式

中級編譯器調(diào)優(yōu)

大多數(shù)情況下钾怔,優(yōu)化編譯器其實只是選擇合適的 JVM 以及為目標(biāo)主機選擇合適的編譯器(-cient,-server 或是-xx:+TieredCompilation)蒙挑。多層編譯經(jīng)常是長時運行應(yīng)用程序的最佳選擇宗侦,短暫應(yīng)用程序則選擇毫秒級性能的 client 編譯器。

優(yōu)化代碼緩存

當(dāng) JVM 編譯代碼時忆蚀,它會將匯編指令集保存在代碼緩存矾利。代碼緩存具有固定的大小,并且一旦它被填滿馋袜,JVM 則不能再編譯更多的代碼男旗。

我們可以很容易地看到如果代碼緩存很小所具有的潛在問題。有些熱點代碼將會被編譯桃焕,而其他的則不會被編譯剑肯,這個應(yīng)用程序?qū)赃\行大量的解釋代碼來結(jié)束。

這是當(dāng)使用 client 編譯器模式或分層編譯時很頻繁的一個問題观堂。當(dāng)使用普通 server 編譯器模式時让网,編譯合格的類的數(shù)量將被填入代碼緩存,通常只有少量的類會被編譯师痕。但是當(dāng)使用 client 編譯器模式時溃睹,編譯合格的類的數(shù)量將會高很多。

在 Java 7 版本胰坟,分層編譯默認(rèn)的代碼緩存大小經(jīng)常是不夠的因篇,需要經(jīng)常提高代碼緩存大小。大型項目若使用 client 編譯器模式,則也需要提高代碼緩存大小竞滓。

現(xiàn)在并沒有一個好的機制可以確定一個特定的應(yīng)用到底需要多大的代碼緩存咐吼。因此,當(dāng)需要提高代碼緩存時商佑,這將是一種湊巧的操作锯茄,一個通常的做法是將代碼緩存變成默認(rèn)大小的兩倍或四倍。

可以通過 –XX:ReservedCodeCacheSize=Nflag(N 就是之前提到的默認(rèn)大胁杳弧)來最大化代碼緩存大小肌幽。代碼緩存的管理類似于 JVM 中的內(nèi)存管理:有一個初始大小(用-XX:InitialCodeCacheSize=N 來聲明)抓半。代碼緩存的大小從初始大小開始喂急,隨著緩存被填滿而逐漸擴(kuò)大。代碼緩存的初始大小是基于芯片架構(gòu)(例如 Intel 系列機器笛求,client 編譯器模式下代碼緩存大小起始于 160KB廊移,server 編譯器模式下代碼緩存大小則起始于 2496KB)以及使用的編譯器的。重定義代碼緩存的大小并不會真正影響性能涣易,所以設(shè)置 ReservedCodeCacheSize 的大小一般是必要的画机。

再者冶伞,如果 JVM 是 32 位的新症,那么運行過程大小不能超過 4GB。這包括了 Java 堆响禽,JVM 自身所有的代碼空間(包括其本身的庫和線程棧)徒爹,應(yīng)用程序分配的任何的本地內(nèi)存,當(dāng)然還有代碼緩存芋类。

所以說代碼緩存并不是無限的隆嗅,很多時候需要為大型應(yīng)用程序來調(diào)優(yōu)(或者甚至是使用分層編譯的中型應(yīng)用程序)。比如 64 位機器侯繁,為代碼緩存設(shè)置一個很大的值并不會對應(yīng)用程序本身造成影響胖喳,應(yīng)用程序并不會內(nèi)存溢出,這些額外的內(nèi)存預(yù)定一般都是被操作系統(tǒng)所接受的贮竟。

編譯閾值

在 JVM 中丽焊,編譯是基于兩個計數(shù)器的:一個是方法被調(diào)用的次數(shù),另一個是方法中循環(huán)被回彈執(zhí)行的次數(shù)咕别〖冀。回彈可以有效的被認(rèn)為是循環(huán)被執(zhí)行完成的次數(shù),不僅因為它是循環(huán)的結(jié)尾惰拱,也可能是因為它執(zhí)行到了一個分支語句雌贱,例如 continue。

當(dāng) JVM 執(zhí)行一個 Java 方法,它會檢查這兩個計數(shù)器的總和以決定這個方法是否有資格被編譯欣孤。如果有馋没,則這個方法將排隊等待編譯。這種編譯形式并沒有一個官方的名字降传,但是一般被叫做標(biāo)準(zhǔn)編譯披泪。

但是如果方法里有一個很長的循環(huán)或者是一個永遠(yuǎn)都不會退出并提供了所有邏輯的程序會怎么樣呢?這種情況下搬瑰,JVM 需要編譯循環(huán)而并不等待方法被調(diào)用款票。所以每執(zhí)行完一次循環(huán),分支計數(shù)器都會自增和自檢泽论。如果分支計數(shù)器計數(shù)超出其自身閾值艾少,那么這個循環(huán)(并不是整個方法)將具有被編譯資格。

這種編譯叫做棧上替換(OSR)翼悴,因為即使循環(huán)被編譯了缚够,這也是不夠的:JVM 必須有能力當(dāng)循環(huán)正在運行時,開始執(zhí)行此循環(huán)已被編譯的版本鹦赎。換句話說谍椅,當(dāng)循環(huán)的代碼被編譯完成,若 JVM 替換了代碼(前棧)古话,那么循環(huán)的下個迭代執(zhí)行最新的被編譯版本則會更加快雏吭。

標(biāo)準(zhǔn)編譯是被-XX:CompileThreshold=Nflag 的值所觸發(fā)。Client 編譯器模式下陪踩,N 默認(rèn)的值 1500杖们,而 Server 編譯器模式下,N 默認(rèn)的值則是 10000肩狂。改變 CompileThreshold 標(biāo)志的值將會使編譯器相對正常情況下提前(或推遲)編譯代碼摘完。在性能領(lǐng)域,改變 CompileThreshold 標(biāo)志是很被推薦且流行的方法傻谁。事實上孝治,您可能知道 Java 基準(zhǔn)經(jīng)常使用此標(biāo)志(比如:對于很多 server 編譯器來說,經(jīng)常在經(jīng)過 8000 次迭代后改變次標(biāo)志)审磁。

我們已經(jīng)知道 client 編譯器和 server 編譯器在最終的性能上有很大的差別谈飒,很大程度上是因為編譯器在編譯一個特定的方法時,對于兩種編譯器可用的信息并不一樣力图。降低編譯閾值步绸,尤其是對于 server 編譯器,承擔(dān)著不能使應(yīng)用程序運行達(dá)到最佳性能的風(fēng)險吃媒,但是經(jīng)過測試應(yīng)用程序我們也發(fā)現(xiàn)瓤介,將閾值從 8000 變成 10000吕喘,其實有著非常小的區(qū)別和影響。

檢查編譯過程

中級優(yōu)化的最后一點其實并不是優(yōu)化本身刑桑,而是它們并不能提高應(yīng)用程序的性能氯质。它們是 JVM(以及其他工具)的各個標(biāo)志,并可以給出編譯工作的可見性祠斧。它們中最重要的就是–XX:+PrintCompilation(默認(rèn)狀態(tài)下是 false)闻察。

如果 PrintCompilation 被啟用,每次一個方法(或循環(huán))被編譯琢锋,JVM 都會打印出剛剛編譯過的相關(guān)信息辕漂。不同的 Java 版本輸出形式不一樣,我們這里所說的是基于 Java 7 版本的吴超。

編譯日志中大部分的行信息都是下面的形式:

清單 2. 日志形式
timestamp compilation_id attributes (tiered_level) method_name size depot

顯示更多

這里 timestamp 是編譯完成時的時間戳钉嘹,compilation_id 是一個內(nèi)部的任務(wù) ID,且通常情況下這個數(shù)字是單調(diào)遞增的鲸阻,但有時候?qū)τ?server 編譯器(或任何增加編譯閾值的時候)跋涣,您可能會看到失序的編譯 ID。這表明編譯線程之間有些快有些慢鸟悴,但請不要隨意推斷認(rèn)為是某個編譯器任務(wù)莫名其妙的非常慢陈辱。

用 jstat 命令檢查編譯

要想看到編譯日志,則需要程序以-XX:+PrintCompilation flag 啟動细诸。如果程序啟動時沒有 flag沛贪,您可以通過 jstat 命令得到有限的可見性信息。

Jstat 有兩個選項可以提供編譯器信息揍堰。其中鹏浅,-compile 選項提供總共有多少方法被編譯的總結(jié)信息(下面 6006 是要被檢查的程序的進(jìn)程 ID):

清單 3 進(jìn)程詳情
% jstat -compiler 6006
CompiledFailedInvalid TimeFailedTypeFailedMethod
206 0 0 1.97 0

顯示更多

注意嗅义,這里也列出了編譯失敗的方法的個數(shù)信息屏歹,以及編譯失敗的最后一個方法的名稱。

另一種選擇之碗,您可以使用-printcompilation 選項得到最后一個被編譯的方法的編譯信息蝙眶。因為 jstat 命令有一個參數(shù)選項用來重復(fù)其操作,您可以觀察每一次方法被編譯的情況褪那。舉個例子:

Jstat 對 6006 號 ID 進(jìn)程每 1000 毫秒執(zhí)行一次: %jstat –printcompilation 6006 1000幽纷,具體的輸出信息在此不再描述。

高級編譯器調(diào)優(yōu)

這一節(jié)我們將介紹編譯工作剩下的細(xì)節(jié)博敬,并且過程中我們會探討一些額外的調(diào)優(yōu)策略友浸。調(diào)優(yōu)的存在很大程度上幫助了 JVM 工程師診斷 JVM 自身的行為性芬。如果您對編譯器的工作原理很感興趣破加,這一節(jié)您一定會喜歡。

編譯線程

從前文中我們知道绳泉,當(dāng)一個方法(或循環(huán))擁有編譯資格時,它就會排隊并等待編譯伦意。這個隊列是由一個或很多個后臺線程組成火窒。這也就是說編譯是一個異步的過程。它允許程序在代碼正在編譯時被繼續(xù)執(zhí)行驮肉。如果一個方法被標(biāo)準(zhǔn)編譯方式所編譯熏矿,那么下一個方法調(diào)用則會執(zhí)行已編譯的方法。如果一個循環(huán)被棧上替換方式所編譯离钝,那么下一次循環(huán)迭代則會執(zhí)行新編譯的代碼票编。

這些隊列并不會嚴(yán)格的遵守先進(jìn)先出原則:哪一個方法的調(diào)用計數(shù)器計數(shù)更高,哪一個就擁有優(yōu)先權(quán)卵渴。所以即使當(dāng)一個程序開始執(zhí)行栏妖,并且有大量的代碼需要編譯,這個優(yōu)先權(quán)順序?qū)椭⒈WC最重要的代碼被優(yōu)先編譯(這也是為什么編譯 ID 在 PrintComilation 的輸出結(jié)果中有時會失序的另一個原因)奖恰。

當(dāng)使用 client 編譯器時吊趾,JVM 啟動一個編譯線程,而 server 編譯器有兩個這樣的線程瑟啃。當(dāng)分層編譯生效時论泛,JVM 會基于某些復(fù)雜方程式默認(rèn)啟動多個 client 和 server 線程,涉及雙日志在目標(biāo)平臺上的 CPU 數(shù)量蛹屿。如下圖所示:

分層編譯下 C1 和 C2 編譯器線程默認(rèn)數(shù)量:

圖 3. C1 和 C2 編譯器默認(rèn)數(shù)量
圖 3. C1 C2 編譯器默認(rèn)數(shù)量

編譯器線程的數(shù)量可以通過-XX:CICompilerCount=N flag 進(jìn)行調(diào)節(jié)設(shè)置屁奏。這個數(shù)量是 JVM 將要執(zhí)行隊列所用的線程總數(shù)。對于分層編譯错负,三分之一的(至少一個)線程被用于執(zhí)行 client 編譯器隊列坟瓢,剩下的(也是至少一個)被用來執(zhí)行 server 編譯器隊列。

在何時我們應(yīng)該考慮調(diào)整這個值呢犹撒?如果一個程序被運行在單 CPU 機器上折联,那么只有一個編譯線程會更好一些:因為對于某個線程來說,其對 CPU 的使用是有限的识颊,并且在很多情況下越少的線程競爭資源會使其運行性能更高诚镰。然而,這個優(yōu)勢僅僅局限于初始預(yù)熱階段祥款,之后清笨,這些具有編譯資格的方法并不會真的引起 CPU 爭用。當(dāng)一個股票批處理應(yīng)用程序運行在單 CPU 機器上并且編譯器線程被限制成只有一個刃跛,那么最初的計算過程將比一般情況下快 10%(因為它沒有被其他線程進(jìn)行 CPU 爭用)抠艾。迭代運行的次數(shù)越多,最初的性能收益就相對越少桨昙,直到所有的熱點方法被編譯完性能收益也隨之終止检号。

結(jié)束語

本文詳細(xì)介紹了 JIT 編譯器的工作原理首懈。從優(yōu)化的角度講,最簡單的選擇就是使用 server 編譯器的分層編譯技術(shù)谨敛,這將解決大約 90%左右的與編譯器直接相關(guān)的性能問題究履。最后,請保證代碼緩存的大小設(shè)置的足夠大脸狸,這樣編譯器將會提供最高的編譯性能最仑。
原文:https://developer.ibm.com/zh/articles/j-lo-just-in-time/
https://zhuanlan.zhihu.com/p/82118137

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市炊甲,隨后出現(xiàn)的幾起案子泥彤,更是在濱河造成了極大的恐慌,老刑警劉巖卿啡,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吟吝,死亡現(xiàn)場離奇詭異,居然都是意外死亡颈娜,警方通過查閱死者的電腦和手機剑逃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來官辽,“玉大人蛹磺,你說我怎么就攤上這事⊥停” “怎么了萤捆?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長俗批。 經(jīng)常有香客問我俗或,道長,這世上最難降的妖魔是什么岁忘? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任辛慰,我火速辦了婚禮,結(jié)果婚禮上臭觉,老公的妹妹穿的比我還像新娘昆雀。我一直安慰自己,他們只是感情好蝠筑,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著揩懒,像睡著了一般什乙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上已球,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天臣镣,我揣著相機與錄音辅愿,去河邊找鬼。 笑死忆某,一個胖子當(dāng)著我的面吹牛点待,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弃舒,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼癞埠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了聋呢?” 一聲冷哼從身側(cè)響起苗踪,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎削锰,沒想到半個月后通铲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡器贩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年颅夺,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛹稍。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡碗啄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出稳摄,到底是詐尸還是另有隱情稚字,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布厦酬,位于F島的核電站胆描,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏仗阅。R本人自食惡果不足惜昌讲,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望减噪。 院中可真熱鬧短绸,春花似錦、人聲如沸筹裕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朝卒。三九已至证逻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抗斤,已是汗流浹背囚企。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工丈咐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人龙宏。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓棵逊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親银酗。 傳聞我的和親對象是個殘疾皇子辆影,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355