主頁 > 後端開發 > 大量類加載器創建導致詭異FullGC

大量類加載器創建導致詭異FullGC

2020-10-20 20:56:00 後端開發

現象

最近接手了一個同事的專案,某一天介面的回應耗時突然增加了很多,由幾十ms 增加到了幾十秒,

首先查看機器上的日志,有呼叫第三方介面超時,查詢資料庫超時,立馬查看第三方介面監控和資料庫監控,一切正常,可能由于 GC 停頓造成統計的超時,這個時候我們通過 jstat -gcutil pid 查看 gc 情況,資料如下:

  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00   0.00   3.88  12.86  76.39  45.62    211    8.574   892  626.192  634.767
  0.00   0.00   4.10  12.86  76.39  45.62    211    8.574   893  626.192  634.767
  0.00   0.00   0.00  12.88  76.39  45.62    211    8.574   894  626.915  635.489
  0.00   0.00   0.11  12.88  76.39  45.62    211    8.574   896  627.678  636.253
  0.00   0.00   0.00  12.87  76.39  45.62    211    8.574   897  628.926  637.500
  0.00   0.00   0.00  12.87  76.39  45.62    211    8.574   899  630.381  638.956
  0.00   0.00   1.92  12.87  76.39  45.62    211    8.574   901  631.155  639.729
  0.00   0.00   0.00  12.87  76.39  45.62    211    8.574   902  632.379  640.954
  0.00   0.00   2.14  12.87  76.39  45.62    211    8.574   903  633.094  641.668
  0.00   0.00   0.00  12.88  76.39  45.62    211    8.574   904  633.859  642.433
復制代碼

這里我們可以看到年輕代(E) 使用率很小,老年代(O)使用率 12% 也不多,M(Metaspace) 使用率 76.39% 也沒占滿,Yong GC 沒有變化,Full GC 一直在進行,每次耗時800多ms,結合前面 E、O 和 M 使用率都沒有變化,說明記憶體一直回收不掉,

JVM 記憶體大小相關配置如下:

 -Xms3g -Xmx3g -Xmn1g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m
復制代碼

接下來我們看下 GC 日志:

2020-08-13T23:11:00.352+0800: 214929.371: [GC (CMS Initial Mark)  276144K(3040896K), 0.0405942 secs]
2020-08-13T23:11:00.886+0800: 214929.905: [Full GC (Metadata GC Threshold)  290482K->275966K(3040896K), 0.7939954 secs]
2020-08-13T23:11:01.693+0800: 214930.712: [Full GC (Last ditch collection)  275966K->275964K(3040896K), 0.8086755 secs]
2020-08-13T23:11:02.520+0800: 214931.539: [Full GC (Metadata GC Threshold)  295199K->273816K(3040896K), 0.8332017 secs]
2020-08-13T23:11:03.366+0800: 214932.385: [Full GC (Last ditch collection)  273816K->273799K(3040896K), 0.7748226 secs]
復制代碼

GC 日志中有 Metadata GC Threshold ,結合前面 Metaspace 使用率最高我們猜測可能是 Metaspace 溢位了,然后我們在日志中 grep OutOfMemory 關鍵字,有如下報錯:

java.lang.OutOfMemoryError: Metaspace
復制代碼

至此可以確認是 Metaspace 出問題了,但是為什么 jstat 輸出的使用率只有 76.39% 呢?大家如果經常使用 jstat 看一下正常的程式就會很多正常情況 Metaspace 都占用 90% 以上,

Metaspace

Metaspace 元資料空間,專門用來存盤類的元資料,它是 JDK8 中用來替代 Perm 的特殊資料結構,

Metaspace 空間被分配在本地記憶體中(非堆上),默認不限制記憶體使用,可以使用 MaxMetaspaceSize 指定最大值,MetaspaceSize 指定最小值,默認 21 M,通過 mmap 來從作業系統申請記憶體,申請的記憶體會分成一個一個 Metachunk,以 Metachunk 為單位將記憶體分配給類加載器,每個 Metachunk 對應唯一一個類加載器,一個類加載器可以有多個 Metachunk ,

可以用 java -XX:+PrintFlagsFinal -version 來查看 JVM 的默認引數值

在 Java 虛擬機中,每個類加載器都有一個 ClassLoaderData 的資料結構,ClassloaderData 內部有管理記憶體的 Metaspace,Metaspace 在 initialize 的時候會呼叫 get_initialization_chunk 分配第一塊 Metachunk,類加載器在類的時候是以 Metablock 為單位來使用 Metachunk,

//classLoaderData.hpp
class ClassLoaderData : public CHeapObj<mtClass> {
...

  Metaspace * _metaspace;  // Meta-space where meta-data defined by the
                           // classes in the class loader are allocated.
  Mutex* _metaspace_lock;  // Locks the metaspace for allocations and setup.

...
}

// metaspace.hpp
class Metaspace : public CHeapObj<mtClass> {
...
 private:
  void initialize(Mutex* lock, MetaspaceType type);

  Metachunk* get_initialization_chunk(MetadataType mdtype,
                                      size_t chunk_word_size,
                                      size_t chunk_bunch);
...
}

// metachunk.hpp
class Metachunk : public Metabase<Metachunk>
class Metablock : public Metabase<Metablock>

// Metablock 和 Metachunk 的父類
template <class T>
class Metabase VALUE_OBJ_CLASS_SPEC {
  size_t _word_size;
  T*     _next;
  T*     _prev;
...
}
復制代碼

下圖所示是每個類加載器分配記憶體結構,

image.png

接下來我們講下什么時候會觸發 FullGC,有個引數 MinMetaspaceFreeRatio(默認40) ,當滿足如下條件就會進行 GC,如果當前需要申請的記憶體比剩余可以 commit 的空間還要大,如果還沒有達到 MaxMetaspaceSize 的話,會觸發擴容,

剩余可以 commit 的空間大小 < (commited 大小 * MinMetaspaceFreeRatio)

上面說到 commited 的記憶體,這里還有幾個概念 :used、capacity、reserved,如下圖所示

  • used: chunk 中已經使用的 block 記憶體,這些 block 中都加載了類的資料,

  • capacity:在使用的 chunk 記憶體,

  • commited:所有分配的 chunk 記憶體,這里包含空閑可以再次被利用的,

  • reserved:是可以使用的內存大小,

image.png

如下所示,是列印出來的記憶體資訊,最后一行是開啟壓縮指標(64位壓縮為32位)后,Metaspace 中專門存放 kclass 的資訊,

Heap
 par new generation   total 30720K, used 1519K [0x00000007f8600000, 0x00000007fa750000, 0x00000007fa750000)
  eden space 27328K,   5% used [0x00000007f8600000, 0x00000007f877bcc8, 0x00000007fa0b0000)
  from space 3392K,   0% used [0x00000007fa400000, 0x00000007fa400000, 0x00000007fa750000)
  to   space 3392K,   0% used [0x00000007fa0b0000, 0x00000007fa0b0000, 0x00000007fa400000)
 concurrent mark-sweep generation total 68288K, used 21614K [0x00000007fa750000, 0x00000007fea00000, 0x00000007fea00000)
 Metaspace       used 23505K, capacity 30704K, committed 30720K, reserved 1073152K
  class space    used 3341K, capacity 7550K, committed 7552K, reserved 1048576K
復制代碼

基礎知識講完了,現在我們回到開頭,我們通過 jstat 列印出的 M 是怎么計算的呢?這里使用率并不是我們理解的整個 Metaspace 記憶體的使用率,

M = used / commited
復制代碼

所以 Metaspace 記憶體溢位了,使用率也才 76%,有兩種可能:

  1. 這次分配的記憶體達到了 61M( 256M*24% ) 以上?

  2. 給類加載器分配的 chunk 使用率很低?

第一種顯然不太可能,一個類不可能需要這么大的記憶體,第二種有種情況,當創建很多類加載器,而每個類加載器又加載了很少的類,

上面我們說了剩余空閑記憶體小于metaspaceGC的閾值就會執行FullGC,但是我們開頭說有些正常場景我們通過 jstat 列印的使用率都達到了 90% 多都沒有觸發 FullGC,這是為什么呢?歡迎留言分享你的答案

排查程式

首先,我們看下 Metaspace 加載的到底是哪些類

jcmd pid GC.class_stats |awk '{print $13}'| sort | uniq -c |sort -r| head
復制代碼

通過 jcmd 查看加載的類,然后統計數量,我們看到,Script1 被加載了兩萬多次,按 JVM 類加載的雙親委派方式,一個類最多被加載一次,這里出現了多次,可能是不同的類加載器加載的,

27348 Script1
   3
   2 ClassName
   1 sun.util.spi.CalendarProvider
   1 sun.util.resources.en.TimeZoneNames_en
   1 sun.util.resources.en.CurrencyNames_en_US
   1 sun.util.resources.en.CalendarData_en
   1 sun.util.resources.TimeZoneNamesBundle
   1 sun.util.resources.TimeZoneNames
   1 sun.util.resources.ParallelListResourceBundle$KeySet
復制代碼

通過 jcmd 查看,需要在啟動是加上引數:-XX:+UnlockDiagnosticVMOptions

然后我們再看下 JVM 類加載器的資料

jmap -clstats pid

這里 classes 是加載類的數量,從輸出中可以看到有大量 GroovyClassLoader 類加載器,

class_loader    classes bytes   parent_loader   alive?  type
<bootstrap>     2850    4913169   null          live    <internal>
0x000000077bc27bc0      1       1394    0x000000077bc64418      dead    groovy/lang/GroovyClassLoader$InnerLoader@0x00000007f0dcf828
0x000000077d9e7d98      0       0       0x0000000770800000      dead    groovy/lang/GroovyClassLoader@0x00000007f0af9890
0x00000007805e8050      0       0       0x0000000770800000      dead    groovy/lang/GroovyClassLoader@0x00000007f0af9890
0x000000077df07de0      0       0       0x0000000770800000      dead    groovy/lang/GroovyClassLoader@0x00000007f0af9890
0x0000000780028010      1       1394    0x000000078005a6c8      dead    groovy/lang/GroovyClassLoader$InnerLoader@0x00000007f0dcf828
0x0000000776467650      1       1394    0x000000077646b190      dead    groovy/lang/GroovyClassLoader$InnerLoader@0x00000007f0dcf828
0x000000077a167a00      1       1394    0x000000077a16b380      dead    groovy/lang/GroovyClassLoader$InnerLoader@0x00000007f0dcf828
復制代碼

通過統計,每個 GroovyClassLoader$InnerLoader 都只加載一個類,然后他的數量一共有 27348,跟上面的 Script1 類數量剛好對的上,說明就是這個類加載器加載的,

接下來怎么定位哪里生產的類加載器加載的類呢?

首先看 groovy 是哪里引入的,然后本地除錯,加上JVM 引數:-XX:+UnlockDiagnosticVMOptions,加載類的時候控制臺就會列印,就可以一步一步定位到哪里加載的,

我們專案中用 sharding 做的分表,sharding 引入的 groovy 版本如下

<dependency>
    <groupId>io.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <version>3.0.0.M1</version>
</dependency>
<dependency>
  <groupId>org.codehaus.groovy</groupId>
  <artifactId>groovy</artifactId>
  <classifier>indy</classifier>
  <version>2.4.5</version>
</dependency>
復制代碼

最終定位到出現問題的代碼如下,當你配置分表的運算式后,每次執行查詢操作,都會創建一個 GroovyShell 來執行配置的運算式,在 GroovyShell 中,每次都會生成一個類加載器,來加載類 Script1,加載完后又無法被 GC 掉,導致記憶體泄露,

public InlineShardingStrategy(final InlineShardingStrategyConfiguration inlineShardingStrategyConfig) {
    Preconditions.checkNotNull(inlineShardingStrategyConfig.getShardingColumn(), "Sharding column cannot be null.");
    Preconditions.checkNotNull(inlineShardingStrategyConfig.getAlgorithmExpression(), "Sharding algorithm expression cannot be null.");
    shardingColumn = inlineShardingStrategyConfig.getShardingColumn();
    String algorithmExpression = InlineExpressionParser.handlePlaceHolder(inlineShardingStrategyConfig.getAlgorithmExpression().trim());
    closure = (Closure) new GroovyShell().evaluate(Joiner.on("").join("{it -> \"", algorithmExpression, "\"}"));
}
復制代碼

這里升級 sharding 新版本即可,新版本中 GroovyShell 是static 的,

public final class InlineExpressionParser {
...
    private static final GroovyShell SHELL = new GroovyShell();
...
}
復制代碼

這里還有個疑問,類加載器加載用完了并且狀態是 dead 為什么不回收掉呢?

本地復現

復現的代碼很簡單,引入上述 groovy 版本,在運行時加上 JVM 引數

// -Xmx100M -Xms100M -verbose:class -XX:+PrintGCDetails -XX:MaxMetaspaceSize=30M -XX:MetaspaceSize=30M -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled -XX:+UnlockDiagnosticVMOptions -XX:+HeapDumpOnOutOfMemoryError
public static void main(String[] args) {
    for (int i = 0; i < 4000; i++) {
        new GroovyShell().parse("");
    }
}
復制代碼

接下來主要講下,怎么用 mat 來排查這個類加載為什么沒有被回收,用 mat 加載上示例程式 dump 出來的堆,選擇 Histogram ,然后在正則中輸入 GroovyClassLoader ,Objects 是表示創建物件數量,這里有 3255 個,說明上面的 for 回圈執行了 3255 次之后 Metaspace 就溢位了,

image.png

接下來選擇 Dominator Tree,然后輸入 Script1 正則過濾,右鍵選擇:Path To Gc Roots,這里我們只關心強參考,所以 execlude 其他型別參考,

image.png

如果類加載器被回收,它所加載的類也會被回收,如果類有被參考,肯定不能被回收,所以,我們從 Script1 的物件開始,如下圖所有,Script1 類有被參考,最終到達 GC root (AppClassLoader),所以 Full GC 也沒法回收掉,

image.png

看完三件事??

如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:

  1. 點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力,

  2. 關注公眾號 『 java爛豬皮 』,不定期分享原創知識,

  3. 同時可以期待后續文章ing??

  4. 歡迎關注作者gitee,希望共同學習



轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/182750.html

標籤:其他

上一篇:什么會導致Java應用程式的CPU使用率飆升?

下一篇:解決服務器行程退出問題(metaspace溢位)實戰

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more