JVM 日志讀取與解析

JVM 隨著 Oracle 的快速發(fā)布策略红选,會經(jīng)常發(fā)生變化腰池。所以在一些版本行之有效的參數(shù)名眉,可能在另一些新的版本里并不起作用弦讽∥畚荆可以通過這個命令查看當前 JVM 默認的參數(shù)配置

java -XX:+PrintFlagsFinal

但是 JVM 的參數(shù)配置項很多,長長的列表上坦袍,很多配置項不需要我們修改十厢。所以可以過濾并只關(guān)注我們需要的參數(shù)等太。

還有一個命令可用

java -XX:+PrintCommandLineFlags -version

gaopengdeMacBook-Pro-2:script gaopeng$ java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)

可以看到 JVM 采用了并行收集器捂齐,堆內(nèi)存的初始化/最大值配置等。

一 gc 日志

線上機器的 GC 日志缩抡,需要在運行時加入相關(guān)的 JVM 參數(shù)奠宜,然后得到一個時間周期內(nèi)的 GC 日志。GC 日志直接的可讀性相對較低瞻想,需要了解日志各項內(nèi)容的含義压真。

在本地測試的時候,我用了如下的 demo 代碼:

public class PrintGCDetailsDemo {

    public static void main(String[] args) {
        int _1m = 1024*1024;
        byte[] data = new byte[_1m];

        data = null;

        System.gc();
    }
}

運行這段代碼之前蘑险,在對應(yīng)的 JVM 啟動參數(shù)上添加變更:

-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-XX:+PrintHeapAtGC
-Xloggc:/Users/gaopeng/gc.log

打印出詳細的 GC 日志滴肿,并輸出為文件。

要查看 GC 日志的詳細內(nèi)容佃迄。

Java HotSpot(TM) 64-Bit Server VM (25.221-b11) for bsd-amd64 JRE (1.8.0_221-b11), built on Jul  4 2019 04:36:22 by "java_re" with gcc 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
Memory: 4k page, physical 8388608k(224720k free)

/proc/meminfo:

CommandLine flags: -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 
{Heap before GC invocations=1 (full 0):
 PSYoungGen      total 38400K, used 3022K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 9% used [0x0000000795580000,0x0000000795873838,0x0000000797600000)
  from space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
  to   space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
 ParOldGen       total 87552K, used 0K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x0000000740000000,0x0000000745580000)
 Metaspace       used 2937K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 320K, capacity 388K, committed 512K, reserved 1048576K
2020-10-21T16:40:16.802-0800: 0.107: [GC (System.gc()) [PSYoungGen: 3022K->544K(38400K)] 3022K->552K(125952K), 0.0012104 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap after GC invocations=1 (full 0):
 PSYoungGen      total 38400K, used 544K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 0% used [0x0000000795580000,0x0000000795580000,0x0000000797600000)
  from space 5120K, 10% used [0x0000000797600000,0x0000000797688000,0x0000000797b00000)
  to   space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
 ParOldGen       total 87552K, used 8K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x0000000740002000,0x0000000745580000)
 Metaspace       used 2937K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 320K, capacity 388K, committed 512K, reserved 1048576K
}
{Heap before GC invocations=2 (full 1):
 PSYoungGen      total 38400K, used 544K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 0% used [0x0000000795580000,0x0000000795580000,0x0000000797600000)
  from space 5120K, 10% used [0x0000000797600000,0x0000000797688000,0x0000000797b00000)
  to   space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
 ParOldGen       total 87552K, used 8K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x0000000740002000,0x0000000745580000)
 Metaspace       used 2937K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 320K, capacity 388K, committed 512K, reserved 1048576K
2020-10-21T16:40:16.803-0800: 0.108: [Full GC (System.gc()) [PSYoungGen: 544K->0K(38400K)] [ParOldGen: 8K->396K(87552K)] 552K->396K(125952K), [Metaspace: 2937K->2937K(1056768K)], 0.0036883 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap after GC invocations=2 (full 1):
 PSYoungGen      total 38400K, used 0K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 0% used [0x0000000795580000,0x0000000795580000,0x0000000797600000)
  from space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
  to   space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
 ParOldGen       total 87552K, used 396K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x0000000740063038,0x0000000745580000)
 Metaspace       used 2937K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 320K, capacity 388K, committed 512K, reserved 1048576K
}
Heap
 PSYoungGen      total 38400K, used 998K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
  eden space 33280K, 3% used [0x0000000795580000,0x0000000795679b20,0x0000000797600000)
  from space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
  to   space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
 ParOldGen       total 87552K, used 396K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
  object space 87552K, 0% used [0x0000000740000000,0x0000000740063038,0x0000000745580000)
 Metaspace       used 2949K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 322K, capacity 388K, committed 512K, reserved 1048576K

關(guān)于如何查看 gc 日志泼差,就不在這里展開,有單獨的篇章呵俏。youngGC 次數(shù)頻繁堆缘,對系統(tǒng)的影響一般不會很大。需要注意的是 fullGC普碎。

二 dump 內(nèi)存快照

除了 gc 日志吼肥,還有一類文件是需要我們注意的。

.hprof

即內(nèi)存轉(zhuǎn)儲快照麻车。何時生成快照文件缀皱,需要在 JVM 中設(shè)置并重啟。

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/Users/gaopeng/

如上的 JVM 配置表示在系統(tǒng)內(nèi)存溢出時保存內(nèi)存快照文件到指定目錄下动猬。除了這個快照生成時機啤斗,還有兩個配置項可用

HeapDumpAfterFullGC
HeapDumpBeforeFullGC

這兩個配置項,是在使用

java -XX:+PrintFlagsFinal

查看系統(tǒng)全局配置時找到的枣察,默認都是關(guān)閉狀態(tài)争占。

我使用一個會導(dǎo)致內(nèi)存溢出的 case:

public class HeapOOM {
    static class OOMObject {

    }

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();

        while(true) {
            list.add(new OOMObject());
        }
    }
}

不斷創(chuàng)建對象燃逻,并且也沒有丟棄。然后設(shè)置較小的堆空間臂痕。

-verbose:gc
-Xms20M
-Xmx20M
-Xmn10M
-XX:+PrintGCDetails
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/Users/gaopeng/

運行程序伯襟,很快就會報錯

/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/bin/java -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/gaopeng/ "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=54021:/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/tools.jar:/Users/sina/netty/target/classes:/Users/gaopeng/.m2/repository/io/netty/netty-all/4.1.51.Final/netty-all-4.1.51.Final.jar com.gaop.netty.bio.HeapOOM
[GC (Allocation Failure) [PSYoungGen: 8192K->1008K(9216K)] 8192K->5041K(19456K), 0.0073478 secs] [Times: user=0.03 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) --[PSYoungGen: 9200K->9200K(9216K)] 13233K->19432K(19456K), 0.0121535 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 9200K->0K(9216K)] [ParOldGen: 10232K->9753K(10240K)] 19432K->9753K(19456K), [Metaspace: 3091K->3091K(1056768K)], 0.1368537 secs] [Times: user=0.40 sys=0.01, real=0.14 secs] 
[Full GC (Ergonomics) [PSYoungGen: 7859K->8108K(9216K)] [ParOldGen: 9753K->7643K(10240K)] 17613K->15751K(19456K), [Metaspace: 3091K->3091K(1056768K)], 0.1691025 secs] [Times: user=0.47 sys=0.01, real=0.17 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 8108K->8090K(9216K)] [ParOldGen: 7643K->7643K(10240K)] 15751K->15733K(19456K), [Metaspace: 3091K->3091K(1056768K)], 0.0967636 secs] [Times: user=0.40 sys=0.00, real=0.09 secs] 
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /Users/gaopeng/java_pid6999.hprof ...
Heap dump file created [27817336 bytes in 0.098 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.Arrays.copyOf(Arrays.java:3181)
    at java.util.ArrayList.grow(ArrayList.java:265)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
    at java.util.ArrayList.add(ArrayList.java:462)
    at com.gaop.netty.bio.HeapOOM.main(HeapOOM.java:15)
[Full GC (Ergonomics) [PSYoungGen: 8192K->0K(9216K)] [ParOldGen: 7643K->396K(10240K)] 15835K->396K(19456K), [Metaspace: 3116K->3116K(1056768K)], 0.0042065 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 9216K, used 82K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 8192K, 1% used [0x00000007bf600000,0x00000007bf614938,0x00000007bfe00000)
  from space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
  to   space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
 ParOldGen       total 10240K, used 396K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  object space 10240K, 3% used [0x00000007bec00000,0x00000007bec63330,0x00000007bf600000)
 Metaspace       used 3122K, capacity 4500K, committed 4864K, reserved 1056768K
  class space    used 342K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 1

抓住 gc 日志報錯的關(guān)鍵詞:堆內(nèi)存溢出。作為對象分配的主要區(qū)域握童,產(chǎn)生堆內(nèi)存溢出的問題姆怪,就需要知道在報錯的時候,堆空間的使用情況:到底是什么對象的分配占用了堆空間澡绩。這個時候就可以查看 JVM 生成的 .hprof 文件稽揭,即 dump 日志。

查看工具是 visualVM肥卡。

image.png

匯總視圖中的關(guān)注點

  1. class by number of instance 創(chuàng)建實例數(shù)量
  2. class by size of instance 創(chuàng)建實例占用空間

可以看到溪掀,在限制堆空間 20M 的范圍內(nèi),測試實例 OOMObject 的創(chuàng)建數(shù)量為 810326步鉴,占用空間為 12965216 byte揪胃,約 12MB。占據(jù)了老年代的大部分空間氛琢,而且因為仍存在 GCRoots 的關(guān)聯(lián)喊递,無法被回收,持續(xù)不斷的生成最終導(dǎo)致內(nèi)存溢出阳似。

因此骚勘,dump 文件,可以幫我們分析和定位問題關(guān)鍵點撮奏。

三 基礎(chǔ)配置項總結(jié)

堆大小默認配置

按運行程序的類型俏讹,配置堆的大小比例,取決于實際運行機器的物理內(nèi)存挽荡。

類似于 ES 這樣的數(shù)據(jù)檢索服務(wù)節(jié)點

  • 通常把堆的初始化大小藐石,設(shè)置成物理內(nèi)存的一半。這是因為 ES 是存儲類型的服務(wù)定拟,我們需要預(yù)留一半的內(nèi)存給文件緩存

另一種線上計算型節(jié)點于微,比如處理請求的 web 服務(wù)

  • 通常會把堆內(nèi)存設(shè)置為物理內(nèi)存的 2/3,剩下的 1/3 就是給堆外內(nèi)存使用的青自。

ps. 注:這部分只能是一個大概值株依,并且不同場景的適用配置也不同。

配置項

打印 gc 日志

-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-Xloggc:/Users/gaopeng/gc.log

在不同的時機 dump 內(nèi)存快照

-XX:+HeapDumpOnOutOfMemoryError
-XX:+HeapDumpAfterFullGC
-XX:+HeapDumpBeforeFullGC

打印對象年齡分布

PrintTenuringDistribution

打印這個信息是作為調(diào)整 MaxTenuringThreshold 選項的參考延窜。調(diào)節(jié)年輕代對象晉升老年代需要經(jīng)歷的 GC 次數(shù)恋腕。是一個需要關(guān)注的參數(shù)信息。

打印 STW 時間

PrintGCApplicationStoppedTime

超過一定大小的對象逆瑞,將直接在老年代分配

PretenureSizeThreshold
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荠藤,一起剝皮案震驚了整個濱河市伙单,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哈肖,老刑警劉巖吻育,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異淤井,居然都是意外死亡布疼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門币狠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來游两,“玉大人,你說我怎么就攤上這事漩绵〖福” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵渐行,是天一觀的道長轰坊。 經(jīng)常有香客問我,道長祟印,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任粟害,我火速辦了婚禮蕴忆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘悲幅。我一直安慰自己套鹅,他們只是感情好,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布汰具。 她就那樣靜靜地躺著卓鹿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪留荔。 梳的紋絲不亂的頭發(fā)上吟孙,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音聚蝶,去河邊找鬼杰妓。 笑死,一個胖子當著我的面吹牛碘勉,可吹牛的內(nèi)容都是我干的巷挥。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼验靡,長吁一口氣:“原來是場噩夢啊……” “哼倍宾!你這毒婦竟也來了雏节?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤高职,失蹤者是張志新(化名)和其女友劉穎矾屯,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體初厚,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡件蚕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了产禾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片排作。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖亚情,靈堂內(nèi)的尸體忽然破棺而出妄痪,到底是詐尸還是另有隱情,我是刑警寧澤楞件,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布衫生,位于F島的核電站,受9級特大地震影響土浸,放射性物質(zhì)發(fā)生泄漏罪针。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一黄伊、第九天 我趴在偏房一處隱蔽的房頂上張望泪酱。 院中可真熱鬧,春花似錦还最、人聲如沸墓阀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斯撮。三九已至,卻和暖如春扶叉,著一層夾襖步出監(jiān)牢的瞬間勿锅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工辜梳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留粱甫,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓作瞄,卻偏偏與公主長得像茶宵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宗挥,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350