Java垃圾回收機(jī)制

一、常用垃圾回收機(jī)制

1. 標(biāo)記-清除算法(mark-sweep)

顧名思義痘括,標(biāo)記-清除算法分為兩個階段长窄,標(biāo)記(mark)和清除(sweep).

在標(biāo)記階段滔吠,collector從mutator根對象開始進(jìn)行遍歷,對從mutator根對象可以訪問到的對象都打上一個標(biāo)識挠日,一般是在對象的header中疮绷,將其記錄為可達(dá)對象。

而在清除階段嚣潜,collector對堆內(nèi)存(heap memory)從頭到尾進(jìn)行線性的遍歷冬骚,如果發(fā)現(xiàn)某個對象沒有標(biāo)記為可達(dá)對象-通過讀取對象的header信息,則就將其回收懂算。

[圖片上傳失敗...(image-8c300b-1510574775013)]

從上圖我們可以看到只冻,在Mark階段,從根對象1可以訪問到B對象犯犁,從B對象又可以訪問到E對象属愤,所以B,E對象都是可達(dá)的。同理酸役,F(xiàn),G,J,K也都是可達(dá)對象住诸。到了Sweep階段,所有非可達(dá)對象都會被collector回收涣澡。同時贱呐,Collector在進(jìn)行標(biāo)記和清除階段時會將整個應(yīng)用程序暫停(mutator),等待標(biāo)記清除結(jié)束后才會恢復(fù)應(yīng)用程序的運(yùn)行入桂。

缺點

? 標(biāo)記-清除算法的比較大的缺點就是垃圾收集后有可能會造成大量的內(nèi)存碎片奄薇,像上面的圖片所示,垃圾收集后內(nèi)存中存在三個內(nèi)存碎片抗愁,假設(shè)一個方格代表1個單位的內(nèi)存馁蒂,如果有一個對象需要占用3個內(nèi)存單位的話,那么就會導(dǎo)致Mutator一直處于暫停狀態(tài)蜘腌,而Collector一直在嘗試進(jìn)行垃圾收集沫屡,直到Out of Memory。

2. 標(biāo)記-壓縮算法(mark-compact)

? 顧名思義撮珠,標(biāo)記-壓縮算法分為兩個階段沮脖,標(biāo)記(mark)和壓縮(compact).

? 其中標(biāo)記階段跟標(biāo)記-清除算法中的標(biāo)記階段是一樣的,而對于壓縮階段芯急,它的工作就是移動所有的可達(dá)對象到堆內(nèi)存的同一個區(qū)域中勺届,使他們緊湊的排列在一起,從而將所有非可達(dá)對象釋放出來的空閑內(nèi)存都集中在一起娶耍,通過這樣的方式來達(dá)到減少內(nèi)存碎片的目的免姿。

img

3. 復(fù)制算法(copying)

堆內(nèi)存對半分為兩個半?yún)^(qū),只用其中一個半?yún)^(qū)來進(jìn)行對象內(nèi)存的分配伺绽,如果在這個半?yún)^(qū)內(nèi)存不夠給新的對象分配了养泡,那么就開始進(jìn)行垃圾收集嗜湃,將這個半?yún)^(qū)中的所有可達(dá)對象都拷貝到另外一個半?yún)^(qū)中去,然后繼續(xù)在另外那個半?yún)^(qū)進(jìn)行新對象的內(nèi)存分配澜掩。

img

?
mg

缺點:

? 存壓縮為原來的一半购披,利用率比較低,典型的空間換時間

4. 引用計數(shù)算法(reference counting)

? 通過在對象頭中分配一個空間來保存該對象被引用的次數(shù)肩榕。如果該對象被其它對象引用刚陡,則它的引用計數(shù)加一,如果刪除對該對象的引用株汉,那么它的引用計數(shù)就減一筐乳,當(dāng)該對象的引用計數(shù)為0時,那么該對象就會被回收乔妈。

? 采用引用計數(shù)的垃圾收集機(jī)制跟前面三種垃圾收集機(jī)制最大的不同在于蝙云,垃圾收集的開銷被分?jǐn)偟秸麄€應(yīng)用程序的運(yùn)行當(dāng)中了,而不是在進(jìn)行垃圾收集時路召,要掛起整個應(yīng)用的運(yùn)行勃刨,直到對堆中所有對象的處理都結(jié)束。因此股淡,采用引用計數(shù)的垃圾收集不屬于嚴(yán)格意義上的"Stop-The-World"的垃圾收集機(jī)制身隐。

注意:

  • 當(dāng)某個對象的引用計數(shù)減為0時,collector需要遞歸遍歷它所指向的所有域唯灵,將它所有域所指向的對象的引用計數(shù)都減一贾铝,然后才能回收當(dāng)前對象。

  • 但是這種引用計數(shù)算法有一個比較大的問題埠帕,那就是它不能處理環(huán)形數(shù)據(jù) - 即如果有兩個對象相互引用垢揩,那么這兩個對象就不能被回收,因為它們的引用計數(shù)始終為1敛瓷。這也就是我們常說的“內(nèi)存泄漏”問題水孩。如下圖:

    [圖片上傳失敗...(image-56fb94-1510574775013)]

5. 分代收集算法

? 當(dāng)前的商業(yè)虛擬機(jī)都采用的是”分代收集“算法,一般是把java堆分成新生代和老生代琐驴,這樣就可以根據(jù)各個年代的特點采用最適當(dāng)?shù)睦占惴ǎ律谐颖辏瑢ο蟠蠖嗍恰背λ馈翱梢圆捎脧?fù)制算法绝淡,而老年代的對象存活率比較高,而且沒有擔(dān)辈越空間進(jìn)行內(nèi)存分配牢酵,就要采用”標(biāo)記-清除算法“或者”標(biāo)記-整理“算法。

二衙猪、Java垃圾回收

1. Java的內(nèi)存分布

[圖片上傳失敗...(image-5469f-1510574775013)]

其中馍乙,堆內(nèi)存分為年輕代和年老代布近,非堆內(nèi)存主要是Permanent區(qū)域,主要用于存儲一些類的元數(shù)據(jù)丝格,常量池等信息撑瞧。而年輕代又分為兩種,一種是Eden區(qū)域显蝌,另外一種是兩個大小對等的Survivor區(qū)域预伺。

2. Java年輕代垃圾回收機(jī)制

[圖片上傳失敗...(image-3bdb3-1510574775013)]

? 部分的新創(chuàng)建對象分配在新生代。因為大部分對象很快就會變得不可達(dá)曼尊,所以它們被分配在新生代酬诀,然后消失不再。當(dāng)對象從新生代移除時骆撇,我們稱之為"Minor GC"瞒御。新生代使用的是復(fù)制收集算法

? 新生代劃分為三個部分:分別為Eden神郊、Survivor from肴裙、Survivor to,大小比例為8:1:1(為了防止復(fù)制收集算法的浪費(fèi)內(nèi)存過大)屿岂。每次只使用Eden和其中的一塊Survivor践宴,回收時將存活的對象復(fù)制到另一塊Survivor中,這樣就只有10%的內(nèi)存被浪費(fèi)爷怀,但是如果存活的對象總大小超過了Survivor的大小阻肩,那么就把多出的對象放入老年代中。

在三個區(qū)域中有兩個是Survivor區(qū)运授。對象在三個區(qū)域中的存活過程如下:

  1. 大多數(shù)新生對象都被分配在Eden區(qū)烤惊。
  2. 第一次GC過后Eden中還存活的對象被移到其中一個Survivor區(qū)。
  3. 再次GC過程中吁朦,Eden中還存活的對象會被移到之前已移入對象的Survivor區(qū)柒室。
  4. 一旦該Survivor區(qū)域無空間可用時,還存活的對象會從當(dāng)前Survivor區(qū)移到另一個空的Survivor區(qū)逗宜。而當(dāng)前Survivor區(qū)就會再次置為空狀態(tài)雄右。
  5. 經(jīng)過數(shù)次(默認(rèn)是15次)在兩個Survivor區(qū)域移動后還存活的對象最后會被移動到老年代。

如上所述纺讲,兩個Survivor區(qū)域在任何時候必定有一個保持空白擂仍。如果同時有數(shù)據(jù)存在于兩個Survivor區(qū)或者兩個區(qū)域的的使用量都是0,則意味著你的系統(tǒng)可能出現(xiàn)了運(yùn)行錯誤熬甚。

3. Java老年代垃圾回收機(jī)制

? 存活在新生代中但未變?yōu)椴豢蛇_(dá)的對象會被復(fù)制到老年代逢渔。一般來說老年代的內(nèi)存空間比新生代大,所以在老年代GC發(fā)生的頻率較新生代低一些乡括。當(dāng)對象從老年代被移除時肃廓,我們稱之為 "Major GC"(或者Full GC)智厌。 老年代使用標(biāo)記-清理或標(biāo)記-整理算法

空間分配擔(dān)保

在發(fā)生Minor GC前,虛擬機(jī)會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間盲赊。

  1. 如果大于铣鹏,那么Minor GC可以確保是安全的。

  2. 如果小于角钩,虛擬機(jī)會查看HandlePromotionFailure設(shè)置值是否允許擔(dān)任失敗吝沫。

    • 如果允許,那么會繼續(xù)檢查老年代最大可用連續(xù)空間是否大于歷次晉升老年代對象的平均大小
      • 如果大于递礼,將嘗試著進(jìn)行一次Minor GC惨险,盡管這次Minor GC是有風(fēng)險的
      • 如果小于,進(jìn)行一次Full GC
    • 如果不允許脊髓,也要改為進(jìn)行一次Full GC

    ? 前面提到過辫愉,新生代使用復(fù)制收集算法,但為了內(nèi)存利用率将硝,只使用其中一個Survivor空間來作為輪換備份恭朗,因此當(dāng)出現(xiàn)大量對象在Minor GC后仍然存活的情況時(最極端就是內(nèi)存回收后新生代中所有對象都存活),就需要老年代進(jìn)行分配擔(dān)保依疼,讓Survivor無法容納的對象直接進(jìn)入老年代痰腮。與生活中的貸款擔(dān)保類似,老年代要進(jìn)行這樣的擔(dān)保律罢,前提是老年代本身還有容納這些對象的剩余空間膀值,一共有多少對象會活下來,在實際完成內(nèi)存回收之前是無法明確知道的误辑,所以只好取之前每一次回收晉升到老年代對象容量的平均大小值作為經(jīng)驗值沧踏,與老年代的剩余空間進(jìn)行比較,決定是否進(jìn)行Full GC來讓老年代騰出更多空間巾钉。

    ? 取平均值進(jìn)行比較其實仍然是一種動態(tài)概率的手段翘狱,也就是說如果某次Minor GC存活后的對象突增,遠(yuǎn)遠(yuǎn)高于平均值的話砰苍,依然會導(dǎo)致?lián)J潦匈。℉andle Promotion Failure)。如果出現(xiàn)了HandlePromotionFailure失敗赚导,那就只好在失敗后重新發(fā)起一次Full GC历等。雖然擔(dān)保失敗時繞的圈子是最大的,但大部分情況下都還是會將HandlePromotionFailure開關(guān)打開辟癌,避免Full GC過于頻繁。

2.Java垃圾收集器

img
  • Serial收集器(Serial/Serial Old)

    Serial是一個單線程的收集器荐捻,但它的“單線程”意義并不僅僅說明它只會使用一個CPU或一條手機(jī)此案成去完成垃圾和收集工作黍少,更重要的是它進(jìn)行垃圾收集時寡夹,必須暫停其他所有的工作線程,直到它收集結(jié)束厂置。

    img
  • ParNew收集器

    ParNew收集器其實就是Serial收集器的多線程版本菩掏。

    它是運(yùn)行在Server模式下的虛擬機(jī)中首選的新生代收集器,其中有一個與性能無關(guān)但很重要的原因是:除了Serial收集器外昵济,目前只有它能與CMS收集器配合工作智绸。

    img
  • Parallel Scavenge收集器

    ? 該收集器也是一個新生代的垃圾收集器,他也是使用復(fù)制算法的收集器访忿,又是一個并行的垃圾收集器瞧栗。該收集器的特點是他的關(guān)注點與其他的收集器不同,CMS等收集器的關(guān)注點是盡可能縮短垃圾回收時用戶線程的停頓時間海铆,而parallel Scavenge收集器的目標(biāo)是達(dá)到一個可控制的吞吐量迹恐。所謂吞吐量就是CPU用于運(yùn)行代碼的時間與CPU總消耗時間的比值,即吞吐量=運(yùn)行用戶代碼時間/(運(yùn)行用戶代碼時間+垃圾回收時間)卧斟,比如虛擬機(jī)總共運(yùn)行100分鐘殴边,垃圾回收占用了1分鐘,那么吞吐量就是99%珍语。

  • Parallel Old收集器

    Parallel Old是Parallel Scavenge收集器的老年代版本锤岸,使用多線程和“標(biāo)記-整理”算法。

    img
  • CMS收集器

    CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器板乙。CMS是基于“標(biāo)記-清除”算法實現(xiàn)的是偷,它的運(yùn)作過程相對于前面幾種收集器來說更復(fù)雜一些,整個過程分為4個步驟亡驰,包括:

    • 初始標(biāo)記(CMS initial mark)
    • 并發(fā)標(biāo)記(CMS concurrent mark)
    • 重新標(biāo)記(CMS remark)
    • 并發(fā)清除(CMS concurrent sweep)

    其中晓猛,初始標(biāo)記、重新標(biāo)記這兩個步驟仍然需要”Stop The world”凡辱。初始標(biāo)記僅僅只是標(biāo)記一下GC Roots Tracing的過程戒职,而重新標(biāo)記階段則是為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄,這個階段的停頓時間一般會比初始標(biāo)記階段稍長一些透乾,但遠(yuǎn)比并發(fā)標(biāo)記的時間短洪燥。

    由于整個過程中耗時最長的并發(fā)標(biāo)記和并發(fā)清除過程收集器線程都可以與用戶線程一起工作,所以乳乌,從總體上來說捧韵,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的。

    img

    CMS的優(yōu)勢:并發(fā)收集汉操、低停頓再来。

    CMS的缺點:

    • 對CPU資源非常敏感。CMS默認(rèn)啟動的回收線程數(shù)是(CPU數(shù)量 + 3)/4,并發(fā)回收時垃圾收集線程所占CPU資源隨著CPU數(shù)量的增加而下降,而且在CPU不足4個時芒篷,CMS對用戶程序的影響就可能變得很大搜变,導(dǎo)致執(zhí)行速度降低。
    • CMS收集器無法處理浮動垃圾针炉,可能出現(xiàn)“Concurrent Mode Failure”失敗而導(dǎo)致另一次Full GC的產(chǎn)生挠他。
    • CMS是一款基于“標(biāo)記-清除”算法實現(xiàn)的收集器,這意味著收集結(jié)束時會有大量空間碎片產(chǎn)生篡帕≈城郑空間碎片太多的時候,將會給大對象分配帶來很大麻煩镰烧。
  • G1收集器

    G1是一款面向服務(wù)端應(yīng)用的垃圾收集器拢军。HOtSpot開發(fā)團(tuán)隊賦予它的使命是未來可以替換掉CMS收集器。

    G1具備如下特點:

    • 并行與并發(fā):G1能充分利用多CPU拌滋、多核環(huán)境下的硬件優(yōu)勢朴沿,使用多個CPU來縮短Stop-The-World停頓的時間,部分其他收集器原本需要停頓Java線程執(zhí)行的GC動作败砂,G1收集器仍然可以通過并發(fā)的方式讓Java程序繼續(xù)執(zhí)行赌渣。
    • 分代收集:雖然G1可以不需要其他收集器配合就能獨(dú)立管理整個GC堆,但它能夠采用不同的方式去處理新創(chuàng)建的對象和已經(jīng)存活了一段時間昌犹、熬過多次GC的就對象以獲取更好的收集效果坚芜。
    • 空間整合:G1從整體上來看是基于“標(biāo)記-整理”算法實現(xiàn)的收集器,從局部(兩個Region之間)上來看是基于“復(fù)制”算法實現(xiàn)的斜姥,這意味著G1運(yùn)作期間不會產(chǎn)生內(nèi)存空間碎片鸿竖,收集后能提供規(guī)整的可用內(nèi)存。
    • 可預(yù)測的停頓:這是G1相對于CMS的另一大優(yōu)勢铸敏。

    ?

    G1垃圾收集器和CMS垃圾收集器有幾點不同缚忧。首先,最大的不同是內(nèi)存的組織方式變了杈笔。Eden闪水,Survivor和Tenured等內(nèi)存區(qū)域不再是連續(xù)的了,而是變成了一個個大小一樣的region - 每個region從1M到32M不等蒙具。

    ?

    [圖片上傳失敗...(image-f2affc-1510574775013)]

    ?

    一個region有可能屬于Eden球榆,Survivor或者Tenured內(nèi)存區(qū)域。圖中的E表示該region屬于Eden內(nèi)存區(qū)域禁筏,S表示屬于Survivor內(nèi)存區(qū)域持钉,T表示屬于Tenured內(nèi)存區(qū)域。圖中空白的表示未使用的內(nèi)存空間篱昔。G1垃圾收集器還增加了一種新的內(nèi)存區(qū)域每强,叫做Humongous內(nèi)存區(qū)域,如圖中的H塊。這種內(nèi)存區(qū)域主要用于存儲大對象-即大小超過一個region大小的50%的對象空执。

    ?

    在G1垃圾收集器中窘茁,年輕代的垃圾回收過程跟PS垃圾收集器和CMS垃圾收集器差不多。

    [圖片上傳失敗...(image-5a0154-1510574775013)]

    ?

    對于年老代上的垃圾收集脆烟,G1垃圾收集器也分為4個階段,基本跟CMS垃圾收集器一樣房待,但略有不同:

    1. Initial Mark階段 - 同CMS垃圾收集器的Initial Mark階段一樣邢羔,G1也需要暫停應(yīng)用程序的執(zhí)行,它會標(biāo)記從根對象出發(fā)桑孩,在根對象的第一層孩子節(jié)點中標(biāo)記所有可達(dá)的對象拜鹤。但是G1的垃圾收集器的Initial Mark階段是跟minor gc一同發(fā)生的。也就是說流椒,在G1中敏簿,你不用像在CMS那樣,單獨(dú)暫停應(yīng)用程序的執(zhí)行來運(yùn)行Initial Mark階段宣虾,而是在G1觸發(fā)minor gc的時候一并將年老代上的Initial Mark給做了惯裕。

    2. Concurrent Mark階段 - 在這個階段G1做的事情跟CMS一樣。但G1同時還多做了一件事情绣硝,那就是蜻势,如果在Concurrent Mark階段中,發(fā)現(xiàn)哪些Tenured region中對象的存活率很小或者基本沒有對象存活鹉胖,那么G1就會在這個階段將其回收掉握玛,而不用等到后面的clean up階段。這也是Garbage First名字的由來甫菠。同時挠铲,在該階段,G1會計算每個 region的對象存活率寂诱,方便后面的clean up階段使用 拂苹。

    3. Remark階段 - 在這個階段G1做的事情跟CMS一樣, 但是采用的算法不同,能夠在Remark階段更快的標(biāo)記可達(dá)對象刹衫。

    4. Clean up/Copy階段 - 在G1中醋寝,沒有CMS中對應(yīng)的Sweep階段。相反 它有一個Clean up/Copy階段带迟,在這個階段中,G1會挑選出那些對象存活率低的region進(jìn)行回收音羞,這個階段也是和minor gc一同發(fā)生的,如下圖所示:

      [圖片上傳失敗...(image-ed332d-1510574775013)]

    從上可以看到,由于Initial Mark階段和Clean up/Copy階段都是跟minor gc同時發(fā)生的仓犬,相比于CMS嗅绰,G1暫停應(yīng)用程序的時間更少,從而提高了垃圾回收的效率。

    ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窘面,一起剝皮案震驚了整個濱河市翠语,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌财边,老刑警劉巖肌括,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異酣难,居然都是意外死亡谍夭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門憨募,熙熙樓的掌柜王于貴愁眉苦臉地迎上來紧索,“玉大人,你說我怎么就攤上這事菜谣≈槠” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵尾膊,是天一觀的道長媳危。 經(jīng)常有香客問我,道長眯停,這世上最難降的妖魔是什么济舆? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮莺债,結(jié)果婚禮上滋觉,老公的妹妹穿的比我還像新娘。我一直安慰自己齐邦,他們只是感情好椎侠,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著措拇,像睡著了一般我纪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丐吓,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天浅悉,我揣著相機(jī)與錄音,去河邊找鬼券犁。 笑死术健,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的粘衬。 我是一名探鬼主播荞估,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼咳促,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了勘伺?” 一聲冷哼從身側(cè)響起跪腹,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎飞醉,沒想到半個月后冲茸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缅帘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年噪裕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片股毫。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖召衔,靈堂內(nèi)的尸體忽然破棺而出铃诬,到底是詐尸還是另有隱情,我是刑警寧澤苍凛,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布趣席,位于F島的核電站,受9級特大地震影響醇蝴,放射性物質(zhì)發(fā)生泄漏宣肚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一悠栓、第九天 我趴在偏房一處隱蔽的房頂上張望霉涨。 院中可真熱鬧,春花似錦惭适、人聲如沸蹄溉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至霎挟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間错英,已是汗流浹背菠秒。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留戒突,地道東北人屯碴。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像妖谴,于是被迫代替她去往敵國和親窿锉。 傳聞我的和親對象是個殘疾皇子酌摇,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

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