發(fā)布即巔峰沟启!萬字長文:Java性能調(diào)優(yōu)六大工具:MAT內(nèi)存分析工具

MAT內(nèi)存分析工具

MAT是MemoryAnalyzerTool的簡稱,它是一款功能強大的Java堆內(nèi)存分析器犹菇,可以用于查找內(nèi)存泄漏以及查看內(nèi)存消耗情況德迹。MAT是

基于Eclipse開發(fā)的一款免費的性能分析工具,讀者可以在
http://www.eclipse.org/mat/上下載并使用MAT揭芍。

一胳搞,初識MAT

在分析堆快照前,首先需要導(dǎo)出應(yīng)用程序的堆快照称杨。在本書前文中提到的jmap肌毅、JConsole和VisualVM等工具都可以用于獲得Java應(yīng)用程序的堆快照文件。此外姑原,MAT本身也具有這個功能悬而。

如圖6.66所示,在File菜單中選擇AcquireHeapDump命令锭汛,在彈出對話框的當(dāng)前Java應(yīng)用程序列表中選擇要分析的應(yīng)用程序即可笨奠,如圖6.67所示。

image.png
image.png

除了直接在MAT中導(dǎo)出正在運行的應(yīng)用程序堆快照外唤殴,也可以通過File菜單中的OpenHeapDump命令打開一個既存的堆快照文件般婆。

注意:使用MAT既可以打開一個已有的堆快照,也可以直接從活動Java程序中導(dǎo)出堆快照朵逝。

圖6.68所示為正常打開堆快照文件后的MAT界面蔚袍。

image.png

在圖6.68的右側(cè)界面中顯示了堆快照文件的大小、類配名、實例和ClassLoader的總數(shù)啤咽;餅圖中顯示了當(dāng)前堆快照中最大的對象晋辆。將光標(biāo)懸停在餅圖中,可以在左側(cè)的Inspector界面中查看該對象的詳細(xì)信息闰蚕。在餅圖中單擊栈拖,可以對選中的對象進行更多的操作。

單擊工具欄上的柱狀圖按鈕(如圖6.69所示)没陡,可以查看當(dāng)前堆的類信息涩哟,包括類的對象數(shù)量、淺堆(Shallow)大小和深堆(Retained)大小盼玄,如圖6.70所示贴彼。

image.png

通過柱狀圖界面,可以查找引用選中對象的對象集合以及選中對象所引用的對象集合埃儿。如圖6.71所示器仗,選中java.util.Vector對象并右擊,在彈出的右鍵菜單中選擇Listobjects命令童番,彈出的withoutgoingreferences和withincomingreferences子命令分別表示查找java.util.Vector實例的引用對象精钮,以及引用java.util.Vector實例的對象。

image.png

注意:通過MAT剃斧,可以根據(jù)對象間的引用關(guān)系對內(nèi)存中的對象進行分析轨香。

圖6.72顯示了選擇withincomingreferences命令后的輸出結(jié)果,展示了兩個被主線程引用的java.util.Vector局部變量實例幼东。

為了方便查看臂容,柱狀圖還可以根據(jù)ClassLoader和包對類進行排序。圖6.73顯示了MAT的柱狀圖排序功能根蟹,以及一個按照包進行排序的柱狀圖輸出命令脓杉。

image.png

圖6.72 引用關(guān)系查詢結(jié)果

image.png

二,淺堆和深堆

淺堆(ShallowHeap)和深堆(RetainedHeap)是兩個非常重要的概念简逮,它們分別表示一個對象結(jié)構(gòu)所占用的內(nèi)存大小和一個對象被執(zhí)行GC操作后球散,可以真實釋放的內(nèi)存大小。

淺堆是指一個對象所消耗的內(nèi)存散庶。在32位系統(tǒng)中蕉堰,一個對象引用會占據(jù)4個字節(jié),一個int類型會占據(jù)4個字節(jié)督赤,long型變量會占據(jù)8個字節(jié),每個對象頭需要占用8個字節(jié)泻蚊。

根據(jù)堆快照格式不同躲舌,對象的大小可能會向8字節(jié)進行對齊。以String對象為例性雄,圖6.74顯示了String對象的幾個屬性没卸。

3個int類型以及一個引用類型合計占用的內(nèi)存為3×4+4=16字節(jié)羹奉,再加上對象頭的8個字節(jié),因此String對象占用的空間约计,即淺堆的大小是16+8=24字節(jié)诀拭。淺堆的大小只與對象的結(jié)構(gòu)有關(guān),與對象的實際內(nèi)容無關(guān)煤蚌。也就是說耕挨,無論字符串的長度是多少,內(nèi)容是什么尉桩,淺堆的大小始終是24字節(jié)筒占。

image.png

深堆的概念略微復(fù)雜。要理解深堆蜘犁,首先需要了解保留集(RetainedSet)翰苫。對象A的保留集指當(dāng)對象A被垃圾回收后,可以被釋放的所有的對象集合(包括對象A本身)这橙,即對象A的保留集可以被認(rèn)為是只能通過對象A被直接或者間接訪問到的所有對象的集合奏窑。通俗地說,就是指僅被對象A所持有的對象的集合屈扎。深堆是指對象的保留集中所有對象的淺堆之和埃唯。

注意:淺堆指對象本身占用的內(nèi)存,不包括其內(nèi)部引用對象的大小助隧。一個對象的深堆指只能通過該對象訪問到的(直接或間接)所有對象的淺堆之和筑凫,即對象被回收后,可以釋放的真實空間并村。

下面這個例子很好地詮釋了深堆的概念巍实。首先是表示點的類定義:

image.png

接著是表示線的類定義:

image.png

主函數(shù)構(gòu)造了a、b哩牍、c棚潦、d、e膝昆、f丸边、g這7個點,以及aLine荚孵、bLine妹窖、cLine和dLine這4條線,并在程序最后將a收叶、b骄呼、c、d、e這5個點設(shè)置為null蜓萄。具體代碼如下:

image.png

這段代碼的對象引用關(guān)系如圖6.75所示隅茎,其中a、b嫉沽、c辟犀、d、e對象在使用完成后被設(shè)置為null绸硕。

image.png

根據(jù)Point類的結(jié)構(gòu)堂竟,一個Point實例的淺堆大小為4×2+8=16字節(jié),一個Line實例的淺堆大小為4×2+8=16字節(jié)臣咖。使用MAT得到該示例的內(nèi)存快照文件跃捣,如圖6.76所示。為了閱讀方便夺蛇,筆者將代碼中的變量名標(biāo)識到了內(nèi)存快照中的對象上疚漆。

image.png

可以看到,所有的Point實例淺堆和深堆的大小都是16字節(jié)刁赦。而dLine對象娶聘,淺堆為16字節(jié),深堆也是16字節(jié)甚脉,這是因為dLine對象內(nèi)的兩個點f和g沒有被設(shè)置為null丸升,因此即使dLine被回收,f和g也不會被釋放牺氨。對象cLine內(nèi)的引用對象d和e由于僅在cLine內(nèi)還存在引用狡耻,因此只要cLine被釋放,d和e必然也作為垃圾被回收猴凹,即d和e在cLine的保留集內(nèi)夷狰,因此cLine的深堆為16×2+16=48字節(jié)。

對于aLine和bLine對象郊霎,由于兩者均持有對方的一個點沼头,因此當(dāng)aLine被回收時,公共點a在bLine中依然有引用存在书劝,故不會被回收进倍,點a不在aLine對象的保留集中,因此aLine的深堆大小為16+16=32字節(jié)购对。對象bLine與aLine完全一致猾昆。

在MAT中,無論是在柱狀圖還是對象列表中骡苞,選中對象并右擊垂蜗,在彈出的快捷菜單中都有ShowRetainedSet命令坑赡,它可用于顯示指定類或者對象的保留集。圖6.77和圖6.78分別為在bLine對象上進行該操作么抗,以及bLine對象的保留集。

image.png
image.png

三亚铁,支配樹

MAT提供了一個稱為支配樹(DominatorTree)的對象圖蝇刀。支配樹體現(xiàn)了對象實例間的支配關(guān)系。在對象引用圖中徘溢,所有指向?qū)ο驜的路徑都經(jīng)過對象A吞琐,則認(rèn)為對象A支配對象B。如果對象A是離對象B最近的一個支配對象然爆,則認(rèn)為對象A為對象B的直接支配者。支配樹是基于對象間的引用圖所建立的,它具有以下基本性質(zhì):

·對象A的子樹(所有被對象A支配的對象集合)表示對象A的保留集(retainedset)吴超。

·如果對象A支配對象B庄呈,那么對象A的直接支配者也支配對象B。

·支配樹的邊與對象引用圖的邊不直接對應(yīng)剖张。

如圖6.79所示切诀,左圖表示對象引用圖,右圖表示左圖所對應(yīng)的支配樹搔弄。對象A和B由根對象直接支配幅虑,由于在到對象C的路徑中可以經(jīng)過A,也可以經(jīng)過B顾犹,因此對象C的直接支配者也是根對象倒庵。對象F與對象D相互引用,因為到對象F的所有路徑必然經(jīng)過對象D炫刷,因此對象D是對象F的直接支配者擎宝。而到對象D的所有路徑中,必然經(jīng)過對象C柬唯,即使是從對象F到對象D的引用认臊,從根節(jié)點出發(fā),也是經(jīng)過對象C的锄奢,所以對象D的直接支配者為對象C失晴。

image.png

同理,對象E支配對象G拘央。到達對象H的路徑可以通過對象D涂屁,也可以通過對象E,因此對象D和E都不能支配對象H灰伟,而經(jīng)過對象C既可以到達D也可以達到E拆又,因此對象C為對象H的直接支配者儒旬。

在MAT中,單擊工具欄上的對象支配樹按鈕帖族,如圖6.80所示栈源,可以打開對象支配樹視圖。

image.png

圖6.81顯示了對象支配樹視圖的一部分竖般。該截圖顯示部分main線程對象的直接支配對象甚垦,即main線程對象被回收后將被釋放的所有對象的集合。

注意:在對象支配樹中涣雕,某一個對象的子樹表示在該對象被回收后也將被回收的對象的集合艰亮。

image.png

四,垃圾回收根

在Java系統(tǒng)中挣郭,作為垃圾回收的根節(jié)點可能是以下對象之一迄埃。

·系統(tǒng)類:被
bootstrap/systemClassLoader加載的類,例如在rt.jar包中的所有類兑障。

·JNI局部變量:本地代碼中的局部變量侄非,例如用戶自定義的JNI代碼或者JVM內(nèi)部代碼。

·JNI全局變量:本地代碼中的全局變量流译。

·線程:開始彩库,并且沒有停止的線程。

·在用同步鎖:作為鎖的對象先蒋。例如調(diào)用了wait()或者notify()方法的對象骇钦,或者調(diào)用了synchronized(Object)操作的對象。

·Java局部變量:如函數(shù)的輸入?yún)?shù)及方法中的局部變量竞漾。

·本地棧:本地代碼中的輸入眯搭、輸出參數(shù),例如用戶自定義的JNI代碼或者JVM內(nèi)部代碼业岁。

·Finalizer:在等待隊列中將要被執(zhí)行析構(gòu)函數(shù)的對象鳞仙。

·Unfinalized:擁有析構(gòu)函數(shù),但是沒有被析構(gòu)且不在析構(gòu)隊列中的對象笔时。

·不可達對象:從任何一個根對象都無法到達的對象棍好。但為了能夠在MAT中分析,被MAT標(biāo)志為根允耿。

·未知對象:未知的根類型借笙,用于處理一些特殊的堆格式。

通過MAT较锡,可以列出所有的根對象业稼,如圖6.82所示。

image.png

五蚂蕴,內(nèi)存泄漏檢測

MAT提供了自動檢測內(nèi)存泄漏低散,以及統(tǒng)計堆快照內(nèi)對象分布情況的工具俯邓。圖6.83展示了內(nèi)存泄漏檢測工具的使用方法。選擇菜單中的LeakSuspects命令熔号,MAT會自動生成一份報告稽鞭。這份報告羅列了系統(tǒng)內(nèi)可能存在內(nèi)存泄漏的問題點。圖6.84展示了報告中給出的一個問題點樣例引镊。

image.png

注意:仔細(xì)閱讀MAT給出的內(nèi)存泄漏報告川慌,可以幫助開發(fā)人員更快地找到系統(tǒng)的潛在問題。

image.png

六祠乃,最大對象報告

系統(tǒng)中占用內(nèi)存最大的幾個對象,往往是解決系統(tǒng)性能問題的關(guān)鍵所在兑燥。如果應(yīng)用程序發(fā)生內(nèi)存泄漏亮瓷,那么泄漏的對象通常會在堆快照中占據(jù)很大的比重。因此降瞳,查看和分析堆快照中最大的對象具有較高的價值嘱支。

在MAT中,可以自動查找并顯示消耗內(nèi)存最多的幾個對象挣饥。如圖6.85所示除师,通過選擇TopConsumers命令,可以打開消耗內(nèi)存最多的對象的報告扔枫,其中主要以餅圖和表格的形式來展示汛聚。

image.png

七,查找支配者

通過MAT短荐,開發(fā)人員還可以很方便地查找某一個對象或者類的支配者(有關(guān)支配者的概念倚舀,可以參考6.7.3節(jié)“支配樹”)。雖然在支配樹頁面中擁有完整的信息忍宋,但是通過MAT提供的支配者查找功能可以更方便地進行查找痕貌。圖6.86顯示了如何查找對象的支配者。

image.png

在選擇ImmediateDominators命令后糠排,會彈出一個參數(shù)對話框舵稠,用于設(shè)置查找參數(shù),如圖6.87所示入宦。在參數(shù)對話框中哺徊,注意務(wù)必正確輸入-skip參數(shù),否則查詢結(jié)果會忽略所有定義在-skip參數(shù)中的類和實例乾闰。

ImmediateDominators會輸出選中對象的直接支配者(將-skip指定的對象排除在外)唉工。

image.png

八,線程分析

在堆快照中汹忠,還包括當(dāng)前的線程信息淋硝,通過MAT可以查看這些信息雹熬。如圖6.88所示,通過ThreadDetails谣膳、ThreadOverview和ThreadStacks這3個命令竿报,可以查看線程詳情。

image.png

圖6.89所示為選擇ThreadStacks命令后的輸出結(jié)果继谚,其中顯示了當(dāng)前堆快照中的所有線程及線程引用的對象烈菌。

image.png

九,集合使用情況分析

MAT提供了一套對集合使用狀態(tài)進行分析的工具花履,如圖6.90所示芽世。

image.png

使用這些工具,可以查看數(shù)組诡壁、集合的填充率济瓢;可以觀察集合內(nèi)的數(shù)據(jù);也可以分析哈希表的沖突率妹卿。

注意:通過對集合使用情況進行分析旺矾,可以更好地了解系統(tǒng)的內(nèi)存使用情況,查找浪費的內(nèi)存空間夺克。

選擇CollectionFillRatio命令箕宙,可以展示給定集合的填充率。圖6.91所示為該功能的輸出結(jié)果铺纽,其中顯示了填充率為0柬帕、20%以下、80%以下和100%以下的集合個數(shù)狡门。

image.png

通過選擇HashEntries命令雕崩,可以查看Hash表的內(nèi)容。圖6.92所示為該功能的一個輸出示例融撞,其中顯示了選中的Hash表的內(nèi)容盼铁。對于表中的Key和Value對象,通過右鍵快捷菜單尝偎,還可以進一步分析它們的引用情況和其他具體信息饶火。

image.png

十,擴展MAT

MAT是基于Eclipse開發(fā)平臺的產(chǎn)品致扯,因此它也具有很好的擴展性肤寝。開發(fā)者可以使用Eclipse對MAT進行擴展,從而實現(xiàn)符合開發(fā)人員需要的功能更加強勁的內(nèi)存分析工具抖僵。通過擴展MAT鲤看,讀者可以實現(xiàn)諸如自動對象查詢、優(yōu)化界面顯示耍群、報表增強等功能义桂。本節(jié)將通過一個簡單的MAT插件找筝,介紹擴展MAT的基本步驟和方法。

注意:MAT是基于Eclipse的慷吊,因此對MAT進行二次開發(fā)與開發(fā)Eclipse插件非常類似袖裕。

在Java中,java.lang.String對象實現(xiàn)是基于內(nèi)部的value字符數(shù)組溉瓶、偏移量offset和字符串長度count來定義字符串String的真實取值的急鳄。如果內(nèi)部數(shù)組value的實際長度很長,而字符串真實長度count的數(shù)值很小堰酿,則說明這個String的內(nèi)存使用率不高疾宏,存在較為嚴(yán)重的內(nèi)存浪費。

使用公式count/value.length可以計算當(dāng)前String對象的內(nèi)存使用率触创。在最優(yōu)情況下坎藐,String對象的內(nèi)存使用率是100%,即表示value數(shù)組中的所有字符都是當(dāng)前字符串的內(nèi)容嗅榕。當(dāng)使用類似String.subString()的函數(shù)生成新的字符串時,String對象通過調(diào)整offset和count吵聪,而非創(chuàng)建新的value數(shù)組來生成新的字符串凌那,此時String對象的內(nèi)存利用率就會下降。

本節(jié)中展示的插件將在顯示String對象時吟逝,展示String對象的內(nèi)存利用率帽蝶,幫助開發(fā)者快速定位可以優(yōu)化的字符串對象。

為擴展MAT块攒,首先需要安裝MAT程序及Eclipse開發(fā)工具励稳。

(1)在Eclipse平臺中添加MAT目標(biāo)平臺。在Eclipse中打開對話框:Windows|Preferences|Plug-inDevelopment|TargetPlatform囱井。添加MAT平臺驹尼,選擇Add|Nothing|Next。在目標(biāo)平臺的Locations頁面中庞呕,添加Installation新翎,并指定MAT的安裝路徑,如圖6.93所示住练。單擊Finish按鈕地啰,并選擇剛剛添加的MAT平臺作為目標(biāo)平臺。圖6.94所示為配置完成后的目標(biāo)平臺讲逛。

image.png

(2)創(chuàng)建一個插件工程亏吝。選擇File|New|Other|Plug-inproject命令,假設(shè)工程名稱是MATExtension盏混,其他參數(shù)可以使用默認(rèn)設(shè)置蔚鸥。創(chuàng)建完成后惜论,在工程的Dependencies頁面中添加org.eclipse.mat.api依賴,如圖6.95所示株茶。

image.png
image.png

(3)添加插件的擴展點来涨。在本例中添加
org.eclipse.mat.api.nameResolver,如圖6.96所示启盛。在實際開發(fā)中蹦掐,讀者可以根據(jù)自己的需要,選擇合適的擴展點增強MAT的功能僵闯。接著填寫擴展點的具體信息卧抗,如實現(xiàn)擴展點接口的類名和包名,Eclipse會自動生成指定的類鳖粟,如圖6.97所示社裆。

image.png
image.png

編輯生成的StringUsageDisplayer類,具體代碼如下:

image.png

StringUsageDisplayer的功能是當(dāng)MAT中顯示String對象時向图,計算String對象的count值與value數(shù)組的長度比值泳秀。注釋@Subject指定當(dāng)前
IClassSpecificNameResolver只對java.lang.String對象有效。

(4)當(dāng)完成開發(fā)后榄攀,還需要對插件進行打包嗜傅。選擇File|Export|Plug-inDevelopment|
Deployableplug-insandfragments命令,在打開的對話框中選中要打包的插件檩赢,并設(shè)置MAT的安裝路徑進行插件安裝吕嘀,如圖6.98所示。

安裝完成后贞瞒,在MAT的plugins目錄下就有了MATExtension插件的JAR包偶房。

安裝插件后的MAT,可以使用以下OQL查詢?nèi)〉盟袃?nèi)存利用率不是100%的String军浆。

image.png

查詢結(jié)果如圖6.99所示棕洋,其中不僅顯示了字符串的真實取值,也顯示了當(dāng)前字符串的內(nèi)存使用率乒融,可以幫助開發(fā)人員快速定位能夠優(yōu)化的字符串拍冠。

注意:通過對MAT的擴展,可以讓MAT更貼近實際生產(chǎn)環(huán)境簇抵,使之更易于使用庆杜,提高了堆內(nèi)存分析的效率。

image.png
image.png

本文給大家講解的內(nèi)容是Java性能調(diào)優(yōu)六大工具:MAT內(nèi)存分析工具

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碟摆,一起剝皮案震驚了整個濱河市晃财,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖断盛,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罗洗,死亡現(xiàn)場離奇詭異,居然都是意外死亡钢猛,警方通過查閱死者的電腦和手機伙菜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來命迈,“玉大人贩绕,你說我怎么就攤上這事『撸” “怎么了淑倾?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長征椒。 經(jīng)常有香客問我娇哆,道長,這世上最難降的妖魔是什么勃救? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任碍讨,我火速辦了婚禮,結(jié)果婚禮上蒙秒,老公的妹妹穿的比我還像新娘勃黍。我一直安慰自己,他們只是感情好税肪,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布溉躲。 她就那樣靜靜地躺著榜田,像睡著了一般益兄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上箭券,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天净捅,我揣著相機與錄音,去河邊找鬼辩块。 笑死蛔六,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的废亭。 我是一名探鬼主播国章,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼豆村!你這毒婦竟也來了液兽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤掌动,失蹤者是張志新(化名)和其女友劉穎四啰,沒想到半個月后宁玫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡柑晒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年欧瘪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匙赞。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡佛掖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出罚屋,到底是詐尸還是另有隱情苦囱,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布脾猛,位于F島的核電站撕彤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏猛拴。R本人自食惡果不足惜羹铅,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望愉昆。 院中可真熱鬧职员,春花似錦、人聲如沸跛溉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芳室。三九已至专肪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堪侯,已是汗流浹背嚎尤。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留伍宦,地道東北人芽死。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像次洼,于是被迫代替她去往敵國和親关贵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

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