Metaspace 之一:Metaspace總體介紹(永久代被替換緣由、元空間特色、元空間內存查看分析方法)

回顧

根據JVM內存區域的劃分,簡單的畫了下方的這個示意圖。區域主要分爲兩大塊,一塊是堆區(Heap),咱們所New出的對象都會在堆區進行分配,在C語言中的malloc所分配的方法就是從Heap區獲取的。而垃圾回收器主要是對堆區的內存進行回收的。html

而另外一部分則是非堆區,非堆區主要包括用於編譯和保存本地代碼的「代碼緩存區(Code Cache)」、保存JVM本身的靜態數據的「永生代(Perm Gen)」、存放方法參數局部變量等引用以及記錄方法調用順序的「Java虛擬機棧(JVM Stack)」和「本地方法棧(Local Method Stack)」。java

垃圾回收器主要回收的是堆區中未使用的內存區域,並對相應的區域進行整理。在堆區中,又根據對象內存的存活時間或者對象大小,分爲「年輕代」和「年老代」。「年輕代」中的對象是不穩定的易產生垃圾,而「年老代」中的對象比較穩定,不易產生垃圾。之因此將其分開,是分而治之,根據不一樣區域的內存塊的特色,採起不一樣的內存回收算法,從而提升堆區的垃圾回收的效率。web

永久代存放JVM運行時使用的類。永久代一樣包含了Java SE庫的類和方法。永久代的對象在full GC時進行垃圾收集。算法

1、元空間替換持久代

1.一、持久代

  PermGen space的全稱是Permanent Generation space,是指內存的永久保存區域,說說爲何會內存益出:這一部分用於存放Class和Meta的信息,Class在被 Load的時候被放入PermGen space區域,它和和存放Instance的Heap區域不一樣,因此若是你的APP會LOAD不少CLASS的話,就極可能出現PermGen space錯誤。這種錯誤常見在web服務器對JSP進行pre compile的時候。json

  JVM 種類有不少,好比 Oralce-Sun Hotspot, Oralce JRockit, IBM J9, Taobao JVM(淘寶好樣的!)等等。固然武林盟主是Hotspot了,這個毫無爭議。須要注意的是,PermGen space是Oracle-Sun Hotspot纔有,JRockit以及J9是沒有這個區域。bootstrap

持久代中包含了虛擬機中全部可經過反射獲取到的數據,好比Class和Method對象。不一樣的Java虛擬機之間可能會進行類共享,所以持久代又分爲只讀區和讀寫區。數組

JVM用於描述應用程序中用到的類和方法的元數據也存儲在持久代中。JVM運行時會用到多少持久代的空間取決於應用程序用到了多少類。除此以外,Java SE庫中的類和方法也都存儲在這裏。緩存

若是JVM發現有的類已經再也不須要了,它會去回收(卸載)這些類,將它們的空間釋放出來給其它類使用。Full GC會進行持久代的回收。服務器

  • JVM中類的元數據在Java堆中的存儲區域。
  • Java類對應的HotSpot虛擬機中的內部表示也存儲在這裏。
  • 類的層級信息,字段,名字。
  • 方法的編譯信息及字節碼。
  • 變量
  • 常量池和符號解析

持久代的大小數據結構

  • 它的上限是MaxPermSize,默認是64M
  • Java堆中的連續區域 : 若是存儲在非連續的堆空間中的話,要定位出持久代到新對象的引用很是複雜而且耗時。卡表(card table),是一種記憶集(Remembered Set),它用來記錄某個內存代中普通對象指針(oops)的修改。
  • 持久代用完後,會拋出OutOfMemoryError "PermGen space"異常。解決方案:應用程序清理引用來觸發類卸載;增長MaxPermSize的大小。
  • 須要多大的持久代空間取決於類的數量,方法的大小,以及常量池的大小。

1.二、爲何移除持久代

  • 它的大小是在啓動時固定好的——很難進行調優。-XX:MaxPermSize,設置成多少好呢?
  • HotSpot的內部類型也是Java對象:它可能會在Full GC中被移動,同時它對應用不透明,且是非強類型的,難以跟蹤調試,還須要存儲元數據的元數據信息(meta-metadata)。
  • 簡化Full GC:每個回收器有專門的元數據迭代器。
  • 能夠在GC不進行暫停的狀況下併發地釋放類數據。
  • 使得原來受限於持久代的一些改進將來有可能實現

根據上面的各類緣由,永久代最終被移除,方法區移至Metaspace,字符串常量移至Java Heap

1.三、移除持久代後,PermGen空間的情況

  • 這部份內存空間將所有移除。

  • JVM的參數:PermSize 和 MaxPermSize 會被忽略並給出警告(若是在啓用時設置了這兩個參數)。

 

 

2、元空間

隨着JDK8的到來,JVM再也不有PermGen。但類的元數據信息(metadata)還在,只不過再也不是存儲在連續的堆空間上,而是移動到叫作「Metaspace」的本地內存(Native memory)中。

2.一、metaspace的組成

  • Klass Metaspace:Klass Metaspace就是用來存klass的,klass是咱們熟知的class文件在jvm裏的運行時數據結構,不過有點要提的是咱們看到的相似A.class實際上是存在heap裏的,是java.lang.Class的一個對象實例。這塊內存是緊接着Heap的,和咱們以前的perm同樣,這塊內存大小可經過-XX:CompressedClassSpaceSize參數來控制,這個參數前面提到了默認是1G,可是這塊內存也能夠沒有,假如沒有開啓壓縮指針就不會有這塊內存,這種狀況下klass都會存在NoKlass Metaspace裏,另外若是咱們把-Xmx設置大於32G的話,其實也是沒有這塊內存的,由於會這麼大內存會關閉壓縮指針開關。還有就是這塊內存最多隻會存在一塊。
  • NoKlass Metaspace:NoKlass Metaspace專門來存klass相關的其餘的內容,好比method,constantPool等,這塊內存是由多塊內存組合起來的,因此能夠認爲是不連續的內存塊組成的。這塊內存是必須的,雖然叫作NoKlass Metaspace,可是也其實能夠存klass的內容,上面已經提到了對應場景。

Klass Metaspace和NoKlass Mestaspace都是全部classloader共享的,因此類加載器們要分配內存,可是每一個類加載器都有一個SpaceManager,來管理屬於這個類加載的內存小塊。若是Klass Metaspace用完了,那就會OOM了,不過通常狀況下不會,NoKlass Mestaspace是由一塊塊內存慢慢組合起來的,在沒有達到限制條件的狀況下,會不斷加長這條鏈,讓它能夠持續工做。

元空間的本質和永久代相似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。所以,默認狀況下,元空間的大小僅受本地內存限制,但能夠經過如下參數來指定元空間的大小: 
  -XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:若是釋放了大量的空間,就適當下降該值;若是釋放了不多的空間,那麼在不超過MaxMetaspaceSize時,適當提升該值。 
  -XX:MaxMetaspaceSize,最大空間,默認是沒有限制的。 
  除了上面兩個指定大小的選項之外,還有兩個與 GC 相關的屬性: 
  -XX:MinMetaspaceFreeRatio,在GC以後,最小的Metaspace剩餘空間容量的百分比,減小爲分配空間所致使的垃圾收集 
  -XX:MaxMetaspaceFreeRatio,在GC以後,最大的Metaspace剩餘空間容量的百分比,減小爲釋放空間所致使的垃圾收集

  -verbose參數是爲了獲取類型加載和卸載的信息

2.二、元空間的特色

  • 充分利用了Java語言規範中的好處:類及相關的元數據的生命週期與類加載器的一致
  • 每一個加載器有專門的存儲空間
  • 只進行線性分配
  • 不會單獨回收某個類
  • 省掉了GC掃描及壓縮的時間
  • 元空間裏的對象的位置是固定的
  • 若是GC發現某個類加載器再也不存活了,會把相關的空間整個回收掉

2.三、元空間的內存分配模型

  • 絕大多數的類元數據的空間都從本地內存中分配
  • 用來描述類元數據的類(klasses)也被刪除了
  • 分元數據分配了多個虛擬內存空間
  • 給每一個類加載器分配一個內存塊的列表。塊的大小取決於類加載器的類型; sun/反射/代理對應的類加載器的塊會小一些
  • 歸還內存塊,釋放內存塊列表
  • 一旦元空間的數據被清空了,虛擬內存的空間會被回收掉
  • 減小碎片的策略

咱們來看下JVM是如何給元數據分配虛擬內存的空間的 

你能夠看到虛擬內存空間是如何分配的(vs1,vs2,vs3) ,以及類加載器的內存塊是如何分配的。CL是Class Loader的縮寫。

理解_mark和_klass指針

要想理解下面這張圖,你得搞清楚這些指針都是什麼東西。

JVM中,每一個對象都有一個指向它自身類的指針,不過這個指針只是指向具體的實現類,而不是接口或者抽象類。

對於32位的JVM:

_mark : 4字節常量

_klass: 指向類的4字節指針 對象的內存佈局中的第二個字段( _klass,在32位JVM中,相對對象在內存中的位置的偏移量是4,64位的是8)指向的是內存中對象的類定義。

64位的JVM:

_mark : 8字節常量

_klass: 指向類的8字節的指針

開啓了指針壓縮的64位JVM: _mark : 8字節常量

_klass: 指向類的4字節的指針

Java對象的內存佈局

類指針壓縮空間(Compressed Class Pointer Space)

只有是64位平臺上啓用了類指針壓縮纔會存在這個區域。對於64位平臺,爲了壓縮JVM對象中的_klass指針的大小,引入了類指針壓縮空間(Compressed Class Pointer Space)。

CompressedClassSpace

  • java8移除了permanent generation,而後class metadata存儲在native memory中,其大小默認是不受限的,能夠經過-XX:MaxMetaspaceSize來限制
  • 若是開啓了-XX:+UseCompressedOops及-XX:+UseCompressedClassesPointers(默認是開啓),則UseCompressedOops會使用32-bit的offset來表明java object的引用,而UseCompressedClassPointers則使用32-bit的offset來表明64-bit進程中的class pointer;可使用CompressedClassSpaceSize來設置這塊的空間大小
  • 若是開啓了指針壓縮,則CompressedClassSpace分配在MaxMetaspaceSize裏頭,即MaxMetaspaceSize=Compressed Class Space Size + Metaspace area (excluding the Compressed Class Space) Size
  • 查看CompressedClassSpace大小,見《12、jdk工具之jcmd介紹(堆轉儲、堆分析、獲取系統信息、查看堆外內存)

壓縮指針後的內存佈局

指針壓縮概要

  • 64位平臺上默認打開
  • 使用-XX:+UseCompressedOops壓縮對象指針 "oops"指的是普通對象指針("ordinary" object pointers)。 Java堆中對象指針會被壓縮成32位。 使用堆基地址(若是堆在低26G內存中的話,基地址爲0)

  • 使用-XX:+UseCompressedClassPointers選項來壓縮類指針

  • 對象中指向類元數據的指針會被壓縮成32位

  • 類指針壓縮空間會有一個基地址

元空間和類指針壓縮空間的區別

  • 類指針壓縮空間只包含類的元數據,好比InstanceKlass, ArrayKlass 僅當打開了UseCompressedClassPointers選項才生效 爲了提升性能,Java中的虛方法表也存放到這裏 這裏到底存放哪些元數據的類型,目前仍在減小

  • 元空間包含類的其它比較大的元數據,好比方法,字節碼,常量池等。

3、元空間內存管理

元空間的內存管理由元空間虛擬機來完成。先前,對於類的元數據咱們須要不一樣的垃圾回收器進行處理,如今只須要執行元空間虛擬機的C++代碼便可完成。在元空間中,類和其元數據的生命週期和其對應的類加載器是相同的。話句話說,只要類加載器存活,其加載的類的元數據也是存活的,於是不會被回收掉。 
準確的來講,每個類加載器的存儲區域都稱做一個元空間,全部的元空間合在一塊兒就是咱們一直說的元空間。當一個類加載器被垃圾回收器標記爲再也不存活,其對應的元空間會被回收。在元空間的回收過程當中沒有重定位和壓縮等操做。可是元空間內的元數據會進行掃描來肯定Java引用。 
元空間虛擬機負責元空間的分配,其採用的形式爲組塊分配。組塊的大小因類加載器的類型而異。在元空間虛擬機中存在一個全局的空閒組塊列表。當一個類加載器須要組塊時,它就會從這個全局的組塊列表中獲取並維持一個本身的組塊列表。當一個類加載器再也不存活,那麼其持有的組塊將會被釋放,並返回給全局組塊列表。類加載器持有的組塊又會被分紅多個塊,每個塊存儲一個單元的元信息。組塊中的塊是線性分配(指針碰撞分配形式)。組塊分配自內存映射區域。這些全局的虛擬內存映射區域以鏈表形式鏈接,一旦某個虛擬內存映射區域清空,這部份內存就會返回給操做系統。

上圖展現的是虛擬內存映射區域如何進行元組塊的分配。類加載器1和3代表使用了反射或者爲匿名類加載器,他們使用了特定大小組塊。 而類加載器2和4根據其內部條目的數量使用小型或者中型的組塊。

4、Metaspace調優

使用-XX:MaxMetaspaceSize參數能夠設置元空間的最大值,默認是沒有上限的,也就是說你的系統內存上限是多少它就是多少。-XX:MetaspaceSize選項指定的是元空間的初始大小,若是沒有指定的話,元空間會根據應用程序運行時的須要動態地調整大小。

MaxMetaspaceSize的調優

  • -XX:MaxMetaspaceSize={unlimited}
  • 元空間的大小受限於你機器的內存
  • 限制類的元數據使用的內存大小,以避免出現虛擬內存切換以及本地內存分配失敗。若是懷疑有類加載器出現泄露,應當使用這個參數;32位機器上,若是地址空間可能會被耗盡,也應當設置這個參數。
  • 元空間的初始大小是21M——這是GC的初始的高水位線,超過這個大小會進行Full GC來進行類的回收。
  • 若是啓動後GC過於頻繁,請將該值設置得大一些
  • 能夠設置成和持久代同樣的大小,以便推遲GC的執行時間

CompressedClassSpaceSize的調優

  • 只有當-XX:+UseCompressedClassPointers開啓了纔有效
  • -XX:CompressedClassSpaceSize=1G
  • 因爲這個大小在啓動的時候就固定了的,所以最好設置得大點。
  • 沒有使用到的話不要進行設置
  • JVM後續可能會讓這個區能夠動態的增加。不須要是連續的區域,只要從基地址可達就行;可能會將更多的類元信息放回到元空間中;將來會基於PredictedLoadedClassCount的值來自動的設置該空間的大小

正如前面提到了,Metaspace VM管理Metaspace空間的增加。但有時你會想經過在命令行顯示的設置參數-XX:MaxMetaspaceSize來限制Metaspace空間的增加。默認狀況下,-XX:MaxMetaspaceSize並無限制,所以,在技術上,Metaspace的尺寸能夠增加到交換空間,而你的本地內存分配將會失敗。

每次垃圾收集以後,Metaspace VM會自動的調整high watermark,推遲下一次對Metaspace的垃圾收集。

這兩個參數,-XX:MinMetaspaceFreeRatio和-XX:MaxMetaspaceFreeRatio,相似於GC的FreeRatio參數,能夠放在命令行。

5、Metaspace可使用的工具

針對Metaspace,JDK自帶的一些工具作了修改來展現Metaspace的信息:

  • jmap -clstats :打印類加載器的統計信息(取代了在JDK8以前打印類加載器信息的permstat)。
  • jstat -gc :Metaspace的信息也會被打印出來。
  • jcmd GC.class_stats:這是一個新的診斷命令,可使用戶鏈接到存活的JVM,轉儲Java類元數據的詳細統計。

示例1:jmap -clstats 

[ciadmin@2-103test_app pos-gateway-cloud]$ jmap -clstats 26964
Attaching to process ID 26964, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. computing liveness..................................................................................liveness analysis may be inaccurate ...
class_loader    classes    bytes    parent_loader    alive?    type

<bootstrap>    2699    4611703      null      live    <internal>
0x00000000a1013a00    1    880    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a3e931e8    1    880      null      dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a083d280    1    1471    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a1c057c8    1    880    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a1013938    1    1474    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a1013d38    1    1471    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a141ae78    1    880    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a083d1b8    1    1473    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a163c658    1    880    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a293afa8    1    1473    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a19ec0a0    15    70893    0x00000000a001b938    live    com/aliyun/openservices/shade/com/alibaba/fastjson/util/ASMClassLoader@0x000000010066b7a0
0x00000000a2778848    1    1474    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a141a900    1    880    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a083d8c0    1    1473    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a163c720    1    880    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
...
0x00000000a094fe68    0    0    0x00000000a0007438    live    java/net/URLClassLoader@0x000000010000ecd0

total = 177    12836    20539140        N/A        alive=9, dead=168        N/A    
[ciadmin@2-103test_app pos-gateway-cloud]$ 

示例二:jstat -gc 26964

[ciadmin@2-103test_app pos-gateway-cloud]$ jstat -gc 26964
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
3072.0 3072.0 2384.8  0.0   62976.0   6699.1   445440.0   67911.5   69760.0 68124.8 8320.0 7929.0   3792   36.649  12      1.971   38.620
[ciadmin@2-103test_app pos-gateway-cloud]$ 

示例三:jcmd 5943 GC.class_stats

[ciadmin@2-103test_app pos-gateway-cloud]$ jcmd 5943 GC.class_stats
5943:
GC.class_stats command requires -XX:+UnlockDiagnosticVMOptions
[ciadmin@2-103test_app pos-gateway-cloud]$ 

說是:應用程序啓動時增長-XX:+UnlockDiagnosticVMOptions參數

加了上面的參數後,從新來一把以下:

D:\workspace\study\target\classes\com\dxz\jvm>jcmd 4332 GC.class_stats
4332:
Index Super  InstBytes KlassBytes annotations  CpAll MethodCount Bytecodes MethodAll  ROAll   RWAll   Total ClassName
    1    -1  258458040        480           0      0           0         0         0     24     584     608 [Ljava.lang.Object;
    2   368  217343856       1000           0   6864          51      3955     13744   8664   13888   22552 java.util.HashMap
    3   368  144895744       1432           0  15584          93      9536     37136  19104   37048   56152 java.util.concurrent.ConcurrentHashMap
    4   363   99615560        928           0   8232          24      1719      6040   4392   11304   15696 java.net.URLClassLoader
    5    -1   90560256        480           0      0           0         0         0     32     584     616 [Ljava.util.concurrent.ConcurrentHashMap$Node;
    6    -1   90559840        480           0      0           0         0         0     32     584     616 [Ljava.util.WeakHashMap$Entry;
    7   367   72447872       1384           0   5288          59      2245     13544   7080   14016   21096 java.util.Vector
    8   367   54335928       1320           0   4936          49      2359     12104   6600   12592   19192 java.util.ArrayList
    9   368   54335904        976           0   4952          32      1845     11968   4856   13744   18600 java.util.WeakHashMap
   10    14   54335856        656           0   7488          33      1513      8504   4792   12496   17288 sun.misc.URLClassPath
   11    14   45280240        504           0   4960          27      2551     11792   5232   12536   17768 java.security.AccessControlContext
   12    14   45279920        528           0   4328          12      1024      3760   2488    6496    8984 java.security.ProtectionDomain
   13    14   36226208        568           0   1344           8       223      1744   1024    2952    3976 java.util.concurrent.ConcurrentHashMap$Node
   14    -1   36224752        496           0   1144          14       109      2520   1112    3272    4384 java.lang.Object
   15    14   36224032        552           0   1840           7       410      2744   1288    4160    5448 java.lang.ref.ReferenceQueue
   16    14   36223936        552           0   5320          14      1796      4648   3552    7328   10880 java.security.CodeSource
   17     7   36223936       1424           0    864           6        88      1664    704    3552    4256 java.util.Stack
   18   373   27167952       1008           0    808           4        69      1000    592    2528    3120 java.util.Collections$SynchronizedSet
   19    -1   27167880        480           0      0           0         0         0     24     584     608 [Ljava.security.ProtectionDomain;
   20    14   18112048        496           0    360           2        10       920    216    1720    1936 java.lang.ref.ReferenceQueue$Lock
   21    -1   18111968        480           0      0           0         0         0     24     584     608 [Ljava.security.Principal;
...
  484    14          0        496           0   1416          20       737      3736   2240    3680    5920 sun.util.locale.LocaleUtils
            1535868936     298000        1536 955600        6934    264621   1445736 885528 1980368 2865896 Total
              53591.2%      10.4%        0.1%  33.3%           -      9.2%     50.4%  30.9%   69.1%  100.0%
Index Super  InstBytes KlassBytes annotations  CpAll MethodCount Bytecodes MethodAll  ROAll   RWAll   Total ClassName

D:\workspace\study\target\classes\com\dxz\jvm>

6、提升GC的性能

若是你理解了元空間的概念,很容易發現GC的性能獲得了提高。

  • Full GC中,元數據指向元數據的那些指針都不用再掃描了。不少複雜的元數據掃描的代碼(尤爲是CMS裏面的那些)都刪除了。
  • 元空間只有少許的指針指向Java堆。這包括:類的元數據中指向java/lang/Class實例的指針;數組類的元數據中,指向java/lang/Class集合的指針。
  • 沒有元數據壓縮的開銷
  • 減小了根對象的掃描(再也不掃描虛擬機裏面的已加載類的字典以及其它的內部哈希表)
  • 減小了Full GC的時間
  • G1回收器中,併發標記階段完成後能夠進行類的卸載

java8中metaspace總結以下:

PermGen 空間的情況

這部份內存空間將所有移除。

JVM的參數:PermSize 和 MaxPermSize 會被忽略並給出警告(若是在啓用時設置了這兩個參數)。

Metaspace 內存分配模型

大部分類元數據都在本地內存中分配。

用於描述類元數據的「klasses」已經被移除。

Metaspace 容量

默認狀況下,類元數據只受可用的本地內存限制(容量取決因而32位或是64位操做系統的可用虛擬內存大小)。

新參數(MaxMetaspaceSize)用於限制本地內存分配給類元數據的大小。若是沒有指定這個參數,元空間會在運行時根據須要動態調整。

Metaspace 垃圾回收

對於僵死的類及類加載器的垃圾回收將在元數據使用達到「MaxMetaspaceSize」參數的設定值時進行。

適時地監控和調整元空間對於減少垃圾回收頻率和減小延時是頗有必要的。持續的元空間垃圾回收說明,可能存在類、類加載器致使的內存泄漏或是大小設置不合適。

7、元空間的問題

前面已經提到,元空間虛擬機採用了組塊分配的形式,同時區塊的大小由類加載器類型決定。類信息並非固定大小,所以有可能分配的空閒區塊和類須要的區塊大小不一樣,這種狀況下可能致使碎片存在。元空間虛擬機目前並不支持壓縮操做,因此碎片化是目前最大的問題。

相關文章
相關標籤/搜索