JVM虛擬機(jī)原理深入解析

JVM內(nèi)存區(qū)域

我們在編寫程序時潘拨,經(jīng)常會遇到OOM(out of Memory)以及內(nèi)存泄漏等問題。為了避免出現(xiàn)這些問題惯裕,我們首先必須對JVM的內(nèi)存劃分有個具體的認(rèn)識秦爆。JVM將內(nèi)存主要劃分為:方法區(qū)、虛擬機(jī)棧宙彪、本地方法棧矩动、堆、程序計數(shù)器释漆。JVM運(yùn)行時數(shù)據(jù)區(qū)如下

image

程序計數(shù)器

程序計數(shù)器是線程私有的區(qū)域悲没,很好理解嘛~,每個線程當(dāng)然得有個計數(shù)器記錄當(dāng)前執(zhí)行到那個指令男图。占用的內(nèi)存空間小示姿,可以把它看成是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。如果線程在執(zhí)行Java方法逊笆,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令地址栈戳;如果執(zhí)行的是Native方法,這個計數(shù)器的值為空(Undefined)难裆。此內(nèi)存區(qū)域是唯一一個在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域子檀。

Java虛擬機(jī)棧

與程序計數(shù)器一樣,Java虛擬機(jī)棧也是線程私有的乃戈。其生命周期與線程相同褂痰。如何理解虛擬機(jī)棧呢?本質(zhì)上來講症虑,就是個棧脐恩。里面存放的元素叫棧幀,棧幀好像很復(fù)雜的樣子侦讨,其實它很簡單驶冒!它里面存放的是一個函數(shù)的上下文苟翻,具體存放的是執(zhí)行的函數(shù)的一些數(shù)據(jù)。執(zhí)行的函數(shù)需要的數(shù)據(jù)無非就是局部變量表(保存函數(shù)內(nèi)部的變量)骗污、操作數(shù)棧(執(zhí)行引擎計算時需要)崇猫,方法出口等等。

執(zhí)行引擎每調(diào)用一個函數(shù)時需忿,就為這個函數(shù)創(chuàng)建一個棧幀诅炉,并加入虛擬機(jī)棧。換個角度理解屋厘,每個函數(shù)從調(diào)用到執(zhí)行結(jié)束涕烧,其實是對應(yīng)一個棧幀的入棧和出棧。

注意這個區(qū)域可能出現(xiàn)的兩種異常:一種是StackOverflowError汗洒,當(dāng)前線程請求的棧深度大于虛擬機(jī)所允許的深度時议纯,會拋出這個異常。制造這種異常很簡單:將一個函數(shù)反復(fù)遞歸自己溢谤,最終會出現(xiàn)棧溢出錯誤(StackOverflowError)瞻凤。另一種異常是OutOfMemoryError異常,當(dāng)虛擬機(jī)検郎保可以動態(tài)擴(kuò)展時(當(dāng)前大部分虛擬機(jī)都可以)阀参,如果無法申請足夠多的內(nèi)存就會拋出OutOfMemoryError,如何制作虛擬機(jī)棧OOM呢瞻坝,參考一下代碼

public void stackLeakByThread(){
    while(true){
        new Thread(){
            public void run(){
                while(true){
                }
            }
        }.start()
    }
}

這段代碼有風(fēng)險蛛壳,可能會導(dǎo)致操作系統(tǒng)假死,請謹(jǐn)慎使用~~~

本地方法棧

本地方法棧與虛擬機(jī)棧所發(fā)揮的作用很相似所刀,他們的區(qū)別在于虛擬機(jī)棧為執(zhí)行Java代碼方法服務(wù)衙荐,而本地方法棧是為Native方法服務(wù)。與虛擬機(jī)棧一樣勉痴,本地方法棧也會拋出StackOverflowError和OutOfMemoryError異常赫模。

Java堆

Java堆可以說是虛擬機(jī)中最大一塊內(nèi)存了。它是所有線程所共享的內(nèi)存區(qū)域蒸矛,幾乎所有的實例對象都是在這塊區(qū)域中存放瀑罗。當(dāng)然,隨著JIT編譯器的發(fā)展雏掠,所有對象在堆上分配漸漸變得不那么“絕對”了斩祭。

Java堆是垃圾收集器管理的主要區(qū)域。由于現(xiàn)在的收集器基本上采用的都是分代收集算法乡话,所有Java堆可以細(xì)分為:新生代和老年代摧玫。在細(xì)致分就是把新生代分為:Eden空間、From Survivor空間、To Survivor空間诬像。當(dāng)堆無法再擴(kuò)展時屋群,會拋出OutOfMemoryError異常。

方法區(qū)

方法區(qū)存放的是類信息坏挠、常量芍躏、靜態(tài)變量等。方法區(qū)是各個線程共享區(qū)域降狠,很容易理解对竣,我們在寫Java代碼時,每個線程度可以訪問同一個類的靜態(tài)變量對象榜配。由于使用反射機(jī)制的原因否纬,虛擬機(jī)很難推測那個類信息不再使用,因此這塊區(qū)域的回收很難蛋褥。另外临燃,對這塊區(qū)域主要是針對常量池回收,值得注意的是JDK1.7已經(jīng)把常量池轉(zhuǎn)移到堆里面了壁拉。同樣谬俄,當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時柏靶,會拋出OutOfMemoryError弃理。
制造方法區(qū)內(nèi)存溢出,注意屎蜓,必須在JDK1.6及之前版本才會導(dǎo)致方法區(qū)溢出痘昌,原因后面解釋,執(zhí)行之前,可以把虛擬機(jī)的參數(shù)-XXpermSize和-XX:MaxPermSize限制方法區(qū)大小

List<String> list =new ArrayList<String>();
int i =0;
while(true){
    list.add(String.valueOf(i).intern());
} 

運(yùn)行后會拋出java.lang.OutOfMemoryError:PermGen space異常炬转。
解釋一下辆苔,String的intern()函數(shù)作用是如果當(dāng)前的字符串在常量池中不存在,則放入到常量池中扼劈。上面的代碼不斷將字符串添加到常量池驻啤,最終肯定會導(dǎo)致內(nèi)存不足,拋出方法區(qū)的OOM荐吵。

下面解釋一下骑冗,為什么必須將上面的代碼在JDK1.6之前運(yùn)行。我們前面提到先煎,JDK1.7后贼涩,把常量池放入到堆空間中,這導(dǎo)致intern()函數(shù)的功能不同薯蝎,具體怎么個不同法遥倦,且看看下面代碼:

String str1 =new StringBuilder("ad").append("dc").toString();
System.out.println(str1.intern()==str1);

String str2=new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern()==str2);

這段代碼在JDK1.6和JDK1.7運(yùn)行的結(jié)果不同。JDK1.6結(jié)果是:false,false 占锯,JDK1.7結(jié)果是true, false袒哥。原因是:JDK1.6中缩筛,intern()方法會吧首次遇到的字符串實例復(fù)制到常量池中,返回的也是常量池中的字符串的引用堡称,而StringBuilder創(chuàng)建的字符串實例是在堆上面歪脏,所以必然不是同一個引用,返回false粮呢。在JDK1.7中婿失,intern不再復(fù)制實例,常量池中只保存首次出現(xiàn)的實例的引用啄寡,因此intern()返回的引用和由StringBuilder創(chuàng)建的字符串實例是同一個豪硅。為什么對str2比較返回的是false呢?這是因為挺物,JVM中內(nèi)部在加載類的時候懒浮,就已經(jīng)有"java"這個字符串,不符合“首次出現(xiàn)”的原則识藤,因此返回false砚著。

垃圾回收(GC)

JVM的垃圾回收機(jī)制中,判斷一個對象是否死亡痴昧,并不是根據(jù)是否還有對象對其有引用稽穆,而是通過可達(dá)性分析。對象之間的引用可以抽象成樹形結(jié)構(gòu)赶撰,通過樹根(GC Roots)作為起點(diǎn)舌镶,從這些樹根往下搜索,搜索走過的鏈稱為引用鏈豪娜,當(dāng)一個對象到GC Roots沒有任何引用鏈相連時餐胀,則證明這個對象是不可用的,該對象會被判定為可回收的對象瘤载。

那么那些對象可作為GC Roots呢否灾?主要有以下幾種:

1.虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象。
2.方法區(qū)中類靜態(tài)屬性引用的對象鸣奔。
3.方法區(qū)中常量引用的對象
4.本地方法棧中JNI(即一般說的Native方法)引用的對象墨技。

另外,Java還提供了軟引用和弱引用溃蔫,這兩個引用是可以隨時被虛擬機(jī)回收的對象健提,我們將一些比較占內(nèi)存但是又可能后面用的對象,比如Bitmap對象伟叛,可以聲明為軟引用貨弱引用私痹。但是注意一點(diǎn),每次使用這個對象時候,需要顯示判斷一下是否為null紊遵,以免出錯账千。

三種常見的垃圾收集算法

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

首先,通過可達(dá)性分析將可回收的對象進(jìn)行標(biāo)記暗膜,標(biāo)記后再統(tǒng)一回收所有被標(biāo)記的對象匀奏,標(biāo)記過程其實就是可達(dá)性分析的過程。這種方法有2個不足點(diǎn):效率問題学搜,標(biāo)記和清除兩個過程的效率都不高娃善;另一個是空間問題,標(biāo)記清除之后會產(chǎn)生大量的不連續(xù)的內(nèi)存碎片瑞佩。

2.復(fù)制算法

為了解決效率問題聚磺,復(fù)制算法是將內(nèi)存分為大小相同的兩塊,每次只使用其中一塊炬丸。當(dāng)這塊內(nèi)存用完了瘫寝,就將還存活的對象復(fù)制到另一塊內(nèi)存上面。然后再把已經(jīng)使用過的內(nèi)存一次清理掉稠炬。這使得每次只對半個區(qū)域進(jìn)行垃圾回收焕阿,內(nèi)存分配時也不用考慮內(nèi)存碎片情況。

但是首启,這代價實在是讓人無法接受暮屡,需要犧牲一般的內(nèi)存空間。研究發(fā)現(xiàn)闽坡,大部分對象都是“朝生夕死”栽惶,所以不需要安裝1:1比例劃分內(nèi)存空間愁溜,而是將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間疾嗅,每次使用Eden空間和一塊Survivor空間,默認(rèn)比例為Eden:Survivor=8:1.新生代區(qū)域就是這么劃分冕象,每次實例在Eden和一塊Survivor中分配代承,回收時,將存活的對象復(fù)制到剩下的另一塊Survivor渐扮。這樣只有10%的內(nèi)存會被浪費(fèi)论悴,但是帶來的效率卻很高。當(dāng)剩下的Survivor內(nèi)存不足時墓律,可以去老年代內(nèi)存進(jìn)行分配擔(dān)保膀估。如何理解分配擔(dān)保呢,其實就是耻讽,內(nèi)存不足時察纯,去老年代內(nèi)存空間分配,然后等新生代內(nèi)存緩過來了之后,把內(nèi)存歸還給老年代饼记,保持新生代中的Eden:Survivor=8:1.另外香伴,兩個Survivor分別有自己的名稱:From Survivor、To Survivor具则。二者身份經(jīng)常調(diào)換即纲,即有時這塊內(nèi)存與Eden一起參與分配,有時是另一塊博肋。因為他們之間經(jīng)常相互復(fù)制低斋。

3.標(biāo)記-整理算法

標(biāo)記整理算法很簡單,就是先標(biāo)記需要回收的對象匪凡,然后把所有存活的對象移動到內(nèi)存的一端拔稳。這樣的好處是避免了內(nèi)存碎片。

類加載機(jī)制

類從被加載到虛擬機(jī)內(nèi)存開始锹雏,到卸載出內(nèi)存為止巴比,整個生命周期包括:加載、驗證、準(zhǔn)備、解析泥彤、初始化幻捏、使用和卸載七個階段。

其中加載丈氓、驗證、準(zhǔn)備、初始化奸远、和卸載這5個階段的順序是確定的。而解析階段不一定:它在某些情況下可以在初始化階段之后再開始讽挟,這是為了支持Java的運(yùn)行時綁定懒叛。

關(guān)于初始化:JVM規(guī)范明確規(guī)定,有且只有5中情況必須執(zhí)行對類的初始化(加載耽梅、驗證薛窥、準(zhǔn)備自然再此之前要發(fā)生):
1.遇到new、getstatic眼姐、putstatic诅迷、invokestatic,如果類沒有初始化众旗,則必須初始化罢杉,這幾條指令分別是指:new新對象、讀取靜態(tài)變量贡歧、設(shè)置靜態(tài)變量滩租,調(diào)用靜態(tài)函數(shù)拱镐。
2.使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用時,如果類沒初始化持际,則需要初始化
3.當(dāng)初始化一個類時沃琅,如果發(fā)現(xiàn)父類沒有初始化,則需要先觸發(fā)父類初始化蜘欲。
4.當(dāng)虛擬機(jī)啟動時益眉,用戶需要制定一個執(zhí)行的主類(包含main函數(shù)的類),虛擬機(jī)會先初始化這個類姥份。
5.但是用JDK1.7啟的動態(tài)語言支持時郭脂,如果一個MethodHandle實例最后解析的結(jié)果是REF_getStatic、REF_putStatic澈歉、Ref_invokeStatic的方法句柄時展鸡,并且這個方法句柄所對應(yīng)的類沒有進(jìn)行初始化,則要先觸發(fā)其初始化埃难。

另外要注意的是:通過子類來引用父類的靜態(tài)字段莹弊,不會導(dǎo)致子類初始化:

public class SuperClass{
    public static int value=123;
    static{
        System.out.printLn("SuperClass init!");
    }
}

public class SubClass extends SuperClass{
    static{
        System.out.println("SubClass init!");
    }

}

public class Test{

    public static void main(String[] args){
        System.out.println(SubClass.value);
    }
}

最后只會打印:SuperClass init!
對應(yīng)靜態(tài)變量涡尘,只有直接定義這個字段的類才會被初始化忍弛,因此通過子類類引用父類中定義的靜態(tài)變量只會觸發(fā)父類初始化而不會觸發(fā)子類初始化。

通過數(shù)組定義來引用類考抄,不會觸發(fā)此類的初始化:

public class Test{

    public static void main(String[] args){
        SuperClass[] sca=new SuperClass[10];
    }
}

常量會在編譯階段存入調(diào)用者的常量池细疚,本質(zhì)上并沒有直接引用到定義常量的類,因此不會觸發(fā)定義常量的類初始化川梅,示例代碼如下:

public class ConstClass{
    public static final String HELLO_WORLD="hello world";
    static {
        System.out.println("ConstClass init!");
    }

}

public class Test{
    public static void main(String[] args){

        System.out.print(ConstClass.HELLO_WORLD);
    }

}

上面代碼不會出現(xiàn)ConstClass init!

加載

加載過程主要做以下3件事

1.通過一個類的全限定名稱來獲取此類的二進(jìn)制流
2.將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu)
3.在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)訪問入口疯兼。

驗證

這個階段主要是為了確保Class文件字節(jié)流中包含信息符合當(dāng)前虛擬機(jī)的要求,并且不會出現(xiàn)危害虛擬機(jī)自身的安全

準(zhǔn)備

準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段贫途,這些變量所使用的內(nèi)存都在方法區(qū)中分配吧彪。首先,這個時候分配內(nèi)存僅僅包括類變量(被static修飾的變量)潮饱,而不包括實例變量来氧。實例變量會在對象實例化時隨著對象一起分配在java堆中。其次這里所說的初始值“通常情況下”是數(shù)據(jù)類型的零值香拉,假設(shè)一個類變量定義為

public static int value=123;

那變量value在準(zhǔn)備階段后的初始值是0,而不是123中狂,因為還沒有執(zhí)行任何Java方法凫碌,而把value賦值為123是在程序編譯后,存放在類構(gòu)造函數(shù)<clinit>()方法中胃榕。

解析

解析階段是把虛擬機(jī)中常量池的符號引用替換為直接引用的過程盛险。

初始化

類初始化時類加載的最后一步瞄摊,前面類加載過程中,除了加載階段用戶可以通過自定義類加載器參與以外苦掘,其余動作都是虛擬機(jī)主導(dǎo)和控制换帜。到了初始化階段,才是真正執(zhí)行類中定義Java程序代碼鹤啡。

準(zhǔn)備階段中惯驼,變量已經(jīng)賦過一次系統(tǒng)要求的初始值,而在初始化階段递瑰,根據(jù)程序員通過程序制定的主觀計劃初始化類變量祟牲。初始化過程其實是執(zhí)行類構(gòu)造器<clinit>()方法的過程。

<clinit>()方法是由編譯器自動收集類中所有類變量的賦值動作和靜態(tài)語句塊中的語句合并產(chǎn)生的抖部。收集的順序是按照語句在源文件中出現(xiàn)的順序说贝。靜態(tài)語句塊中只能訪問定義在靜態(tài)語句塊之前的變量,定義在它之后的變量可以賦值慎颗,但不能訪問乡恕。如下所示:

public class Test{
    static{
        i=0;//給變量賦值,可以通過編譯
        System.out.print(i);//這句編譯器會提示:“非法向前引用”
    }
    static int i=1;

}

<clinit>()方法與類構(gòu)造函數(shù)(或者說實例構(gòu)造器<init>())不同俯萎,他不需要顯式地調(diào)用父類構(gòu)造器几颜,虛擬機(jī)會保證子類的<clinit>()方法執(zhí)行之前,父類的<clinit>()已經(jīng)執(zhí)行完畢讯屈。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蛋哭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子涮母,更是在濱河造成了極大的恐慌谆趾,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叛本,死亡現(xiàn)場離奇詭異沪蓬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)来候,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門跷叉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人营搅,你說我怎么就攤上這事云挟。” “怎么了转质?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵园欣,是天一觀的道長。 經(jīng)常有香客問我休蟹,道長沸枯,這世上最難降的妖魔是什么日矫? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮绑榴,結(jié)果婚禮上哪轿,老公的妹妹穿的比我還像新娘。我一直安慰自己翔怎,他們只是感情好窃诉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著姓惑,像睡著了一般褐奴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上于毙,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天敦冬,我揣著相機(jī)與錄音,去河邊找鬼唯沮。 笑死脖旱,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的介蛉。 我是一名探鬼主播萌庆,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼币旧!你這毒婦竟也來了践险?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤吹菱,失蹤者是張志新(化名)和其女友劉穎巍虫,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鳍刷,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡占遥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了输瓜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓦胎。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖尤揣,靈堂內(nèi)的尸體忽然破棺而出搔啊,到底是詐尸還是另有隱情,我是刑警寧澤芹缔,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布坯癣,位于F島的核電站,受9級特大地震影響最欠,放射性物質(zhì)發(fā)生泄漏示罗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一芝硬、第九天 我趴在偏房一處隱蔽的房頂上張望蚜点。 院中可真熱鬧,春花似錦拌阴、人聲如沸绍绘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陪拘。三九已至,卻和暖如春纤壁,著一層夾襖步出監(jiān)牢的瞬間左刽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工酌媒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留欠痴,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓秒咨,卻偏偏與公主長得像喇辽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子雨席,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

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

  • 本篇文章承接上文《內(nèi)存管理與運(yùn)行時數(shù)據(jù)區(qū)剖析》,如果想了解更多JVM運(yùn)行時數(shù)據(jù)區(qū)相關(guān)或者內(nèi)存管理相關(guān)菩咨,請查看上文。...
    遙風(fēng)hugoca閱讀 571評論 0 0
  • 久違的晴天陡厘,家長會抽米。 家長大會開好到教室時,離放學(xué)已經(jīng)沒多少時間了雏亚。班主任說已經(jīng)安排了三個家長分享經(jīng)驗缨硝。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,523評論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友罢低。感恩相遇查辩!感恩不離不棄。 中午開了第一次的黨會网持,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,564評論 0 11
  • 可愛進(jìn)取宜岛,孤獨(dú)成精。努力飛翔功舀,天堂翱翔萍倡。戰(zhàn)爭美好,孤獨(dú)進(jìn)取辟汰。膽大飛翔列敲,成就輝煌阱佛。努力進(jìn)取,遙望戴而,和諧家園凑术。可愛游走...
    趙原野閱讀 2,727評論 1 1
  • 在妖界我有個名頭叫胡百曉所意,無論是何事淮逊,只要找到胡百曉即可有解決的辦法。因為是只狐貍大家以訛傳訛叫我“傾城百曉”扶踊,...
    貓九0110閱讀 3,261評論 7 3