JVM學習筆記——Class類文件解讀

簡述

Java源代碼通過編譯生成.class文件字節(jié)碼后再被JVM解釋轉化為目標機器代碼荤牍,從而實現(xiàn)一次編寫到處挂洛,到處運行("Write Once,Run Anywhere")。字節(jié)碼與平臺無關胆萧,而且并不是只有Java語言編譯為字節(jié)碼文件在虛擬機上運行纱意。

類文件的結構

Class文件是一組以8位字節(jié)為基礎單位的二進制流,各個數(shù)據項目嚴格按照順序緊湊地排列在Class文件中竿屹。Class文件只有兩種數(shù)據類型:無符號數(shù)和表报强。

無符號數(shù)屬于基本的數(shù)據類型灸姊,有u1, u2, u4, u8拱燃,分別代表1個字節(jié)、2個字節(jié)力惯、4個字節(jié)和8個字節(jié)的無符號數(shù)

整個Class文件就是一張表碗誉,由以下數(shù)據項構成:

類型 名稱 數(shù)量 u4 magic(魔數(shù)) 1 u2 minor_version(次版本號) 1 u2 major_version(主版本號) 1 u2 constant_pool_count(常量池容量) 1 cp_info constant_pool(常量池) constant_pool_count-1 u2 access_flags(訪問標志) 1 u2 this_class(類索引) 1 u2 super_class(父類索引) 1 u2 interfaces_count(接口容量) 1 u2 interfaces(接口) interfaces_count u2 fields_count(字段容量) 1 field_info fields(字段) fields_count u2 methods_count(方法容量) 1 mehtod_info methods(方法) method_count u2 attributes_count(屬性容量) 1 attribute attributes(屬性) attributes_count 小試牛刀

寫個簡單實體類,javac編譯后父晶,查看其字節(jié)碼

源碼

public class Person { private int age; public int getAge(){ return age; } public static synchronized void work() { System.out.println("工作"); } public static void main(String[] args) { }}

十六進制Class文件

根據上述數(shù)據項表格我們按順序拆分

魔數(shù)

魔數(shù)站每個Class文件的頭4個字節(jié)哮缺,其作用未確定確定這個文件是否為一個能被虛擬機接受的Class文件?示例中CA FE BA BE為魔數(shù)

版本號

魔數(shù)后面緊跟著版本號

00 00——次版本號

00 34——主版本號

根據如下:

十進制版本號 主版本 jdk1.8 52 jdk1.7 51 jdk1.6 50 jdk1.5 49 jdk1.4 48 jdk1.3 47 jdk1.2 46 jdk1.1 45 十六進制0034,對應十進制52甲喝,對應jdk1.8版本

常量池

常量池可以理解為Class文件之中的資源倉庫尝苇,它是Class文件結構中與其他項目關聯(lián)最多的數(shù)據類型。常量池中主要存放兩大類常量:字面量和符號引用.

字面量——接近于Java中的常量概念埠胖,eg:final修飾糠溜、文本字符串

符號引用——編譯原理概念:類和接口的全限定名、字段的名稱和描述符直撤、方法的名稱和描述符

常量池中的每一項常量都是一個表非竿,每種常量都有自己的結構,14種常量含義:

類型 標志 描述 CONSTANT_utf8_info 1 utf-8編碼的字符串 CONSTANT_Integer_info 3 整型字面量 CONSTANT_Float_info 4 浮點型字面量 CONSTANT_Long_info 5 長整型字面量 CONSTANT_Double_info 6 雙精度浮點型字面量 CONSTANT_Class_info 7 類或者接口的符號引用 CONSTANT_String_info 8 字符串型字面量 CONSTANT_Fieldref_info 9 字段的符號引用 CONSTANT_Methodref_info 10 類中方法的符號引用 CONSTANT_InterfaceMethoderf_info 11 接口中方法的符號引用 CONSTANT_NameAndType_info 12 字段或方法的部分符號引用 CONSTANT_MethodHandle_info 15 表示方法句柄 CONSTANT_MethodType_info 16 標識方法類型 CONSTANT_InvokeDynamic_info 18 表示一個動態(tài)方法調用點 0×0029轉十進制為41谋竖,代表常量池中有40項常量(容量計數(shù)是從1而不是0開始红柱。第0項常量空出來是表達“不引用任何一個常量池項目”)

0A即十進制10承匣,對應表中CONSTANT_Methodref_info,其結構如下:

類型 名稱 描述 u1 tag 值為10 u2 index 指向聲明方法的類描述符CONSTANT_Class_info的索引項 u2 index 指向名稱及類型描述符CONSTANT_NameAndType_info的索引項 0×0007為常量池中第7項CONSTANT_Class_info锤悄,0×001A為第26項CONSTANT_NameAndType_info韧骗。按照《深入理解Java虛擬機》第二版,172頁中表6-6順序解析得:加群617434785里面有文中整理的知識點

00 29 //constant_pool_count(常量池容量) #1零聚、0A 0007 001A //CONSTANT_Methodref_info宽闲,#7,#26 #2握牧、09 0006 001B //CONSTANT_Fieldref_info容诬,#6,#27 #3沿腰、09 001C 001D //CONSTANT_Fieldref_info览徒,#28,#29 #4颂龙、08 001E //CONSTANT_String_info习蓬,#30 #5、0A 001F 0020 //CONSTANT_Methodref_info措嵌,#31躲叼,#32 #6、07 0021 //CONSTANT_Class_info企巢,#33 #7枫慷、07 0022 //CONSTANT_Class_info,#34 #8浪规、01 0003 61 67 65 //CONSTANT_Utf8_info或听,3個字節(jié),age #9笋婿、01 0001 49 //CONSTANT_Utf8_info誉裆,1個字節(jié),I#10缸濒、01 0006 3C 69 6E 69 74 3E //CONSTANT_Utf8_info足丢,6個字節(jié),#11庇配、01 0003 28 29 56 //CONSTANT_Utf8_info斩跌,3個字節(jié),()V#12讨永、01 0004 43 6F 64 65 //CONSTANT_Utf8_info滔驶,4個字節(jié),Code#13卿闹、01 000F 4C 69 6E 65 4E 75 6D 62 //CONSTANT_Utf8_info揭糕,15個字節(jié)萝快,LineNumberTable 65 72 54 61 62 6C 65 #14、01 0012 4C 6F 63 61 6C 56 61 72 //CONSTANT_Utf8_info著角,18個字節(jié)揪漩,LocalVariableTable 69 61 62 6C 65 54 61 62 6C 65#15、01 0004 74 68 69 73 //CONSTANT_Utf8_info吏口,4個字節(jié)奄容,this#16、01 0008 4C 50 65 72 73 6F 6E 3B //CONSTANT_Utf8_info产徊,8個字節(jié)昂勒,LPerson;#17、01 0006 67 65 74 41 67 65 //CONSTANT_Utf8_info舟铜,6個字節(jié)戈盈,getAge#18、01 0003 28 29 49 //CONSTANT_Utf8_info谆刨,3個字節(jié)塘娶,()I#19、01 0004 77 6F 72 6B //CONSTANT_Utf8_info痊夭,4個字節(jié)刁岸,work#20、01 0004 6D 61 69 6E //CONSTANT_Utf8_info她我,4個字節(jié)虹曙,main#21、01 0016 28 5B 4C 6A 61 76 61 2F //CONSTANT_Utf8_info鸦难,22個字節(jié)根吁,([Ljava/lang/String;)V 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56#22、01 0004 61 72 67 73 //CONSTANT_Utf8_info合蔽,4個字節(jié),args#23介返、01 0013 5B 4C 6A 61 76 61 2F 6C //CONSTANT_Utf8_info拴事,19個字節(jié),[Ljava/lang/String; 61 6E 67 2F 53 74 72 69 6E 67 3B#24圣蝎、01 000A 53 6F 75 72 63 65 46 69 //CONSTANT_Utf8_info刃宵,10個字節(jié), SourceFile 6C 65#25徘公、01 000B 50 65 72 73 6F 6E 2E 6A //CONSTANT_Utf8_info牲证,11個字節(jié), Person.java 61 76 61#26关面、0C 000A 000B //CONSTANT_NameAndType_info坦袍,#10十厢,#11#27、0C 0008 0009 //CONSTANT_NameAndType_info捂齐,#8蛮放,#9#28、07 0023 //CONSTANT_Class_info奠宜,#35#29包颁、0C 0024 0025 //CONSTANT_NameAndType_info,#36压真,#37#30娩嚼、01 0006 E5 B7 A5 E4 BD 9C //CONSTANT_Utf8_info,6個字節(jié)滴肿,工作#31待锈、07 0026 //CONSTANT_Class_info,#38#32嘴高、0C 0027 0028 //CONSTANT_NameAndType_info竿音,#39,#40#33拴驮、01 0006 50 65 72 73 6F 6E //CONSTANT_Utf8_info春瞬,6個字節(jié),Person#34套啤、01 0010 6A 61 76 61 2F 6C 61 6E //CONSTANT_Utf8_info宽气,16個字節(jié),java/lang/Object 67 2F 4F 62 6A 65 63 74#35潜沦、01 0010 6A 61 76 61 2F 6C 61 6E //CONSTANT_Utf8_info萄涯,16個字節(jié), java/lang/System 67 2F 53 79 73 74 65 6D#36唆鸡、01 0003 6F 75 74 //CONSTANT_Utf8_info涝影,3個字節(jié),out#37争占、01 0015 4C 6A 61 76 61 2F 69 6F //CONSTANT_Utf8_info燃逻,21個字節(jié), Ljava/io/PrintStream; 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B#38臂痕、01 0013 6A 61 76 2F 69 6F 2F 50 //CONSTANT_Utf8_info伯襟,19個字節(jié),java/io/PrintStream 72 69 6E 74 53 74 72 65 61 6D#39握童、01 0007 70 72 69 6E 74 6C 6E //CONSTANT_Utf8_info姆怪,7個字節(jié),println#40、01 0015 28 4C 6A 61 76 61 2F 6C //CONSTANT_Utf8_info稽揭,21個字節(jié)俺附, (Ljava/lang/String;)V 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56

也可以java -verbose分析Class文件字節(jié)碼,得到結果:

Constant pool: #1 = Methodref #7.#26 // java/lang/Object."":()V #2 = Fieldref #6.#27 // Person.age:I #3 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream; #4 = String #30 // 工作 #5 = Methodref #31.#32 // java/io/PrintStream.println:(Ljava/lang/String;)V #6 = Class #33 // Person #7 = Class #34 // java/lang/Object #8 = Utf8 age #9 = Utf8 I #10 = Utf8 #11 = Utf8 ()V #12 = Utf8 Code #13 = Utf8 LineNumberTable #14 = Utf8 LocalVariableTable #15 = Utf8 this #16 = Utf8 LPerson; #17 = Utf8 getAge #18 = Utf8 ()I #19 = Utf8 work #20 = Utf8 main #21 = Utf8 ([Ljava/lang/String;)V #22 = Utf8 args #23 = Utf8 [Ljava/lang/String; #24 = Utf8 SourceFile #25 = Utf8 Person.java #26 = NameAndType #10:#11 // "":()V #27 = NameAndType #8:#9 // age:I #28 = Class #35 // java/lang/System #29 = NameAndType #36:#37 // out:Ljava/io/PrintStream; #30 = Utf8 工作 #31 = Class #38 // java/io/PrintStream #32 = NameAndType #39:#40 // println:(Ljava/lang/String;)V #33 = Utf8 Person #34 = Utf8 java/lang/Object #35 = Utf8 java/lang/System #36 = Utf8 out #37 = Utf8 Ljava/io/PrintStream; #38 = Utf8 java/io/PrintStream #39 = Utf8 println #40 = Utf8 (Ljava/lang/String;)V

訪問標志

在常量池之后緊接著兩個字節(jié)代表訪問標志淀衣,用于識別一些類或者接口層次的訪問信息

具體的標志位和含義如下:

名稱 標志值 含義 ACC_PUBLIC 0×0001 是否為public ACC_FINAL 0x0010 是否為final ACC_SUPER 0x0020 JDK 1.0.2之后編譯出來的類這個標志都為真 ACC_INTERFACE 0x0200 是否為一個接口 ACC_ABSTRACT 0x0400 是否為abstract類型 ACC_SUPER 0x0020 JDK 1.0.2之后編譯出來的類這個標志都為真 ACC_SYNTHETIC 0x1000 標識這個類并非由用戶代碼產生 ACC_ANNOTATION 0x2000 是否是注解 ACC_ENUM 0x4000 是否是枚舉 沒有使用到的標志位要求一律為0昙读,本例access_flags的值為:ACC_PUBLIC | ACC_SUPER = 0x0021

類索引、父類索引與接口索引

類索引膨桥、父類索引與接口索引(指向常量池)都是u2類型的數(shù)據蛮浑,除了java.lang.Object 之外所有的Java類都有父類,沒有實現(xiàn)接口計數(shù)器為0只嚣,本例:

0006 //this_class Person 0007 //super_class java/lang/Object 0000 //沒有實現(xiàn)結構故0

字段表集合

字段表用于描述類和接口中聲明的變量沮稚。字段包括類級變量和實例級變量,但是不包括方法中的變量册舞。字段信息:字段的作用域蕴掏,public/private/protected 實例變量還是類變量,static 可變性调鲸,final 并發(fā)可見性盛杰, volatile 可否被序列化, transient藐石,字段數(shù)據類型(基本類型即供,對象,數(shù)組)于微,字段名稱

字段表結構:

類型 名稱 數(shù)量 u2 access_flags 1 u2 name_index 1 u2 descriptor_index 1 u2 attributes_count 1 attribute_info attributes attributes_count

對于本例:

0001 //fields_count 字段容量即1個字段 0002 //訪問標志 private 0008 //常量池第8項逗嫡,即age 0009 //字段描述符,常量池第9項株依,即I 0000 //attribute_count

方法表集合

Class文件存儲格式中對方法的描述與對字段的描述幾乎采用完全一致的方式驱证。

方法表結構:

類型 名稱 數(shù)量 u2 access_flags 1 u2 name_index 1 u2 descriptor_index 1 u2 attributes_count 1 attribute_info attributes attributes_count

對于本例:

0004 //方法容量,即4個方法:實例構造器恋腕、getAge()抹锄、work以及main方法 0001 //方法訪問標志,public 000A //常量池第10項吗坚, 000B //方法描述常量池第11個祈远,()V,沒返回值 0001 //attribute_count 000C //常量池第12項商源,Code屬性表,存放方法里的Java代碼 0000002F //屬性表長度 47 ... 47個字節(jié)后0001 //方法訪問標志谋减,public0011 //常量池第17項牡彻,getAge0012 //方法描述常量池第18個,()I 返回int型0001 //attribute_count000C //常量池第12項,Code屬性表庄吼,存放方法里的Java代碼0000002F //屬性表長度 47... 47個字節(jié)后0029 // ACC_PUBLIC缎除,ACC_STATIC,ACC_SYNCHRONIZED 0×0001|0×0008|0×00200013 //常量池第19項总寻,work000B //方法描述常量池第11個器罐,()V,沒返回值0001 //attribute_count000C //常量池第12項渐行,Code屬性表轰坊,存放方法里的Java代碼00000025 //屬性表長度 37...37個字節(jié)后0009 //ACC_PUBLIC, ACC_STATIC 0×0001|0×00080014 //常量池第20項,main0015 //方法描述常量池第21項祟印,([Ljava/lang/String;)V String數(shù)組形參肴沫,無返回類型方法0001 //attribute_count000C //常量池第12項剥啤,Code屬性表千元,存放方法里的Java代碼0000002B //屬性表長度 43

屬性表集合

對于本例:

構造方法:

000C //常量池第12項,Code屬性 0000002F //Code屬性表長度 47 0001 //max_stack 操作數(shù)棧深度最大值 1 0001 //max_locals 局部變量存儲 00000005 //code_length 字節(jié)碼長度 2A B7 00 01 B1 //字節(jié)碼指令 0000 //exception_table_length 0002 //attributes_count 2個屬性 000D //常量池第13項卓囚, LineNumberTable屬性 00000006 //LineNumberTable屬性表長度 0001 //line_number_table_length 0000 //start_pc 字節(jié)碼行號 0001 //line_number Java源碼行號 000E //常量池第14項套鹅,LocalVariableTable屬性 0000000C //attribute_length 0001 //local_variable_table_length 0000 //start_pc 這個局部變量的生命周期開始的字節(jié)碼偏移量 0005 //局部變量作用范圍覆蓋的長度 000F //name_index 局部變量名稱 常量池第15項站蝠,this 0010 //descriptor_index 局部變量描述 常量池第16項,LPerson; 0000 //這個局部變量在棧幀局部變量表中Slot的位置

getAge方法

000C //常量池第12項卓鹿,Code屬性 0000002F //attribute_length 0001 //max_stack 操作數(shù)棧深度最大值 1 0001 //max_locals 局部變量存儲 00000005 //code_length 字節(jié)碼長度 2A B4 00 02 AC //字節(jié)碼指令 0000 //exception_table_length 0002 //attributes_count 2個屬性 000D //常量池第13項菱魔, LineNumberTable屬性 00000006 //LineNumberTable屬性表長度 0001 //line_number_table_length 0000 //start_pc 字節(jié)碼行號 0006 //line_number Java源碼行號 000E //常量池第14項,LocalVariableTable屬性 0000000C //attribute_length 0001 //local_variable_table_length 0000 //start_pc 這個局部變量的生命周期開始的字節(jié)碼偏移量 0005 //局部變量作用范圍覆蓋的長度 000F //name_index 局部變量名稱 常量池第15項减牺,this 0010 //descriptor_index 局部變量描述 常量池第16項豌习,LPerson; 0000 //這個局部變量在棧幀局部變量表中Slot的位置

work方法

000C //常量池第12項,Code屬性 00000025 //attribute_length 0002 //max_stack 操作數(shù)棧深度最大值 2 0000 //max_locals 局部變量存儲 00000009 //code_length 字節(jié)碼長度 B2 00 03 12 04 B6 00 05 B1 //字節(jié)碼指令 0000 //exception_table_length 0001 //attributes_count 1個屬性 000D //常量池第13項拔疚, LineNumberTable屬性 0000000A //LineNumberTable屬性表長度 0002 //line_number_table_length 2個line_number_info 0000 //start_pc 字節(jié)碼行號 000A //line_number Java源碼行號 0008 //start_pc 字節(jié)碼行號 000B //line_number Java源碼行號

main方法

000C //常量池第12項肥隆,Code屬性 0000002B //attribute_length 0000 //max_stack 操作數(shù)棧深度最大值 0 0001 //max_locals 局部變量存儲 00000001 //code_length 字節(jié)碼長度 B1 //字節(jié)碼指令 0000 //exception_table_length 0002 //attributes_count 2個屬性 000D //常量池第13項, LineNumberTable屬性 00000006 //LineNumberTable屬性表長度 0001 //line_number_table_length 1個line_number_info 0000 //start_pc 字節(jié)碼行號 000F //line_number Java源碼行號 000E //常量池第14項稚失,LocalVariableTable屬性 0000000C //attribute_length 0001 //local_variable_table_length 0000 //start_pc 這個局部變量的生命周期開始的字節(jié)碼偏移量 0001 //局部變量作用范圍覆蓋的長度 0016 //name_index 局部變量名稱 常量池第22項栋艳,args 0017 //descriptor_index 局部變量描述 常量池第23項,[Ljava/lang/String; 0000 //這個局部變量在棧幀局部變量表中Slot的位置

總結

本篇做了一個小小的嘗試句各,按照數(shù)據項表格一一解析吸占,感興趣的同學可以讀下《深入理解Java虛擬機》這本圣書。

如果想學習Java工程化凿宾、高性能及分布式矾屯、深入淺出。微服務初厚、Spring件蚕,MyBatis,Netty源碼分析的朋友可以加我的Java進階群:617434785,群里有阿里大牛直播講解技術排作,以及Java大型互聯(lián)網技術的視頻免費分享給大家牵啦。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市妄痪,隨后出現(xiàn)的幾起案子哈雏,更是在濱河造成了極大的恐慌,老刑警劉巖衫生,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裳瘪,死亡現(xiàn)場離奇詭異,居然都是意外死亡障簿,警方通過查閱死者的電腦和手機盹愚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來站故,“玉大人皆怕,你說我怎么就攤上這事∥髀ǎ” “怎么了愈腾?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長岂津。 經常有香客問我虱黄,道長,這世上最難降的妖魔是什么吮成? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任橱乱,我火速辦了婚禮,結果婚禮上粱甫,老公的妹妹穿的比我還像新娘泳叠。我一直安慰自己,他們只是感情好茶宵,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布危纫。 她就那樣靜靜地躺著,像睡著了一般乌庶。 火紅的嫁衣襯著肌膚如雪种蝶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天瞒大,我揣著相機與錄音螃征,去河邊找鬼。 笑死透敌,一個胖子當著我的面吹牛会傲,可吹牛的內容都是我干的锅棕。 我是一名探鬼主播拙泽,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼淌山,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了顾瞻?” 一聲冷哼從身側響起泼疑,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荷荤,沒想到半個月后退渗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡蕴纳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年会油,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片古毛。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡翻翩,死狀恐怖,靈堂內的尸體忽然破棺而出稻薇,到底是詐尸還是另有隱情嫂冻,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布塞椎,位于F島的核電站桨仿,受9級特大地震影響,放射性物質發(fā)生泄漏案狠。R本人自食惡果不足惜服傍,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望骂铁。 院中可真熱鬧吹零,春花似錦、人聲如沸从铲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽名段。三九已至阱扬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間伸辟,已是汗流浹背麻惶。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留信夫,地道東北人窃蹋。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓卡啰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親警没。 傳聞我的和親對象是個殘疾皇子匈辱,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內容