JVM性能調(diào)優(yōu)實(shí)踐——JVM篇

前言

在遇到實(shí)際性能問題時(shí),除了關(guān)注系統(tǒng)性能指標(biāo)。還要結(jié)合應(yīng)用程序的系統(tǒng)的日志、堆棧信息、GClog、threaddump等數(shù)據(jù)進(jìn)行問題分析和定位。關(guān)于性能指標(biāo)分析可以參考前一篇JVM性能調(diào)優(yōu)實(shí)踐——性能指標(biāo)分析。

JVM的調(diào)優(yōu)和故障處理可以使用JDK的幾個(gè)常用命令工具。因?yàn)楸疚氖腔贒ocker容器內(nèi)部的Springboot服務(wù)。需要調(diào)整一下docker容器的啟動參數(shù),才可以使用jmap等工具。jmap命令需要使用Linux的Capability的PTRACE_ATTACH權(quán)限。而Docker自1.10在默認(rèn)的seccomp配置文件中禁用了PTRACE_ATTACH。目前使用的Docker version是17.04.0-ce。支持的Capability列表可以詳看runtime-privilege-and-linux-capabilities。

調(diào)整Capability的方式也比較方便。可以如下直接在運(yùn)行參數(shù)后面加?cap_add,cap-drop

$docker run --cap-add=ALL --cap-drop=MKNOD ...1

也可以在compose中增加:

cap_add: - ALL cap_drop: - NET_ADMIN - SYS_ADMIN12345

Docker容器中的服務(wù)進(jìn)程

在排查問題時(shí),一般是先通過JVM性能調(diào)優(yōu)實(shí)踐——性能指標(biāo)分析中的幾個(gè)命令來分析基礎(chǔ)的服務(wù)器狀態(tài)和信息。在微服務(wù)架構(gòu)中,每臺服務(wù)器部署著若干運(yùn)行著服務(wù)的容器。在不能通過應(yīng)用日志或者問題現(xiàn)象定位問題服務(wù)時(shí),需要找到問題容器。

先通過TOP命令找到耗費(fèi)關(guān)鍵資源的進(jìn)程。

top - 11:45:13 up 318 days, 20:43, 2 users, load average: 0.15, 0.19, 0.18Tasks: 172 total, 1 running, 171 sleeping, 0 stopped, 0 zombie%Cpu(s): 3.1 us, 1.9 sy, 0.0 ni, 94.7 id, 0.0 wa, 0.0 hi, 0.3 si, 0.0 stKiB Mem: 8175392 total, 7868636 used, 306756 free, 204400 buffersKiB Swap: 0 total, 0 used, 0 free. 849564 cached Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 31399 root 20 0 3585612 806804 12228 S 3.0 9.9 548:20.94 java 6331 root 20 0 3445612 925660 15784 S 2.7 11.3 41:40.29 java 31122 root 20 0 3460712 888776 11568 S 2.0 10.9 484:19.31 java 31147 root 20 0 3288180 811476 12748 S 1.3 9.9 263:44.73 java 8506 root 20 0 3254088 750880 6116 S 1.0 9.2 760:45.19 java 22940 root 20 0 1029012 70584 23396 S 0.7 0.9 0:10.68 node 24550 root 20 0 1229088 43096 8712 S 0.7 0.5 160:15.74 node 7 root 20 0 0 0 0 S 0.3 0.0 606:49.74 rcu_sched 454 sshd 20 0 32792 1924 188 S 0.3 0.0 29:15.40 nginx 13721 root 20 0 25396 1956 1324 S 0.3 0.0 56:29.17 AliYunDunUpdate 16225 root 20 0 3072752 429296 6848 S 0.3 5.3 42:51.01 java 20795 root 20 0 2408848 75344 3960 S 0.3 0.9 2361:22 java 23581 root 20 0 16736 2676 2196 R 0.3 0.0 0:00.01 top 31352 root 20 0 206920 1488 1024 S 0.3 0.0 1:20.48 docker-containe 32000 root 20 0 3061760 403708 6548 S 0.3 4.9 127:01.39 java ... 省略其他信息1234567891011121314151617181920212223

因?yàn)镈ocker容器中還有java進(jìn)程,所以需要找到具體的父子進(jìn)程id.用ps -ef命令如下所示。第二列是PID(進(jìn)程ID),第三列是PPID(父進(jìn)程ID)。

$ps -ef |grep java root 6310 6293 0 May21 ? 00:00:00 /bin/sh -c java -Dcontainer.host.ip=...root 6331 6310 2 May21 ? 00:41:51 java -Dcontainer.host.ip= -server ... root 8482 8465 0 Apr16 ? 00:00:00 /bin/sh -c java -Dcontainer.host.ip...root 8506 8482 1 Apr16 ? 12:40:53 java -Dcontainer.host.ip= -server...... 省略其他信息123456

可以使用docker inspect查看容器內(nèi)部信息,找到對應(yīng)的容器實(shí)例的進(jìn)程信息。如下即可打印當(dāng)前宿主機(jī)的所有運(yùn)行的容器實(shí)例的PID,為了方便映射,可以打印對應(yīng)容器名字,或者容器ID:

## 打印容器pid和容器id$docker ps -q | xargs docker inspect --format '{{.State.Pid}}, {{.ID}}' | grep "^${PID}"## 打印容器pid和容器name $ docker ps -q | xargs docker inspect --format '{{.State.Pid}}, {{.Name}}' | grep "^${PID}" 6310, /service-item31369, /gateway-api31094, /service-resource31025, /service-trade30916, /service-user16204, /service-analytics8482, /service-financial... 省略其他信息12345678910111213

如果要分析最消耗內(nèi)存的進(jìn)程,對應(yīng)的pid= 6331,其所在的docker進(jìn)程id也即父進(jìn)程id= 6310,可以定位出service-item服務(wù)最消耗內(nèi)存資源。定位到服務(wù)之后,即可使用docker exec -it service-item ‘/bin/sh’查看容器內(nèi)部信息。

JVM調(diào)優(yōu)基礎(chǔ)命令

在容器內(nèi)部,就可以進(jìn)一步使用jdk提供的jps、jstack、jstat、jmap等工具來進(jìn)行jvm問題排查和調(diào)優(yōu)。

jps[options] [hostid]

jps主要用來輸出JVM中運(yùn)行的進(jìn)程狀態(tài)信息。

-q 輸出類名、Jar名和傳入main方法的參數(shù)

-m 輸出傳入main方法的參數(shù)

-l 輸出main類或Jar的全限名

-v 輸出傳入JVM的參數(shù)

如下查看運(yùn)行的java進(jìn)程信息,打印jar名以及運(yùn)行main方法傳入的參數(shù):

/opt/app # jps -l -m6 /opt/app/app.jar --server.port=8080327 sun.tools.jps.Jps -l -m1234

jstat

jstat - [-t] [-h] [ [] 1

jstat命令可以用于持續(xù)觀察虛擬機(jī)內(nèi)存中各個(gè)分區(qū)的使用率以及GC的統(tǒng)計(jì)數(shù)據(jù)。vmid是Java虛擬機(jī)ID,在Linux/Unix系統(tǒng)取進(jìn)程ID。

如下面輸出的信息,采樣時(shí)間間隔為1000ms,采樣5次:

/opt/app # jstat -gc 6 1000 5 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 1536.0 1536.0 1233.7 0.0 171520.0 169769.2 249344.0 57018.6 93912.0 91906.8 11264.0 10853.7 6224 47.439 5 3.423 50.8631536.0 1536.0 1233.7 0.0 171520.0 169805.4 249344.0 57018.6 93912.0 91906.8 11264.0 10853.7 6224 47.439 5 3.423 50.8631536.0 1536.0 0.0 1536.0 171520.0 3527.9 249344.0 60347.4 96728.0 94808.1 11520.0 11174.7 6225 47.453 5 3.423 50.8761536.0 1536.0 0.0 1536.0 171520.0 4742.1 249344.0 60347.4 96728.0 94808.1 11520.0 11174.7 6225 47.453 5 3.423 50.8761536.0 1536.0 0.0 1536.0 171520.0 7589.3 249344.0 60347.4 96728.0 94808.1 11520.0 11174.7 6225 47.453 5 3.423 50.87612345678

上述各個(gè)列的含義:

S0C、S1C、S0U、S1U:young代的Survivor 0/1區(qū)容量(Capacity)和使用量(Used)。0是FromSurvivor,1是ToSurvivor。

EC、EU:Eden區(qū)容量和使用量

OC、OU:年老代容量和使用量

MC、MU:元數(shù)據(jù)區(qū)(Metaspace)已經(jīng)committed的內(nèi)存空間和使用量

CCSC、CCSU:壓縮Class(Compressed class space)committed的內(nèi)存空間和使用量。

YGC、YGT:young代GC次數(shù)和GC耗時(shí)

FGC、FGCT:Full GC次數(shù)和Full GC耗時(shí)

GCT:GC總耗時(shí)

可以通過分區(qū)占用量上看到,在第2-3秒之間發(fā)生了一次YGC。YGC次數(shù)+1,并且Survivor from區(qū)的內(nèi)存空間從1233.7->0,Survivor from從0->1536。Eden區(qū)也釋放了很多內(nèi)存空間。其他變化的空間占用也有元數(shù)據(jù)區(qū)以及元數(shù)據(jù)區(qū)的壓縮Class區(qū)。Compressed class space也是元數(shù)據(jù)區(qū)的一部分,默認(rèn)是1G,也可以關(guān)閉。具體的jvm8內(nèi)存分布不再詳述。下一篇GC優(yōu)化會再展開整理下。

如果只看gc的總統(tǒng)計(jì)信息,也可以用jstat -gcutil vmid查詢:

/opt/app # jstat -gcutil 6 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 100.00 73.76 24.20 98.02 97.00 6225 47.453 5 3.423 50.876 123

jmap [option] pid

jmap可以用來查看堆內(nèi)存的使用詳情。內(nèi)存各個(gè)分區(qū)可以通過jmap -heap pid來查看。得到的輸出如下:

$jmap -heap 6Attaching to process ID 6, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.121-b13using thread-local object allocation.Parallel GC with 2 thread(s)Heap Configuration: MinHeapFreeRatio = 0 MaxHeapFreeRatio = 100 MaxHeapSize = 536870912 (512.0MB) NewSize = 44564480 (42.5MB) MaxNewSize = 178782208 (170.5MB) OldSize = 89653248 (85.5MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB)Heap Usage:PS Young GenerationEden Space: capacity = 170393600 (162.5MB) used = 99020080 (94.43290710449219MB) free = 71373520 (68.06709289550781MB) 58.11255821814904% usedFrom Space: capacity = 4194304 (4.0MB) used = 786432 (0.75MB) free = 3407872 (3.25MB) 18.75% usedTo Space: capacity = 4194304 (4.0MB) used = 0 (0.0MB) free = 4194304 (4.0MB) 0.0% usedPS Old Generation capacity = 255328256 (243.5MB) used = 65264912 (62.24147033691406MB) free = 190063344 (181.25852966308594MB) 25.561178783126927% used39531 interned Strings occupying 4599760 bytes.1234567891011121314151617181920212223242526272829303132333435363738394041424344454647

Heap Configuration是堆內(nèi)存的配置信息。可以通過運(yùn)行參數(shù)改變。一般通過分析內(nèi)存分布和使用情況以及GC信息,可以針對不同的應(yīng)用不斷調(diào)整到合適的堆內(nèi)存分區(qū)配置。

Heap Usage可以看堆內(nèi)存實(shí)時(shí)的占用情況。

使用jmap -histo[:live] pid查看堆內(nèi)存中的對象的數(shù)目,占用內(nèi)存(單位是byte),如果帶上live則只統(tǒng)計(jì)活對象,如下:

/opt/app/logs # jmap -histo:live 6 | more num #instances #bytes class name---------------------------------------------- 1: 127610 19132008 [C 2: 6460 4074512 [B 3: 37041 3259608 java.lang.reflect.Method 4: 125182 3004368 java.lang.String 5: 86616 2771712 java.util.concurrent.ConcurrentHashMap$Node 6: 70783 2265056 java.util.HashMap$Node 7: 17686 1967496 java.lang.Class 8: 15834 1448440 [Ljava.util.HashMap$Node; 9: 35360 1414400 java.util.LinkedHashMap$Entry 10: 21948 1231624 [Ljava.lang.Object; 11: 9940 1165728 [I 12: 986 1064480 [Ljava.util.concurrent.ConcurrentHashMap$Node; 13: 18685 1046360 java.util.LinkedHashMap 14: 30351 971232 java.lang.ref.WeakReference 15: 50340 805440 java.lang.Object 16: 13490 539600 java.lang.ref.SoftReference 17: 17705 513768 [Ljava.lang.String; 18: 18781 450744 org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource$DefaultCacheKey 19: 20272 434456 [Ljava.lang.Class; 20: 17270 414480 java.beans.MethodRef 21: 23616 377856 java.lang.Integer 22: 11192 358144 java.util.LinkedList 23: 14911 357864 java.util.ArrayList 24: 5700 319200 java.beans.MethodDescriptor12345678910111213141516171819202122232425262728293031

以上示例的排序是按照占用內(nèi)存字節(jié)數(shù)倒序的。class name列中”[C,[B,[I “是代表char,byte,int.”[L+類名”代表其他實(shí)例。這種寫法跟Class文件的Java的類型表述含義是一致的。

在進(jìn)行問題排查時(shí),可以使用jmap把進(jìn)程內(nèi)存使用情況dump到文件中,或者dump**.hprof**文件,在本地使用MAT(Eclipse Memory Analyzer)進(jìn)行分析。也可以直接用jhat分析查看。

/opt/app# jmap -dump:format=b,file=heapdump 6Dumping heap to /opt/app/logs/heapdump ...Heap dump file created /opt/app# jmap -dump:live,format=b,file=heapLive.hprof 6 123456

jstack [option] pid

jstack可以用來查看Java進(jìn)程內(nèi)的線程堆棧信息。

-l long listings,會打印出額外的鎖信息,在發(fā)生死鎖時(shí)可以用jstack -l pid來觀察鎖持有情況

-m mixed mode,不僅會輸出Java堆棧信息,還會輸出C/C++堆棧信息(比如Native方法)

輸出信息如下:

/opt/app # jstack -l 6Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.121-b13 mixed mode):"elasticsearch[Oneg the Prober][listener][T#1]" #221 daemon prio=5 os_prio=0 tid=0x00007fc2a418a800 nid=0x195 waiting on condition [0x00007fc28318d000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000e29f88d0> (a java.util.concurrent.LinkedTransferQueue) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.LinkedTransferQueue.awaitMatch(LinkedTransferQueue.java:737) at java.util.concurrent.LinkedTransferQueue.xfer(LinkedTransferQueue.java:647) at java.util.concurrent.LinkedTransferQueue.take(LinkedTransferQueue.java:1269) at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) Locked ownable synchronizers: - None12345678910111213141516171819

在線上問題排查線程鎖信息時(shí),jstack是個(gè)非常好用的工具,結(jié)合應(yīng)用日志可以迅速定位到問題線程。

Java性能分析工具

對于Java性能調(diào)優(yōu),以前一直比較好用的工具是JRockit,JProfile(商業(yè))等工具,但隨著JDK7 up40版本之后,jdk會自帶JMC(JavaMissionControl)工具。可以分析本地應(yīng)用以及連接遠(yuǎn)程ip使用。提供了實(shí)時(shí)分析線程、內(nèi)存,CPU、GC等信息的可視化界面。從jdk8 up40開始,JMC還提供了在運(yùn)行時(shí)創(chuàng)建JFR記錄(飛行記錄器)。如果是全面分析heap dump,再綜合使用MAT(Eclipse Memory Analyzer)。基本就可以做很多日常的性能調(diào)優(yōu)以及線上問題排查了。下文簡單介紹一些JMC,基于java version “1.8.0_60”。

Java Mission Control

在Mac上使用的話,需要先找到j(luò)dk中的jmc路徑。

$find /Library/Java -name missioncontrol 12

在我本地的目錄是/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/lib/missioncontrol/。打開jmc之后,常用的話可以留在dock下面。

啟動需要觀察的應(yīng)用,然后即可在JMC的MBean服務(wù)器中觀察到綜合信息如下:

進(jìn)一步觀察內(nèi)存以及GC的情況,視圖如下,可以觀察到運(yùn)行時(shí)內(nèi)存各個(gè)分區(qū)的占用率。對于“堆直方圖”默認(rèn)是不開啟的,可以通過右上角的刷新值來啟用,會影響性能。一般用于排查內(nèi)存中的大對象的回收問題以及OOM問題時(shí)可以開啟觀察。

對于線程死鎖、線程池資源方面的分析,可以到線程視圖中觀察活動線程。

Java飛行記錄器(Java Flight Recorder)

Java 8 up40開始,可以使用JMC創(chuàng)建JFR記錄。JFR可以采樣分析收集Java應(yīng)用程序以及JVM的信息, 它的最小開銷小于2%,不會影響其他JVM優(yōu)化。JFR不會記錄所有方法調(diào)用,只會探測熱點(diǎn)方法,但不包含Native方法的線程采樣。如果要開啟JFR,需要應(yīng)用啟動參數(shù)中添加:

-XX:+UnlockCommertialFeatures -XX:+FlightRecorder 1

一般還是建議本地調(diào)優(yōu)和分析時(shí)使用。JFR可以提供固定時(shí)間的采樣(默認(rèn)是1min),以及持續(xù)時(shí)間的記錄。它們都會dump到一個(gè)“.jfr”的文件中。

分析內(nèi)存信息如下,可以看到內(nèi)存使用量,以及基礎(chǔ)的GC配置和統(tǒng)計(jì)信息:

詳細(xì)分析內(nèi)存情況時(shí),需要進(jìn)一步查看“內(nèi)存分配”以及“對象統(tǒng)計(jì)信息”。其中“對象統(tǒng)計(jì)信息”也是默認(rèn)不開啟的,需要在創(chuàng)建jfr時(shí)選擇“啟用”如下:

然后即可看到對象統(tǒng)計(jì)信息:

對于熱點(diǎn)方法以及熱點(diǎn)線程的采樣分析圖表也很直觀,在分析一些循環(huán)調(diào)用時(shí)可以重點(diǎn)關(guān)注熱點(diǎn)方法,對于有問題的熱點(diǎn)方法可以進(jìn)一步查看“堆棧跟蹤”下的調(diào)用鏈:

總結(jié)

本文主要介紹了java常用的性能優(yōu)化和排查問題的工具,以及JavaMissionControl工具的一些功能。JMC是官方提供的免費(fèi)工具,結(jié)合MAT,基本可以處理性能優(yōu)化的80%場景。JMC還可以鏈接遠(yuǎn)程ip進(jìn)行分析。但對于線上問題排查,還是建議使用jstat,jstack,jmap工具等,結(jié)合top、vmstat等快速排查和定位問題。

性能排查一般問題都集中在cpu、內(nèi)存。前者分析線程,后者分析具體出現(xiàn)問題的內(nèi)存分區(qū)。對于磁盤、IO等資源瓶頸需要綜合很多業(yè)務(wù)場景進(jìn)行具體定位。

還有一部分比較大的話題,就是GC,會在下一篇進(jìn)行總結(jié)和整理。如果想學(xué)習(xí)Java工程化、高性能及分布式、深入淺出。微服務(wù)、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java進(jìn)階群:617434785,群里有阿里大牛直播講解技術(shù),以及Java大型互聯(lián)網(wǎng)技術(shù)的視頻免費(fèi)分享給大家。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,665評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,627評論 2 380

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