主頁 > 後端開發 > 從原理聊JVM(二):從串行收集器到磁區收集開創者G1

從原理聊JVM(二):從串行收集器到磁區收集開創者G1

2023-04-25 07:41:56 後端開發

作者:京東科技 康志興

1 前言

隨著Java的進化程序,涌現出各種不同的垃圾回收器,從串行執行到并行執行,從高吞吐到低延遲,終極目標就是讓開發人員專注于程式的代碼書寫而無需關注記憶體管理,

JDK早期出現的垃圾回收器通常單獨作用于不同分代,到后期出現的G1開始,才可以進行全區域收集,

關于垃圾回收器的基礎知識請翻看前一篇:從原理聊JVM(一):染色標記和垃圾回收演算法

2 串行收集器(Serial)

比較老的收集器,單執行緒,所收集時必須暫停應用的作業執行緒,直到收集結束,但和其他收集器的單執行緒相比更加簡單、高效

作用于新生代的收集器叫Serial,采用標記復制演算法;作用于年老代的收集器叫Serial Old,采用標記整理演算法,

3 并行收集器(Parallel)

多條垃圾收集執行緒并行作業,在多核CPU下效率更高,但應用執行緒仍然處于等待狀態,

并行收集器也分為ParNewParallel Old,可以理解為它們就是Serial和Serial Old的多執行緒并行版本,甚至部分代碼進行了復用,

ParNew較為流行的原因是因為除了Serial只有它能和CMS搭配使用,但自JDK9開始,由于更先進的G1的出現,官方直接取消了單獨指定ParNew的引數-XX:+UseParNewGC,使其并入了CMS收集器,成為它專門處理新生代的組成部分,

而Parallel Old則搭配新生代收集器ParallelScavenge成為名副其實的“吞吐量優先”的搭配組合,

4 ParallelScavenge

ParallelScavenge收集器是面向新生代的垃圾回收器,它和ParNew其實非常類似,使用標記復制演算法并行收集,區別在于二者關注點不同,ParalletScavenge的目標是達到一個可控制的吞吐量(Throughput),更高的吞吐量意味著最大限度的使用處理器的資源來縮短整體的垃圾回收時間,ParalletScavenge有兩個重要引數:

?-XX:MaxGCPauseMillis

收集器將盡力保證記憶體回識訓費的時間不超過用戶設定值,但這是以犧牲吞吐量為代價的,要求用更短的時間來完成垃圾收集,那么系統就需要降低新生代大小,新生代變小了自然垃圾回識訓更加頻繁,每次垃圾回收都有很多必要作業(比如等待所有執行緒到達安全點),那么更頻繁的垃圾回收就導致了整體吞吐量的降低,

?-XX:GCTimeRatioGCTimeRatio

是垃圾收集時間占總時間的比率,換句話說:其表示運行用戶代碼時間是GC運行時間的X倍,比如默認為99,則垃圾收集時間占比應該1/(1+99),這個數越低,運行用戶代碼時間占比越低,

ParallelScavenge收集器還可以通過引數(-XX:+UseAdaptiveSizePolicy)來激活自適應調節策略,激活后,就不需要人工指定新生代的大小(Xmn)、Eden與Survivor區的比例(XX:SurvivorRatio)、晉升年老代物件大小(XX:PretenureSizeThreshold)等細節引數了,虛擬機會根據當前系統的運行情況收集性能監控資訊,動態調整這些引數以提供最合適的停頓時間或者最大的吞吐量,

5 CMS收集器(Concurrent Mark Sweep)

CMS收集器是縮短暫停應用時間(Low Pause)為目標而設計的,最開始CMS僅僅是年老代收集器,后來將ParNew并入作為其年輕代收集器,

相較上述收集器,CMS是第一個無需全程STW而允許部分階段并發執行的收集器,

垃圾回收實際上主要是兩個階段:識別垃圾和回收垃圾,CMS在這兩個階段分別做了努力來降低停頓:

?識別垃圾

CMS將標記程序打散,并將主要的染色標記程序和用戶執行緒同步進行,并通過增量更新方式解決了參考切換帶來的漏標的問題,

?垃圾回收

CMS采用清除演算法,相比復制和整理,清除演算法由于僅處理死亡物件所以不需要任何停頓,

CMS運行步驟

具體來說,CMS整個程序分為4個步驟:

1. 初始標記(Initial Mark)[STW]

初始標記只是標記一下GC Roots能直接關聯到的物件,速度很快,

2. 并發標記(Concurrent Marking)

并發標記階段是標記可回收物件,

3. 重新標記(Remark)[STW]

重新標記階段則是為了修正并發標記期間因用戶程式繼續運作導致標記產生變動的那一部分物件的標記記錄,這個階段暫停時間比初始標記階段稍長一點,但遠比并發標記時間短,

CMS用增量更新來做并發標記,也就是說并發標記程序中,如果某個已經標記為存活的物件增加了對非存活物件的參考,那么將其標記為灰色,然后在重新標記階段將這一部分物件重新掃描,

4. 并發清除(Concurrent Sweep)

清理洗掉掉標記階段判斷的已經死亡的物件,由于不需要移動存活物件,所以這個階段也是可以與用戶執行緒同時并發的,

優點

由于整個程序中消耗最長的并發標記和并發清除程序收集器執行緒都可以與用戶執行緒一起作業,所以,CMS收集器記憶體回收與用戶一起并發執行的,大大減少了暫停時間,

缺點

1. 處理器資源敏感

垃圾回收的執行緒能夠與用戶執行緒同時執行,這樣雖然不會導致STW,但是由于分攤了處理器的計算資源從而導致應用程式變慢,降低了總吞吐量,

2. 記憶體敏感

當垃圾回收和用戶執行緒在同步運行時產生的垃圾,由于已經過了標記階段所以不會標記后清除,這部分垃圾只能等到下一次GC時才會被清除,這就是浮動垃圾問題,

而且由于垃圾回收和用戶執行緒同步運行,所以不能等堆滿了再GC,而是需要預留一部分記憶體來保證GC程序中用戶執行緒仍有可用記憶體,為了降低GC頻率,只能等垃圾攢多一點再觸發GC,那么GC時可供用戶執行緒使用的記憶體就不多了,

如果GC尚未結束用戶執行緒分配記憶體失敗,這個情況叫做“并發失敗”,這時虛擬機會降級使用Serial Old來重新進行一次高吞吐的年老代收集,這樣停頓時間就長了,

線上環境應根據實際情況來調整觸發GC的記憶體使用閾值,該引數為:-XX:CMSInitiatingOccupancyFraction

3. CMS基于標記清除演算法,所以記憶體碎片過多后,會頻繁觸發Full GC,且不可避免,CMS會在若干次觸發后進行一次記憶體碎片的合并整理,記憶體整理程序涉及存活物件的移動,(在Shenandoah和ZGC出現前)無法并發,

6 G1收集器(Garbage First)

G1收集器相比上述垃圾回收器有了里程碑式的創新,它將堆記憶體劃分多個大小相等的獨立區域(Region),并且能建立“停頓時間模型”,使暫停時間可控,并盡量將-XX:MaxGCPauseMillis(默認200ms)作為停頓目標,根據Oracle官網的描述,G1是一個“軟實時”的收集器,只是盡量保證在目標停頓時間內完成垃圾收集作業,但不能確保一定:

It is important to note that G1 is not a real-time collector. It meets the set pause time target with high probability but not absolute certainty.

能預測的原因是它能避免對整個堆進行全區收集,而是將整個堆分為若干個小的區域(Region),每個Region是單次垃圾回收的最小單元,在系統運行程序中,G1跟蹤各個Region里的垃圾堆積價值大小(所獲得空間大小以及回收所需時間),在后臺維護一個優先串列,每次根據允許的收集時間,優先回收價值最大的Region,從而保證了再有限時間內獲得更高的收集效率,這也是Garbage First名稱的由來,

G1的分代模型

G1也分為年輕代和年老代,但不是固定劃分,而是每個Region根據運行情況動態劃分,

G1還有一個特殊的區域叫Humongous,G1將超過了一個Region容量一半的大物件,都存放在Humongous區域中,如果物件超過了Region大小,則存放在N個連續的Humongous Region中,G1的大多數行為都把Humongous Region作為老年代的一部分來進行看待,

TAMS(Top at mark start)

為了保證垃圾回收程序中的同時Region也能夠被使用,G1為每一個Region設計了兩個名為TAMS的指標,分別是Previous TAMS(PTAMS)、Next TAMS(NTAMS),在并發標記階段開始前,TAMS指標指向Region內占用記憶體的邊界,在并發標記階段中,G1默認指標之上的物件為存活物件不去進行標記,而物件分配時,用戶執行緒直接在指標之上分配,這就保證了掃描行為和物件分配互不干擾,

TAMS.png

G1如何判定Region的“價值”

G1運行期間會收集每個Region的價值資訊,比如回收耗時、記憶集的臟卡數量等,通過計算得出每個Region回收的性價比,G1的停頓預測模型就是通過這些資訊,找出在用戶預期時間內獲得更高回收收益的Region組合,

Remembered Sets

G1堆中的每一個Region都有一份Rememberd Set,也叫RSet,它的作用就是為每一個Region記錄哪些Region對其含有參考,

g1-card-table

RSet的更新需要執行緒同步處理,由于物件參考變更非常頻繁,如果同步寫卡表消耗非常大,所以通常會把更新資訊存入佇列中再異步更新RSet,這個佇列就叫Dirty Card Queue

G1的垃圾回收程序

當Eden中無法分配物件時,觸發Young GC,

當年老代占比到達45%時,等待下一次Young GC時進行并發標記,

并發標記結束后馬上執行Mixed GC,

當Mixed GC對記憶體的清理速度趕不上分配新物件的速度時觸發Full GC,G1的Full GC將使用單執行緒(JDK11后改為多執行緒)執行標記整理演算法,所以耗時巨大,

G1的Young GC

觸發時機

當JVM無法在Eden區分配物件時,

回收范圍

Eden區和Survivor區

運行程序(所有階段均STW)

1. 根掃描

將所有Eden區中的GC Root和RSet記錄的外部參考作為掃描存活物件的入口,

2. 更新RSet

通過Dirty Card Queue中的card更新RSet,保證RSet能準確反應老年代對該Region是否存在參考,

3. 處理RSet

將Eden區中被RSet指向的物件標記為存活物件,

4. 物件復制

判斷存活物件的年齡,如果未達到“閾值”,則復制到一個Surviver區中,否則復制到Old區中,如果Surviver空間不夠,則將部分物件直接復制到Old區中,

5. 處理參考

處理軟參考、弱參考、虛參考等,最終清空全部Eden區,這時清理過的記憶體空間沒有記憶體碎片,

G1的Mixed GC

觸發時機

年老代占用空間超過整個堆的45%(可通過引數-XX:InitiatingHeapOccupancyPercent進行設定)

事實上,并不會立刻觸發,而且等待下一次Young GC,同步進行初始標記步驟,

回收范圍

被并發標記過的Region,這些Region是G1通過價值測算動態選中的,

運行程序

1. 初始標記(Initial Marking)[STW]

標記GC Roots直接關聯的物件,并修改TAMS指標的值,值得注意的是,這一階段并不單獨執行,而是在Minor GC時同步完成,所以實際上這個階段沒有額外停頓,

2. 并發標記(Concurrent Marking)

與用戶執行緒并發執行,順著GC Root遞回標記,標記完成后,重新掃描SATB記錄的有參考變動的物件,如果這時發現空的Region則直接將其清空,

3. 重新標記(Remark)[STW]

由于并發標記是并發執行,并發標記結束后,仍然存在少量的參考變動的物件,所以在這個階段可以STW來處理這部分遺留的物件,并且開始計算所有Region的活躍度,

4. 清理(Clean Up)[STW]

根據用戶期望的停頓時間來制定回收計劃,選擇全部是非存活物件的Old區和回收收益較高的Region加入回收集,清空記憶集,重置已經被清理的空的Region(這一步是非STW的),

5. 拷貝(Coping)[STW]

將回收集其中的存活物件復制到空的Region中,最后清空這些舊的Region,

這個階段的演算法和Young GC完全一致,但默認分8次執行完成(可由引數-XX:G1MixedGCCountTarget設定),所以每次清理的回收集包括Eden區、Survivor區和八分之一的Old區,低存活度(垃圾多)的Region清理的較快,所以會被G1優先回收,

混合回收并不一定要進行8次,有一個閾值-XX :G1HeapWastePercent(默認值為10%),意思是允許整個堆記憶體中有10%的空間被浪費,意味著如果發現可以回收的垃圾占堆記憶體的比例低于10%,則不再進行混合回收,

優點

G1相比較之前的垃圾回收器最大的變化是通過化整為零的思路,將堆分為若干個小的Region來減少GC的范圍,從而達到“低延遲”的目的,

并且G1的垃圾回收程序采用標記復制的演算法,避免了空間碎片化的問題,

缺點

1.記憶體占用較高,由于G1磁區比CMS更多,每個Region都需要建立卡表,其中新生代物件變動頻繁,又加大了卡表維護的成本,

2.G1不僅需要通過寫前屏障來更新卡表,還需要寫后屏障來跟蹤并發時的指標變化以實作快照搜索演算法(SATB),這樣雖然相比增量更新演算法能夠減少并發標記和重新標記階段的消耗,但是用戶程式運行時的計算負載就高了,

3.G1和CMS同樣具有“并發回收”的能力,所以垃圾回收的速度如果跟不上用戶創建新物件的速度,那么就會觸發一個Full GC來獲取更多記憶體,通常把期望停頓時間設定為一兩百毫秒或者兩三百毫秒會是比較合理的,

最佳實踐

1.不要設定年輕代大小年輕代大小應當由G1自行控制,設定為固定值將覆寫暫停時間目標

2.暫停時間目標不要過于嚴苛G1為了Young GC能夠縮短時間需要減少Eden區的個數,那么Young GC就會更加頻繁,Mixed GC想要達到停頓目標就需要減少回收的垃圾數量,如果回收速度低于新物件分配速度將引起Full GC,

3.CMS和G1的選擇目前在小記憶體應用上CMS的表現大概率仍然要會優于G1,而在大記憶體應用上G1則大多能發揮其優勢,這個優劣勢的Java堆容量平衡點通常在6GB至8GB之間,

7 總結

在GC的選擇上,同樣是“沒有銀彈”,不同的收集器有著各自的特點和適用場景,即使是Epsilon也會在特定場合下發揮作用,我們應針對不同的業務特征和系統情況選擇最合適的垃圾回收器,而不是一味求新,

參考:

1.《深入理解Java虛擬機》 by 周志明

2.Getting Started with the G1 Garbage Collector

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

標籤:Java

上一篇:Go中的有限狀態機FSM的詳細介紹

下一篇:返回列表

標籤雲
其他(157978) Python(38094) JavaScript(25390) Java(17990) C(15217) 區塊鏈(8259) C#(7972) AI(7469) 爪哇(7425) MySQL(7140) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5328) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4559) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2430) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1960) Web開發(1951) HtmlCss(1923) python-3.x(1918) 弹簧靴(1913) C++(1911) xml(1889) PostgreSQL(1873) .NETCore(1855) 谷歌表格(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
最新发布
  • 從原理聊JVM(二):從串行收集器到磁區收集開創者G1

    隨著Java的進化程序,涌現出各種不同的垃圾回收器,從串行執行到并行執行,從高吞吐到低延遲,終極目標就是讓開發人員專注于程式的代碼書寫而無需關注記憶體管理。 ......

    uj5u.com 2023-04-25 07:41:56 more
  • Go中的有限狀態機FSM的詳細介紹

    1、FSM簡介 1.1 有限狀態機的定義 有限狀態機(Finite State Machine,FSM)是一種數學模型,用于描述系統在不同狀態下的行為和轉移條件。 狀態機有三個組成部分:狀態(State)、事件(Event)、動作(Action),事件(轉移條件)觸發狀態的轉移和動作的執行。動作的執 ......

    uj5u.com 2023-04-25 07:41:31 more
  • Go語言入門11(泛型)

    泛型 問題解決 一個計算sum的函式 func sum(slice []int) int { var res int for _, value := range slice { res += value } return res } ? 如果需要提供對int,float64,string三種資料型別 ......

    uj5u.com 2023-04-25 07:41:22 more
  • C語言實驗報告范例

    實驗報告四 一, 實驗型別:設計型 二, 實驗室: 三, 指導老師: 四, 日期: 五, 實驗名稱:if分支陳述句的嵌套 六, 實驗目的: 1, 學習if嵌套結構,能夠用C語言編程解決日常生活的實體 2, 明確if陳述句在實作分支結構控制陳述句方面的特點和優勢 3, 熟練掌握關系運算子、關系運算式、邏輯運 ......

    uj5u.com 2023-04-25 07:40:54 more
  • 逍遙自在學C語言 | 賦值運算子

    前言 在C語言中,賦值運算子用于將一個值賦給變數 這個程序分為兩個步驟: 計算賦值運算子右側的運算式 將結果賦給左側的變數。 C語言提供了多個不同的賦值運算子,包括基本的賦值運算子、復合賦值運算子以及條件賦值運算子等 一、人物簡介 第一位閃亮登場,有請今后會一直教我們C語言的老師 —— 自在。 第二 ......

    uj5u.com 2023-04-25 07:35:35 more
  • 面試最常問的陣列轉樹,樹轉陣列 c++ web框架paozhu實作

    剛畢業同學,找作業常被問 二維陣列轉樹,樹轉二維陣列 需要支持無限層級實作,如果你了解這個語言那么實作起來還要一番思考 c++ web框架 paozhu使用 需要實作資料庫表資料到前臺選單實作,就是這種功能 二維陣列轉樹,樹轉二維陣列 保存時候樹二維陣列,展示時候樹樹狀。 這個技術難點在于無限遞回, ......

    uj5u.com 2023-04-25 07:25:18 more
  • 吃透Redis面試八股文

    Redis連環40問,絕對夠全! Redis是什么? Redis(Remote Dictionary Server)是一個使用 C 語言撰寫的,高性能非關系型的鍵值對資料庫。與傳統資料庫不同的是,Redis 的資料是存在記憶體中的,所以讀寫速度非常快,被廣泛應用于快取方向。Redis可以將資料寫入磁盤 ......

    uj5u.com 2023-04-24 09:53:06 more
  • 吃透Redis面試八股文

    Redis連環40問,絕對夠全! Redis是什么? Redis(Remote Dictionary Server)是一個使用 C 語言撰寫的,高性能非關系型的鍵值對資料庫。與傳統資料庫不同的是,Redis 的資料是存在記憶體中的,所以讀寫速度非常快,被廣泛應用于快取方向。Redis可以將資料寫入磁盤 ......

    uj5u.com 2023-04-24 09:46:49 more
  • 【Visual Leak Detector】原始碼編譯 VLD 庫

    說明 使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。本篇介紹 VLD 原始碼的編譯。同系列文章目錄可見 《記憶體泄漏檢測工具》目錄 1. VLD 庫的依賴檔案 以 vld2.5.1 版本為例,下載原始碼 后,原始碼包中各檔案的用途可看本人另一篇博客 【VLD】原始碼檔案概覽。使用 vld2.5.1- ......

    uj5u.com 2023-04-24 07:45:41 more
  • 13、c++使用單例模式實作命名空間函式

    本案例實作一個test命名空間,此命名空間內有兩個函式,分別為getName()和getNameSpace(); 宣告命名空間及函式 namespace test{ const std::string& getName()和(); const std::string& getNameSpace(); ......

    uj5u.com 2023-04-24 07:45:36 more