Java-GC垃圾回收器和垃圾回收算法

1. 垃圾回收算法

GC和FGC的區(qū)別

次數(shù)上頻繁收集Young區(qū),次數(shù)上較少收集Old區(qū)毕泌,基本不動(dòng)元空間顿仇。GC(YGC)是指新生代的垃圾回收温数,GC很頻繁,因?yàn)榇蠖鄶?shù)的Java對(duì)象存活時(shí)間都很短憔披,所以GC的回收速度很快、也很頻繁。FGC是指養(yǎng)老區(qū)(Old)的垃圾回收芬膝,GC回收速度不頻繁望门,也不快,因?yàn)橐獟呙枵麄€(gè)老年區(qū)的空間锰霜,所以它的速度比GC慢10倍左右筹误。
GC的四大算法引用計(jì)數(shù)法、復(fù)制算法(Copying)癣缅、標(biāo)記清除(Mark-Swap)厨剪、標(biāo)記壓縮(Mark-Compact)

1.1 引用計(jì)數(shù)法

  • JVM的實(shí)現(xiàn)一般不使用這種方法,應(yīng)用在微軟的COM/ActionScript3/Python...
  • 有被引用這個(gè)對(duì)象友存,計(jì)數(shù)器就+1祷膳,沒(méi)有被引用的就被回收了
    缺點(diǎn)如下:
  • 1.每次對(duì)對(duì)象進(jìn)行賦值的時(shí)候都要將計(jì)數(shù)器+1,且計(jì)數(shù)器本身也有消耗
  • 2.較難處理循環(huán)引用(A引用B屡立,B引用A)
System.gc();  //手動(dòng)喚醒GC直晨,禁用手動(dòng)GC,JDK1.8的垃圾回收已經(jīng)很智能了
//不是立刻執(zhí)行膨俐,也是要看系統(tǒng)的調(diào)度勇皇,類(lèi)似創(chuàng)建了線程,也不是立刻執(zhí)行吟策,得看系統(tǒng)的調(diào)度

1.2 復(fù)制算法

  • 新生代(Young)中使用的GC就是使用的復(fù)制算法儒士。
-XX:MaxTenuringThreshold  //設(shè)置對(duì)象再新生代中存活代數(shù),默認(rèn)是15檩坚,最大也就是15着撩,因?yàn)閙arkword只留了4bit給其標(biāo)識(shí)
  • 復(fù)制算法的基本思想是將內(nèi)存分為兩塊,每次只用其中一塊匾委,這塊內(nèi)存用完拖叙,就將或者的對(duì)象復(fù)制到另外一塊上去。復(fù)制算法不會(huì)產(chǎn)生內(nèi)存碎片赂乐,但是耗空間薯鳍。
  • 從根集合(GCRoots)開(kāi)始,通過(guò)Tracing從FROM區(qū)找到存活對(duì)象挨措,拷貝到TO區(qū)中挖滤;FROM區(qū)和TO區(qū)交換,下次內(nèi)存分配繼續(xù)從TO開(kāi)始浅役。

1.3 標(biāo)記清除算法

  • 思想是:先從內(nèi)存中標(biāo)記出來(lái)要回收的對(duì)象斩松,然后進(jìn)行統(tǒng)一回收。
  • 標(biāo)記清除算法空間節(jié)約出來(lái)了觉既,但是會(huì)產(chǎn)生內(nèi)存碎片惧盹。而且要掃描兩次乳幸,一次標(biāo)記,一次清除钧椰,浪費(fèi)了時(shí)間粹断。

1.4 標(biāo)記壓縮算法

  • 思想是:先從內(nèi)存中標(biāo)記出來(lái)要回收的對(duì)象,然后進(jìn)行統(tǒng)一回收嫡霞,之后對(duì)剩下來(lái)的存活的對(duì)象進(jìn)行整理(丟在同一側(cè))
  • 缺點(diǎn):效率不高瓶埋,需要一定對(duì)象的成本,耗時(shí)比較嚴(yán)重诊沪。
  • 改進(jìn):標(biāo)記-清除-壓縮算法:標(biāo)記清除和標(biāo)記壓縮算法的折中悬赏,進(jìn)行多次的GC后才壓縮。

JVM的GC用的是哪種方法娄徊?

新生代GC使用復(fù)制算法闽颇,在老年代FGC使用標(biāo)記壓縮、標(biāo)記清除算法

2. 垃圾回收算法和垃圾回收器的關(guān)系寄锐?

GC算法(引用計(jì)數(shù)/復(fù)制/標(biāo)記清除/標(biāo)記整理)是內(nèi)存回收的方法論兵多,垃圾回收器就是這些GC算法的落地實(shí)現(xiàn)。到目前為止還沒(méi)有完美的垃圾回收器出現(xiàn)橄仆,更加沒(méi)有萬(wàn)能的收集器剩膘,只是針對(duì)不同場(chǎng)合選用最合適的垃圾收集器。

3. 主要的垃圾收集器

參數(shù) 新生代收集器 新生代算法 老年代收集器 老年代算法
-XX:+UseSerialGC Serial 復(fù)制 SerialOldGC 標(biāo)整
-XX:+UseParNewGC ParNew 復(fù)制 SerialOldGC 標(biāo)整
-XX:+UseParallelGC或-XX:UseParallelOldGC Parallel(Scavenge) 復(fù)制 ParallelOldGC 標(biāo)整
-XX:UseConcMarkSweepGC ParNew 復(fù)制 CMS+Serial Old(SerialOld為CMS出錯(cuò)的后備) 標(biāo)清
-XX:UseG1GC 整體上使用標(biāo)整算法 局部是通過(guò)復(fù)制算法盆顾,不會(huì)產(chǎn)生內(nèi)存碎片

3.1 Serial-串行收集器

為單線程設(shè)計(jì)并且只使用一個(gè)線程進(jìn)行垃圾回收怠褐,會(huì)暫停所有的用戶線程,不適合用在服務(wù)器環(huán)境您宪,運(yùn)行在Client模式下的JVM是個(gè)不錯(cuò)的選擇奈懒,在用戶的桌面應(yīng)用場(chǎng)景中使用串行收集器也是可以接受的。

3.2 Parallel-并行收集器

是Serial的多線程版本宪巨,和Serial共享很多源碼磷杏,多個(gè)垃圾收集線程并行工作,此時(shí)用戶線程是暫停的捏卓,停頓時(shí)間比Serial短极祸,效率更高適合用于科學(xué)計(jì)算/大數(shù)據(jù)處理等弱交互場(chǎng)景。在單核CPU下怠晴,可能并行收集器比串行收集器還慢遥金。

3.3 CMS-標(biāo)記并發(fā)清除-ConcMarkSweep)

用戶線程和垃圾收集線程同時(shí)執(zhí)行(不一定是并行,可能交替執(zhí)行)蒜田,不需要停頓用戶線程稿械,互聯(lián)網(wǎng)公司多用它,適合對(duì)響應(yīng)時(shí)間有要求的場(chǎng)景物邑。(有一段還是會(huì)暫停溜哮,但是比較短),在2020年3月的jdk14中CMS被刪除色解。

3.4 G1

G1垃圾回收器將堆內(nèi)存分成不同的區(qū)域茂嗓,然后并發(fā)的對(duì)其進(jìn)行垃圾的回收。

4.怎么查看服務(wù)器的垃圾回收器科阎?

使用java -XX:+PrintCommandLineFlags -version查看初始配置或者jinfo -flag PrintCommandLineFlags pid查看具體的應(yīng)用程序的參數(shù)述吸。Java8默認(rèn)使用的是Parallel,即并行垃圾回收锣笨。

5. 垃圾收集器的使用

5.1 Serial(新生代)+SerialOld(養(yǎng)老區(qū))

開(kāi)啟配置的參數(shù)-XX:+UseSerialGC蝌矛,整個(gè)過(guò)程都會(huì)停掉用戶線程(STW)。

5.2 ParNew(新生代)+SerialOld(養(yǎng)老區(qū))

新生代采用復(fù)制算法(多個(gè)線程)错英,暫停所有用戶線程(STW)入撒,老年區(qū)采用標(biāo)記整理算法(單個(gè)線程),暫停所有用戶線程(STW)椭岩。 開(kāi)啟配置的參數(shù)-XX:+UseParNewGC茅逮,可以使用-XX:+ParallelGCThreads限制線程數(shù),默認(rèn)開(kāi)啟和CPU核心相同的線程數(shù)判哥,但是ParNewGC這種默認(rèn)搭配的方式已經(jīng)不推薦使用了献雅。

5.3 ParNew(新生代)+CMS(養(yǎng)老區(qū))

使用-XX:+UseConcMarkSweep開(kāi)啟CMS收集器

//CMS-并發(fā)標(biāo)記清除,是一種以獲得最短回收停頓時(shí)間為目標(biāo)的收集器塌计,適合互聯(lián)網(wǎng)網(wǎng)站和B/S的服務(wù)器上挺身,這類(lèi)應(yīng)用注重服務(wù)器的響應(yīng)速度,希望停頓時(shí)間最短
//CMS非常適合堆內(nèi)存大锌仅、CPU核數(shù)多的服務(wù)端應(yīng)用章钾,也是G1出現(xiàn)之前大型應(yīng)用的首選收集器

//開(kāi)啟之后會(huì)默認(rèn)打開(kāi)ParNew,在新生代中使用的收集器(為什么不整合ParallelScavenge?因?yàn)榈讓硬荒芗嫒?
//在垃圾收集階段用戶線程沒(méi)有中斷热芹,所以在CMS運(yùn)行過(guò)程中伍玖,還應(yīng)該保證用戶線程有足夠的內(nèi)存可用。
//不能像其他收集器一樣等到老年區(qū)滿了才回收剿吻,而是應(yīng)該設(shè)定一個(gè)閾值窍箍,便開(kāi)始回收,以確保在垃圾回收的過(guò)程中用戶程序有足夠的空間運(yùn)行丽旅。
//要是CMS運(yùn)行期間預(yù)留的內(nèi)存不夠程序運(yùn)行的需要椰棘,還會(huì)開(kāi)啟SerialOld收集器,作為CMS出錯(cuò)的后備收集器

//CMS的四個(gè)步驟:
//1.初始化標(biāo)記(CMS initial mark)榄笙,會(huì)停掉用戶線程(STE)
//2.并發(fā)標(biāo)記(CMS concurrent mark)邪狞,不會(huì)停掉用戶線程,會(huì)和用戶線程一起進(jìn)行
//3.重新標(biāo)記(CMS remark)茅撞,用來(lái)標(biāo)記出來(lái)之前標(biāo)記了帆卓,但是現(xiàn)在還在使用的一些對(duì)象巨朦,會(huì)停掉用戶線程(STW)
//4.并發(fā)清除(CMS sweep),和用戶線程一起進(jìn)行剑令,清除GCRoots不可達(dá)對(duì)象
CMS的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):并發(fā)收集停頓低
缺點(diǎn):并發(fā)執(zhí)行糊啡,對(duì)CPU壓力大,采用的標(biāo)記清除算法會(huì)產(chǎn)生內(nèi)存碎片
既然CMS使用標(biāo)記壓縮算法會(huì)產(chǎn)生內(nèi)存碎片吁津,那為什么還要使用標(biāo)記壓縮算法而不使用標(biāo)記整理算法棚蓄?因?yàn)?strong>CMS的清理過(guò)程和用戶線程的執(zhí)行是在一起進(jìn)行的,如果你把對(duì)象整理了碍脏,對(duì)象的內(nèi)存位置發(fā)生改變梭依,用戶線程就不能正常執(zhí)行了。標(biāo)整算法適合使用在STW情況下典尾。

5.4 Parallel Scavenge(新生代)+ParallelOld(養(yǎng)老區(qū))

JDK1.8默認(rèn)使用役拴,關(guān)注吞吐量,和ParNew的區(qū)別在于ParallelScavenge有自適應(yīng)調(diào)節(jié)機(jī)制:JVM會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息钾埂,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間(-XX:+MaxGCPauseMills)或最大的吞吐量扎狱。

//關(guān)注吞吐量,和ParNew的區(qū)別在于ParallelScavenge有自適應(yīng)調(diào)節(jié)機(jī)制:JVM會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息勃教,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間(-XX:+MaxGCPauseMills)或最大的吞吐量淤击。
//使用-XX:+UseParallelGC或-XX:+UseParallelOldGC可以互相激活去使用Parallel Scavenge收集器。
//可以指定GC的線程故源,使用-XX:ParallelGCThreads=N去調(diào)整污抬,CPU>8,N=5/8绳军,CPU<8印机,N=實(shí)際個(gè)數(shù)
//ParallelScanvenge和ParallelOld使用的是兩套算法,完全不一樣
//適合在后臺(tái)運(yùn)行而不需要太多交互任務(wù)的任務(wù)门驾,比如科學(xué)計(jì)算射赛、批量處理。也是Stop the world奶是。
//常見(jiàn)在服務(wù)器環(huán)境下使用

5.5 G1(新生代+養(yǎng)老區(qū))

使用參數(shù)-XX:+UseG1GC開(kāi)啟G1收集器楣责,在Young區(qū)和Old區(qū)都能使用,使用G1之后聂沙,Heap只有兩層秆麸,region/Metaspace,以前的Young和Old都包含在region中及汉。
以前的垃圾收集器的特點(diǎn)

//以前的收集器的特點(diǎn):
//1.Young區(qū)和Old區(qū)是各自獨(dú)立并且連續(xù)的內(nèi)存塊
//2.年輕代收集使用Eden+S0+S1進(jìn)行復(fù)制算法
//3.老年代收集必須掃描整個(gè)老年代區(qū)域
//4.都是以盡量少而快速地執(zhí)行GC為設(shè)計(jì)原則

G1的特點(diǎn)

//G1的特點(diǎn):
//像CMS一樣沮趣,可以與應(yīng)用程序線程并發(fā)進(jìn)行
//整理空閑的空間更快
//需要更多時(shí)間來(lái)預(yù)測(cè)GC的停頓時(shí)間
//不希望犧牲大量的吞吐性能
//不需要更大的Java Heap


//1.利用多CPU、多核的硬件優(yōu)勢(shì)坷随,盡量縮短STW房铭。
//2.整體采用標(biāo)記整理算法驻龟,局部采用復(fù)制算法,不會(huì)產(chǎn)生內(nèi)存碎片
//3.G1不再劃分Eden缸匪、S0翁狐、S1,改成一個(gè)個(gè)的region
//4.G1收集器里面整個(gè)內(nèi)存都混合在一起了豪嗽,但是本身小范圍內(nèi)是存在Young和Old的區(qū)分,保留了新生代核老年代豌骏。
//  但他們不再是物理隔離的而是一部分region的集合并且不需要region是連續(xù)的龟梦,也就是說(shuō)依然會(huì)采用不同的GC方式來(lái)處理不同的區(qū)域。
//5.G1也是分代收集器窃躲,但是在整個(gè)內(nèi)存分區(qū)不存在物理上的年輕代和老年代的區(qū)別计贰,也不需要獨(dú)立的To區(qū)來(lái)做復(fù)制的準(zhǔn)備
//  G1只有邏輯上的分代概念,每個(gè)分區(qū)都可能隨G1的運(yùn)行在不同代之間切換
//G1(Garbage-First)收集器是一款面向服務(wù)端應(yīng)用的收集器蒂窒,聚焦于多處理器和大容量的內(nèi)存中躁倒。
//在實(shí)現(xiàn)高吞吐量的同時(shí),盡可能地滿足垃圾暫停時(shí)間的要求

//G1的目的是為了取代CMS的收集器洒琢,它同CMS相比秧秉,在以下方面比較出色:
//1.G1是一個(gè)有整理內(nèi)存過(guò)程的垃圾收集器,不會(huì)產(chǎn)生很多內(nèi)存碎片
//2.G1的Stop the World(STW)更短衰抑,G1在停頓時(shí)間上添加了預(yù)測(cè)機(jī)制象迎,**用戶可以指定停頓的時(shí)間**

//在jdk9中將G1變成默認(rèn)的垃圾回收器以替代CMS

//G1的主要改變是Eden、Survivor和Tenured不再連續(xù)了呛踊,而是被分成了大小一樣的region砾淌。
//每個(gè)region從1M-32M不等,一個(gè)region有可能屬于Eden谭网、Survivor或者是Tenured



//在堆的使用上汪厨,G1并不要求對(duì)象的存儲(chǔ)一定是物理上的連續(xù),只需要邏輯上連續(xù)即可愉择。
//啟動(dòng)時(shí)可以通過(guò)參數(shù)-XX:G1HeapRegionSize=n指定region分區(qū)的大薪俾摇(1-32M,且必須是1<<k)锥涕,默認(rèn)將堆分為2048個(gè)分區(qū)
//也就是說(shuō)最大支持內(nèi)存為32M*2048=64G

G1收集器的過(guò)程

//G1步驟(和CMS類(lèi)似)
//1.初始化標(biāo)記(CMS initial mark)要拂,會(huì)停掉用戶線程
//2.并發(fā)標(biāo)記(CMS concurrent mark),不會(huì)停掉用戶線程站楚,會(huì)和用戶線程一起進(jìn)行
//3.最終標(biāo)記(CMS remark)脱惰,用來(lái)標(biāo)記出來(lái)之前標(biāo)記了,但是現(xiàn)在還在使用的一些對(duì)象窿春,會(huì)停掉用戶線程
//4.篩選回收拉一,和用戶線程一起進(jìn)行采盒,根據(jù)時(shí)間來(lái)進(jìn)行價(jià)值最大化的回收

更多參數(shù):

//-XX:+UseG1GC
//-XX:G1HeapRegionSize=n  //Region區(qū)域大小,1-32M蔚润,且是1<<k
//-XX:MaxGCPauseMills=n   //最大停頓時(shí)間磅氨,JVM盡可能停頓小于這個(gè)時(shí)間
//-XX:InitiatingHeapOccupancyPercent  堆占用多少就啟用GC,默認(rèn)為45(%)
//-XX:ConcGCThreads=n  并發(fā)GC使用的線程數(shù)量
//-XX:G1ReservePercent=n  空閑空間的預(yù)留百分比嫡纠,以降低目標(biāo)空間溢出的風(fēng)險(xiǎn)烦租,默認(rèn)是10(%)

5.6 如何選擇合適的回收器?

//單CPU或者小內(nèi)存除盏,單機(jī)程序
-XX:+UseSerialGC

//多CPU叉橱,需要最大吞吐量,比如后臺(tái)計(jì)算型的應(yīng)用
-XX:+UseParallelGC或-XX:+UseParallelOldGC

//多CPU者蠕,追求最低的停頓窃祝,虛快速響應(yīng),如互聯(lián)網(wǎng)應(yīng)用
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末踱侣,一起剝皮案震驚了整個(gè)濱河市粪小,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抡句,老刑警劉巖探膊,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異待榔,居然都是意外死亡突想,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)究抓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)猾担,“玉大人,你說(shuō)我怎么就攤上這事刺下“筻冢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵橘茉,是天一觀的道長(zhǎng)工腋。 經(jīng)常有香客問(wèn)我,道長(zhǎng)畅卓,這世上最難降的妖魔是什么擅腰? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮翁潘,結(jié)果婚禮上趁冈,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好渗勘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布沐绒。 她就那樣靜靜地躺著,像睡著了一般旺坠。 火紅的嫁衣襯著肌膚如雪乔遮。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天取刃,我揣著相機(jī)與錄音蹋肮,去河邊找鬼。 笑死璧疗,一個(gè)胖子當(dāng)著我的面吹牛坯辩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播病毡,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼濒翻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼屁柏!你這毒婦竟也來(lái)了啦膜?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤淌喻,失蹤者是張志新(化名)和其女友劉穎僧家,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體裸删,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡八拱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涯塔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肌稻。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖匕荸,靈堂內(nèi)的尸體忽然破棺而出爹谭,到底是詐尸還是另有隱情,我是刑警寧澤榛搔,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布诺凡,位于F島的核電站,受9級(jí)特大地震影響践惑,放射性物質(zhì)發(fā)生泄漏腹泌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一尔觉、第九天 我趴在偏房一處隱蔽的房頂上張望凉袱。 院中可真熱鬧,春花似錦侦铜、人聲如沸绑蔫。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)配深。三九已至携添,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間篓叶,已是汗流浹背烈掠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缸托,地道東北人左敌。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像俐镐,于是被迫代替她去往敵國(guó)和親矫限。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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