0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

阿里:每天100w次登陸請(qǐng)求, 8G內(nèi)存該如何設(shè)置JVM參數(shù)?

jf_ro2CN3Fa ? 來(lái)源:千淘萬(wàn)漉 ? 2023-03-13 09:44 ? 次閱讀

每天100w次登陸請(qǐng)求, 8G 內(nèi)存該如何設(shè)置JVM參數(shù)?

    • Step1:新系統(tǒng)上線如何規(guī)劃容量?
    • Step2:該如何進(jìn)行垃圾回收器的選擇?
    • Step3:如何對(duì)各個(gè)分區(qū)的比例、大小進(jìn)行規(guī)劃
    • step4:棧內(nèi)存大小多少比較合適?
    • step5:對(duì)象年齡應(yīng)該為多少才移動(dòng)到老年代比較合適?
    • step6:多大的對(duì)象,可以直接到老年代比較合適?
    • step7:垃圾回收器CMS老年代的參數(shù)優(yōu)化
    • step8:配置OOM時(shí)候的內(nèi)存dump文件和GC日志
    • 調(diào)優(yōu)總結(jié)
  • 什么是ZGC?
  • 如何選擇垃圾收集器?
  • Hotspot為什么使用元空間替換了永久代?
  • 什么是Stop The World ? 什么是OopMap?什么是安全點(diǎn)?
d29eff8c-c02f-11ed-bfe3-dac502259ad0.jpg

在阿里云技術(shù)面終面的時(shí)候被問到這么一個(gè)問題:假設(shè)一個(gè)每天100w次登陸請(qǐng)求的平臺(tái),一個(gè)服務(wù)節(jié)點(diǎn) 8G 內(nèi)存,該如何設(shè)置JVM參數(shù)?

下面以面試題的形式給大家梳理出來(lái),做到一箭雙雕:

  • 既供大家實(shí)操參考
  • 又供大家面試參考

大家要學(xué)習(xí)的,除了 JVM 配置方案 之外,是其 分析問題的思路、思考問題的視角。這些思路和視角,能幫助大家走更遠(yuǎn)、更遠(yuǎn)。

接下來(lái),進(jìn)入正題。

每天100w次登陸請(qǐng)求, 8G 內(nèi)存該如何設(shè)置JVM參數(shù)?

每天100w次登陸請(qǐng)求, 8G 內(nèi)存該如何設(shè)置JVM參數(shù),大概可以分為以下8個(gè)步驟 。

Step1:新系統(tǒng)上線如何規(guī)劃容量?

1.套路總結(jié)

任何新的業(yè)務(wù)系統(tǒng)在上線以前都需要去估算服務(wù)器配置和JVM的內(nèi)存參數(shù),這個(gè)容量與資源規(guī)劃并不僅僅是系統(tǒng)架構(gòu)師的隨意估算的,需要根據(jù)系統(tǒng)所在業(yè)務(wù)場(chǎng)景去估算,推斷出來(lái)一個(gè)系統(tǒng)運(yùn)行模型,評(píng)估JVM性能和GC頻率等等指標(biāo)。以下是我結(jié)合大牛經(jīng)驗(yàn)以及自身實(shí)踐來(lái)總結(jié)出來(lái)的一個(gè)建模步驟:

  • 計(jì)算業(yè)務(wù)系統(tǒng)每秒鐘創(chuàng)建的對(duì)象會(huì)佔(zhàn)用多大的內(nèi)存空間,然后計(jì)算集群下的每個(gè)系統(tǒng)每秒的內(nèi)存佔(zhàn)用空間(對(duì)象創(chuàng)建速度)
  • 設(shè)置一個(gè)機(jī)器配置,估算新生代的空間,比較不同新生代大小之下,多久觸發(fā)一次MinorGC。
  • 為了避免頻繁GC,就可以重新估算需要多少機(jī)器配置,部署多少臺(tái)機(jī)器,給JVM多大內(nèi)存空間,新生代多大空間。
  • 根據(jù)這套配置,基本可以推算出整個(gè)系統(tǒng)的運(yùn)行模型,每秒創(chuàng)建多少對(duì)象,1s以后成為垃圾,系統(tǒng)運(yùn)行多久新生代會(huì)觸發(fā)一次GC,頻率多高。

2.套路實(shí)戰(zhàn)——以登錄系統(tǒng)為例

有些同學(xué)看到這些步驟還是發(fā)憷,說(shuō)的好像是那么回事,一到實(shí)際項(xiàng)目中到底怎麼做我還是不知道!

光說(shuō)不練假把式,以登錄系統(tǒng)為例模擬一下推演過(guò)程:

  • 假設(shè)每天100w次登陸請(qǐng)求,登陸峰值在早上,預(yù)估峰值時(shí)期每秒100次登陸請(qǐng)求。
  • 假設(shè)部署3臺(tái)服務(wù)器,每臺(tái)機(jī)器每秒處理30次登陸請(qǐng)求,假設(shè)一個(gè)登陸請(qǐng)求需要處理1秒鐘,JVM新生代里每秒就要生成30個(gè)登陸對(duì)象,1s之后請(qǐng)求完畢這些對(duì)象成為了垃圾。
  • 一個(gè)登陸請(qǐng)求對(duì)象假設(shè)20個(gè)字段,一個(gè)對(duì)象估算500字節(jié),30個(gè)登陸佔(zhàn)用大約15kb,考慮到RPC和DB操作,網(wǎng)絡(luò)通信、寫庫(kù)、寫緩存一頓操作下來(lái),可以擴(kuò)大到20-50倍,大約1s產(chǎn)生幾百k-1M數(shù)據(jù)。
  • 假設(shè)2C4G機(jī)器部署,分配2G堆內(nèi)存,新生代則只有幾百M(fèi),按照1s1M的垃圾產(chǎn)生速度,幾百秒就會(huì)觸發(fā)一次MinorGC了。
  • 假設(shè)4C8G機(jī)器部署,分配4G堆內(nèi)存,新生代分配2G,如此需要幾個(gè)小時(shí)才會(huì)觸發(fā)一次MinorGC。

所以,可以粗略的推斷出來(lái)一個(gè)每天100w次請(qǐng)求的登錄系統(tǒng),按照4C8G的3實(shí)例集群配置,分配4G堆內(nèi)存、2G新生代的JVM,可以保障系統(tǒng)的一個(gè)正常負(fù)載。

基本上把一個(gè)新系統(tǒng)的資源評(píng)估了出來(lái),所以搭建新系統(tǒng)要每個(gè)實(shí)例需要多少容量多少配置,集群配置多少個(gè)實(shí)例等等這些,并不是拍拍腦袋和胸脯就可以決定的下來(lái)的。

Step2:該如何進(jìn)行垃圾回收器的選擇?

吞吐量還是響應(yīng)時(shí)間

首先引入兩個(gè)概念:吞吐量和低延遲

吞吐量 = CPU在用戶應(yīng)用程序運(yùn)行的時(shí)間 / (CPU在用戶應(yīng)用程序運(yùn)行的時(shí)間 + CPU垃圾回收的時(shí)間)

響應(yīng)時(shí)間 = 平均每次的GC的耗時(shí)

通常,吞吐優(yōu)先還是響應(yīng)優(yōu)先這個(gè)在JVM中是一個(gè)兩難之選。

堆內(nèi)存增大,gc一次能處理的數(shù)量變大,吞吐量大;但是gc一次的時(shí)間會(huì)變長(zhǎng),導(dǎo)致后面排隊(duì)的線程等待時(shí)間變長(zhǎng);相反,如果堆內(nèi)存小,gc一次時(shí)間短,排隊(duì)等待的線程等待時(shí)間變短,延遲減少,但一次請(qǐng)求的數(shù)量變?。ú⒉唤^對(duì)符合)。

無(wú)法同時(shí)兼顧,是吞吐優(yōu)先還是響應(yīng)優(yōu)先,這是一個(gè)需要權(quán)衡的問題。

垃圾回收器設(shè)計(jì)上的考量

  • JVM在GC時(shí)不允許一邊垃圾回收,一邊還創(chuàng)建新對(duì)象(就像不能一邊打掃衛(wèi)生,還在一邊扔垃圾)。
  • JVM需要一段Stop the world的暫停時(shí)間,而STW會(huì)造成系統(tǒng)短暫停頓不能處理任何請(qǐng)求;
  • 新生代收集頻率高,性能優(yōu)先,常用復(fù)制算法;老年代頻次低,空間敏感,避免復(fù)制方式。
  • 所有垃圾回收器的涉及目標(biāo)都是要讓GC頻率更少,時(shí)間更短,減少GC對(duì)系統(tǒng)影響!

CMS和G1

目前主流的垃圾回收器配置是新生代采用ParNew,老年代采用CMS組合的方式,或者是完全采用G1回收器,

從未來(lái)的趨勢(shì)來(lái)看,G1是官方維護(hù)和更為推崇的垃圾回收器。

d2b1306c-c02f-11ed-bfe3-dac502259ad0.jpg

業(yè)務(wù)系統(tǒng):

  • 延遲敏感的推薦CMS;
  • 大內(nèi)存服務(wù),要求高吞吐的,采用G1回收器!

CMS垃圾回收器的工作機(jī)制

CMS主要是針對(duì)老年代的回收器,老年代是標(biāo)記-清除,默認(rèn)會(huì)在一次FullGC算法后做整理算法,清理內(nèi)存碎片。

CMS GC 描述 Stop the world 速度
1.開始標(biāo)記 初始標(biāo)記僅標(biāo)記GCRoots能直接關(guān)聯(lián)到的對(duì)象,速度很快 Yes 很快
2.并發(fā)標(biāo)記 并發(fā)標(biāo)記階段就是進(jìn)行GCRoots Tracing的過(guò)程 No
3.重新標(biāo)記 重新標(biāo)記階段則是為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄。 Yes 很快
4.垃圾回收 并發(fā)清理垃圾對(duì)象(標(biāo)記清除算法) No
  • 優(yōu)點(diǎn):并發(fā)收集、主打“低延時(shí)” 。在最耗時(shí)的兩個(gè)階段都沒有發(fā)生STW,而需要STW的階段都以很快速度完成。
  • 缺點(diǎn):1、消耗CPU;2、浮動(dòng)垃圾;3、內(nèi)存碎片
  • 適用場(chǎng)景:重視服務(wù)器響應(yīng)速度,要求系統(tǒng)停頓時(shí)間最短。

總之:

業(yè)務(wù)系統(tǒng),延遲敏感的推薦CMS;

大內(nèi)存服務(wù),要求高吞吐的,采用G1回收器!

Step3:如何對(duì)各個(gè)分區(qū)的比例、大小進(jìn)行規(guī)劃

一般的思路為:

首先,JVM最重要最核心的參數(shù)是去評(píng)估內(nèi)存和分配,第一步需要指定堆內(nèi)存的大小,這個(gè)是系統(tǒng)上線必須要做的,-Xms 初始堆大小,-Xmx 最大堆大小,后臺(tái)Java服務(wù)中一般都指定為系統(tǒng)內(nèi)存的一半,過(guò)大會(huì)佔(zhàn)用服務(wù)器的系統(tǒng)資源,過(guò)小則無(wú)法發(fā)揮JVM的最佳性能。

其次,需要指定-Xmn新生代的大小,這個(gè)參數(shù)非常關(guān)鍵,靈活度很大,雖然sun官方推薦為3/8大小,但是要根據(jù)業(yè)務(wù)場(chǎng)景來(lái)定,針對(duì)于無(wú)狀態(tài)或者輕狀態(tài)服務(wù)(現(xiàn)在最常見的業(yè)務(wù)系統(tǒng)如Web應(yīng)用)來(lái)說(shuō),一般新生代甚至可以給到堆內(nèi)存的3/4大?。魂P(guān)注公z號(hào):碼猿技術(shù)專欄,回復(fù)關(guān)鍵詞:1111 獲取阿里內(nèi)部java性能調(diào)優(yōu)手冊(cè)!而對(duì)于有狀態(tài)服務(wù)(常見如IM服務(wù)、網(wǎng)關(guān)接入層等系統(tǒng))新生代可以按照默認(rèn)比例1/3來(lái)設(shè)置。服務(wù)有狀態(tài),則意味著會(huì)有更多的本地緩存和會(huì)話狀態(tài)信息常駐內(nèi)存,應(yīng)為要給老年代設(shè)置更大的空間來(lái)存放這些對(duì)象。

最后,是設(shè)置-Xss棧內(nèi)存大小,設(shè)置單個(gè)線程棧大小,默認(rèn)值和JDK版本、系統(tǒng)有關(guān),一般默認(rèn)512~1024kb。一個(gè)后臺(tái)服務(wù)如果常駐線程有幾百個(gè),那麼棧內(nèi)存這邊也會(huì)佔(zhàn)用了幾百M(fèi)的大小。

JVM參數(shù) 描述 默認(rèn) 推薦
-Xms Java堆內(nèi)存的大小 OS內(nèi)存64/1 OS內(nèi)存一半
-Xmx Java堆內(nèi)存的最大大小 OS內(nèi)存4/1 OS內(nèi)存一半
-Xmn Java堆內(nèi)存中的新生代大小,扣除新生代剩下的就是老年代的內(nèi)存大小了 跌認(rèn)堆的1/3 sun推薦3/8
-Xss 每個(gè)線程的棧內(nèi)存大小 和idk有關(guān) sun

對(duì)于8G內(nèi)存,一般分配一半的最大內(nèi)存就可以了,因?yàn)闄C(jī)器本上還要占用一定內(nèi)存,一般是分配4G內(nèi)存給JVM,

引入性能壓測(cè)環(huán)節(jié),測(cè)試同學(xué)對(duì)登錄接口壓至1s內(nèi)60M的對(duì)象生成速度,采用ParNew+CMS的組合回收器,

正常的JVM參數(shù)配置如下:

-Xms3072M-Xmx3072M-Xss1M-XX:MetaspaceSize=256M-XX:MaxMetaspaceSize=256M-XX:SurvivorRatio=8

這樣設(shè)置可能會(huì)由于動(dòng)態(tài)對(duì)象年齡判斷原則 導(dǎo)致頻繁full gc。為啥呢?

壓測(cè)過(guò)程中,短時(shí)間(比如20S后)Eden區(qū)就滿了,此時(shí)再運(yùn)行的時(shí)候?qū)ο笠呀?jīng)無(wú)法分配,會(huì)觸發(fā)MinorGC,

假設(shè)在這次GC后S1裝入100M,馬上過(guò)20S又會(huì)觸發(fā)一次MinorGC,多出來(lái)的100M存活對(duì)象+S1區(qū)的100M已經(jīng)無(wú)法順利放入到S2區(qū),此時(shí)就會(huì)觸發(fā)JVM的動(dòng)態(tài)年齡機(jī)制,將一批100M左右的對(duì)象推到老年代保存,持續(xù)運(yùn)行一段時(shí)間,系統(tǒng)可能一個(gè)小時(shí)候內(nèi)就會(huì)觸發(fā)一次FullGC。

按照默認(rèn)81的比例來(lái)分配時(shí), survivor區(qū)只有 1G的 10%左右,也就是幾十到100M,

如果 每次minor GC垃圾回收過(guò)后進(jìn)入survivor對(duì)象很多,并且survivor對(duì)象大小很快超過(guò) Survivor 的 50% , 那么會(huì)觸發(fā)動(dòng)態(tài)年齡判定規(guī)則,讓部分對(duì)象進(jìn)入老年代.

而一個(gè)GC過(guò)程中,可能部分WEB請(qǐng)求未處理完畢, 幾十兆對(duì)象,進(jìn)入survivor的概率,是非常大的,甚至是一定會(huì)發(fā)生的.

如何解決這個(gè)問題呢?為了讓對(duì)象盡可能的在新生代的eden區(qū)和survivor區(qū), 盡可能的讓survivor區(qū)內(nèi)存多一點(diǎn),達(dá)到200兆左右,

于是我們可以更新下JVM參數(shù)設(shè)置:

-Xms3072M-Xmx3072M-Xmn2048M-Xss1M-XX:MetaspaceSize=256M-XX:MaxMetaspaceSize=256M-XX:SurvivorRatio=8

說(shuō)明:
‐Xmn2048M‐XX:SurvivorRatio=8
年輕代大小2g,eden與survivor的比例為8:1:1,也就是1.6g:0.2g:0.2g
d2ca57b8-c02f-11ed-bfe3-dac502259ad0.jpg

survivor達(dá)到200m,如果幾十兆對(duì)象到底survivor, survivor 也不一定超過(guò) 50%

這樣可以防止每次垃圾回收過(guò)后,survivor對(duì)象太早超過(guò) 50% ,

這樣就降低了因?yàn)閷?duì)象動(dòng)態(tài)年齡判斷原則導(dǎo)致的對(duì)象頻繁進(jìn)入老年代的問題,

什么是JVM動(dòng)態(tài)年齡判斷規(guī)則呢?

對(duì)象進(jìn)入老年代的動(dòng)態(tài)年齡判斷規(guī)則 (動(dòng)態(tài)晉升年齡計(jì)算閾值):Minor GC 時(shí),Survivor 中年齡 1 到 N 的對(duì)象大小超過(guò) Survivor 的 50% 時(shí),則將大于等于年齡 N 的對(duì)象放入老年代。

核心的優(yōu)化策略是:是讓短期存活的對(duì)象盡量都留在survivor里,不要進(jìn)入老年代,這樣在minor gc的時(shí)候這些對(duì)象都會(huì)被回收,不會(huì)進(jìn)到老年代從而導(dǎo)致full gc 。

應(yīng)該如何去評(píng)估新生代內(nèi)存和分配合適?

這里特別說(shuō)一下,JVM最重要最核心的參數(shù)是去評(píng)估內(nèi)存和分配,

第一步需要指定堆內(nèi)存的大小,這個(gè)是系統(tǒng)上線必須要做的,-Xms 初始堆大小,-Xmx 最大堆大小,

后臺(tái)Java服務(wù)中一般都指定為系統(tǒng)內(nèi)存的一半,過(guò)大會(huì)佔(zhàn)用服務(wù)器的系統(tǒng)資源,過(guò)小則無(wú)法發(fā)揮JVM的最佳性能。

其次需要指定-Xmn新生代的大小,這個(gè)參數(shù)非常關(guān)鍵,靈活度很大,雖然sun官方推薦為3/8大小,但是要根據(jù)業(yè)務(wù)場(chǎng)景來(lái)定:

  • 針對(duì)于無(wú)狀態(tài)或者輕狀態(tài)服務(wù)(現(xiàn)在最常見的業(yè)務(wù)系統(tǒng)如Web應(yīng)用)來(lái)說(shuō),一般新生代甚至可以給到堆內(nèi)存的3/4大小;
  • 而對(duì)于有狀態(tài)服務(wù)(常見如IM服務(wù)、網(wǎng)關(guān)接入層等系統(tǒng))新生代可以按照默認(rèn)比例1/3來(lái)設(shè)置。

服務(wù)有狀態(tài),則意味著會(huì)有更多的本地緩存和會(huì)話狀態(tài)信息常駐內(nèi)存,應(yīng)為要給老年代設(shè)置更大的空間來(lái)存放這些對(duì)象。

step4:棧內(nèi)存大小多少比較合適?

-Xss棧內(nèi)存大小,設(shè)置單個(gè)線程棧大小,默認(rèn)值和JDK版本、系統(tǒng)有關(guān),一般默認(rèn)512~1024kb。一個(gè)后臺(tái)服務(wù)如果常駐線程有幾百個(gè),那麼棧內(nèi)存這邊也會(huì)佔(zhàn)用了幾百M(fèi)的大小。

step5:對(duì)象年齡應(yīng)該為多少才移動(dòng)到老年代比較合適?

假設(shè)一次minor gc要間隔二三十秒,并且,大多數(shù)對(duì)象一般在幾秒內(nèi)就會(huì)變?yōu)槔?/p>

如果對(duì)象這么長(zhǎng)時(shí)間都沒被回收,比如2分鐘沒有回收,可以認(rèn)為這些對(duì)象是會(huì)存活的比較長(zhǎng)的對(duì)象,從而移動(dòng)到老年代,而不是繼續(xù)一直占用survivor區(qū)空間。

所以,可以將默認(rèn)的15歲改小一點(diǎn),比如改為5,

那么意味著對(duì)象要經(jīng)過(guò)5次minor gc才會(huì)進(jìn)入老年代,整個(gè)時(shí)間也有一兩分鐘了(5*30s= 150s),和幾秒的時(shí)間相比,對(duì)象已經(jīng)存活了足夠長(zhǎng)時(shí)間了。

所以:可以適當(dāng)調(diào)整JVM參數(shù)如下:

‐Xms3072M‐Xmx3072M‐Xmn2048M‐Xss1M‐XX:MetaspaceSize=256M‐XX:MaxMetaspaceSize=256M‐XX:SurvivorRatio=8‐XX:MaxTenuringThreshold=5

step6:多大的對(duì)象,可以直接到老年代比較合適?

對(duì)于多大的對(duì)象直接進(jìn)入老年代(參數(shù)-XX:PretenureSizeThreshold),一般可以結(jié)合自己系統(tǒng)看下有沒有什么大對(duì)象 生成,預(yù)估下大對(duì)象的大小,一般來(lái)說(shuō)設(shè)置為1M就差不多了,很少有超過(guò)1M的大對(duì)象,

所以:可以適當(dāng)調(diào)整JVM參數(shù)如下:

‐Xms3072M‐Xmx3072M‐Xmn2048M‐Xss1M‐XX:MetaspaceSize=256M‐XX:MaxMetaspaceSize=256M‐XX:SurvivorRatio=8‐XX:MaxTenuringThreshold=5‐XX:PretenureSizeThreshold=1M

step7:垃圾回收器CMS老年代的參數(shù)優(yōu)化

JDK8默認(rèn)的垃圾回收器是-XX:+UseParallelGC(年輕代)和-XX:+UseParallelOldGC(老年代),

如果內(nèi)存較大(超過(guò)4個(gè)G,只是經(jīng)驗(yàn) 值),還是建議使用G1.

這里是4G以內(nèi),又是主打“低延時(shí)” 的業(yè)務(wù)系統(tǒng),可以使用下面的組合:

ParNew+CMS(-XX:+UseParNewGC-XX:+UseConcMarkSweepGC)

新生代的采用ParNew回收器,工作流程就是經(jīng)典復(fù)制算法,在三塊區(qū)中進(jìn)行流轉(zhuǎn)回收,只不過(guò)采用多線程并行的方式加快了MinorGC速度。

老生代的采用CMS。再去優(yōu)化老年代參數(shù) :比如老年代默認(rèn)在標(biāo)記清除以后會(huì)做整理,還可以在CMS的增加GC頻次還是增加GC時(shí)長(zhǎng)上做些取舍,

如下是響應(yīng)優(yōu)先的參數(shù)調(diào)優(yōu):

XX:CMSInitiatingOccupancyFraction=70

設(shè)定CMS在對(duì)內(nèi)存占用率達(dá)到70%的時(shí)候開始GC(因?yàn)镃MS會(huì)有浮動(dòng)垃圾,所以一般都較早啟動(dòng)GC)

XX:+UseCMSInitiatinpOccupancyOnly

和上面搭配使用,否則只生效一次

-XX:+AlwaysPreTouch

強(qiáng)制操作系統(tǒng)把內(nèi)存真正分配給IVM,而不是用時(shí)才分配。

綜上,只要年輕代參數(shù)設(shè)置合理,老年代CMS的參數(shù)設(shè)置基本都可以用默認(rèn)值,如下所示:

‐Xms3072M‐Xmx3072M‐Xmn2048M‐Xss1M‐XX:MetaspaceSize=256M‐XX:MaxMetaspaceSize=256M‐XX:SurvivorRatio=8‐XX:MaxTenuringThreshold=5‐XX:PretenureSizeThreshold=1M‐XX:+UseParNewGC‐XX:+UseConcMarkSweepGC‐XX:CMSInitiatingOccupancyFraction=70‐XX:+UseCMSInitiatingOccupancyOnly‐XX:+AlwaysPreTouch

參數(shù)解釋

1.‐Xms3072M ‐Xmx3072M 最小最大堆設(shè)置為3g,最大最小設(shè)置為一致防止內(nèi)存抖動(dòng)

2.‐Xss1M 線程棧1m

3.‐Xmn2048M ‐XX:SurvivorRatio=8 年輕代大小2g,eden與survivor的比例為81,也就是1.6g0.2g

4.-XX:MaxTenuringThreshold=5 年齡為5進(jìn)入老年代 5.‐XX:PretenureSizeThreshold=1M 大于1m的大對(duì)象直接在老年代生成

6.‐XX:+UseParNewGC ‐XX:+UseConcMarkSweepGC 使用ParNew+cms垃圾回收器組合

7.‐XX:CMSInitiatingOccupancyFraction=70 老年代中對(duì)象達(dá)到這個(gè)比例后觸發(fā)fullgc

8.‐XX:+UseCMSInitiatinpOccupancyOnly 老年代中對(duì)象達(dá)到這個(gè)比例后觸發(fā)fullgc,每次

9.‐XX:+AlwaysPreTouch 強(qiáng)制操作系統(tǒng)把內(nèi)存真正分配給IVM,而不是用時(shí)才分配。

step8:配置OOM時(shí)候的內(nèi)存dump文件和GC日志

額外增加了GC日志打印、OOM自動(dòng)dump等配置內(nèi)容,幫助進(jìn)行問題排查

-XX:+HeapDumpOnOutOfMemoryError

在Out Of Memory,JVM快死掉的時(shí)候,輸出Heap Dump到指定文件。

不然開發(fā)很多時(shí)候還真不知道怎么重現(xiàn)錯(cuò)誤。

路徑只指向目錄,JVM會(huì)保持文件名的唯一性,叫java_pid${pid}.hprof。

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=${LOGDIR}/

因?yàn)槿绻赶蛱囟ǖ奈募募汛嬖?,反而不能寫入?/p>

輸出4G的HeapDump,會(huì)導(dǎo)致IO性能問題,在普通硬盤上,會(huì)造成20秒以上的硬盤IO跑滿,

需要注意一下,但在容器環(huán)境下,這個(gè)也會(huì)影響同一宿主機(jī)上的其他容器。

GC的日志的輸出也很重要:

-Xloggc:/dev/xxx/gc.log
-XX:+PrintGCDateStamps
-XX:+PrintGCDetails

GC的日志實(shí)際上對(duì)系統(tǒng)性能影響不大,打日志對(duì)排查GC問題很重要。

一份通用的JVM參數(shù)模板

一般來(lái)說(shuō),大企業(yè)或者架構(gòu)師團(tuán)隊(duì),都會(huì)為項(xiàng)目的業(yè)務(wù)系統(tǒng)定制一份較為通用的JVM參數(shù)模板,但是許多小企業(yè)和團(tuán)隊(duì)可能就疏于這一塊的設(shè)計(jì),如果老板某一天突然讓你負(fù)責(zé)定制一個(gè)新系統(tǒng)的JVM參數(shù),你上網(wǎng)去搜大量的JVM調(diào)優(yōu)文章或博客,結(jié)果發(fā)現(xiàn)都是零零散散的、不成體系的JVM參數(shù)講解,根本下不了手,這個(gè)時(shí)候你就需要一份較為通用的JVM參數(shù)模板了,不能保證性能最佳,但是至少能讓JVM這一層是穩(wěn)定可控的,

在這里給大家總結(jié)了一份模板:

基于4C8G系統(tǒng)的ParNew+CMS回收器模板(響應(yīng)優(yōu)先),新生代大小根據(jù)業(yè)務(wù)靈活調(diào)整!

-Xms4g
-Xmx4g
-Xmn2g
-Xss1m
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=10
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+AlwaysPreTouch
-XX:+HeapDumpOnOutOfMemoryError
-verbose:gc
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-Xloggc:gc.log

如果是GC的吞吐優(yōu)先,推薦使用G1,基于8C16G系統(tǒng)的G1回收器模板:

G1收集器自身已經(jīng)有一套預(yù)測(cè)和調(diào)整機(jī)制了,因此我們首先的選擇是相信它,

即調(diào)整-XX:MaxGCPauseMillis=N參數(shù),這也符合G1的目的——讓GC調(diào)優(yōu)盡量簡(jiǎn)單!

同時(shí)也不要自己顯式設(shè)置新生代的大小(用-Xmn或-XX:NewRatio參數(shù)),

如果人為干預(yù)新生代的大小,會(huì)導(dǎo)致目標(biāo)時(shí)間這個(gè)參數(shù)失效。

-Xms8g
-Xmx8g
-Xss1m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=150
-XX:InitiatingHeapOccupancyPercent=40
-XX:+HeapDumpOnOutOfMemoryError
-verbose:gc
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-Xloggc:gc.log
G1參數(shù) 描述 默認(rèn)值
XX:MaxGCPauseMillis=N 最大GC停頓時(shí)間。柔性目標(biāo),JVM滿足90%,不保證100%。 200
-XX:nitiatingHeapOccupancyPercent=n 當(dāng)整個(gè)堆的空間使用百分比超過(guò)這個(gè)值時(shí),就會(huì)融發(fā)MixGC 45

針對(duì)-XX:MaxGCPauseMillis來(lái)說(shuō),參數(shù)的設(shè)置帶有明顯的傾向性:調(diào)低↓:延遲更低,但MinorGC頻繁,MixGC回收老年代區(qū)減少,增大Full GC的風(fēng)險(xiǎn)。調(diào)高↑:?jiǎn)未位厥崭嗟膶?duì)象,但系統(tǒng)整體響應(yīng)時(shí)間也會(huì)被拉長(zhǎng)。

針對(duì)InitiatingHeapOccupancyPercent來(lái)說(shuō),調(diào)參大小的效果也不一樣:調(diào)低↓:更早觸發(fā)MixGC,浪費(fèi)cpu。調(diào)高↑:堆積過(guò)多代回收region,增大FullGC的風(fēng)險(xiǎn)。

調(diào)優(yōu)總結(jié)

系統(tǒng)在上線前的綜合調(diào)優(yōu)思路:

1、業(yè)務(wù)預(yù)估:根據(jù)預(yù)期的并發(fā)量、平均每個(gè)任務(wù)的內(nèi)存需求大小,然后評(píng)估需要幾臺(tái)機(jī)器來(lái)承載,每臺(tái)機(jī)器需要什么樣的配置。

2、容量預(yù)估:根據(jù)系統(tǒng)的任務(wù)處理速度,然后合理分配Eden、Surivior區(qū)大小,老年代的內(nèi)存大小。

3、回收器選型:響應(yīng)優(yōu)先的系統(tǒng),建議采用ParNew+CMS回收器;吞吐優(yōu)先、多核大內(nèi)存(heap size≥8G)服務(wù),建議采用G1回收器。

4、優(yōu)化思路:讓短命對(duì)象在MinorGC階段就被回收(同時(shí)回收后的存活對(duì)象

5、到目前為止,總結(jié)到的調(diào)優(yōu)的過(guò)程主要基于上線前的測(cè)試驗(yàn)證階段,所以我們盡量在上線之前,就將機(jī)器的JVM參數(shù)設(shè)置到最優(yōu)!

JVM調(diào)優(yōu)只是一個(gè)手段,但并不一定所有問題都可以通過(guò)JVM進(jìn)行調(diào)優(yōu)解決,大多數(shù)的Java應(yīng)用不需要進(jìn)行JVM優(yōu)化,我們可以遵循以下的一些原則:

  • 上線之前,應(yīng)先考慮將機(jī)器的JVM參數(shù)設(shè)置到最優(yōu);
  • 減少創(chuàng)建對(duì)象的數(shù)量(代碼層面);
  • 減少使用全局變量和大對(duì)象(代碼層面);
  • 優(yōu)先架構(gòu)調(diào)優(yōu)和代碼調(diào)優(yōu),JVM優(yōu)化是不得已的手段(代碼、架構(gòu)層面);
  • 分析GC情況優(yōu)化代碼比優(yōu)化JVM參數(shù)更好(代碼層面);

通過(guò)以上原則,我們發(fā)現(xiàn),其實(shí)最有效的優(yōu)化手段是架構(gòu)和代碼層面的優(yōu)化,而JVM優(yōu)化則是最后不得已的手段,也可以說(shuō)是對(duì)服務(wù)器配置的最后一次“壓榨”。

基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

  • 項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 視頻教程:https://doc.iocoder.cn/video/

什么是ZGC?

ZGC (Z Garbage Collector)是一款由Oracle公司研發(fā)的,以低延遲為首要目標(biāo)的一款垃圾收集器。

它是基于動(dòng)態(tài)Region內(nèi)存布局,(暫時(shí))不設(shè)年齡分代,使用了讀屏障、染色指針和內(nèi)存多重映射等技術(shù)來(lái)實(shí)現(xiàn)可并發(fā)的標(biāo)記-整理算法的收集器。

在 JDK 11 新加入,還在實(shí)驗(yàn)階段,

主要特點(diǎn)是:回收TB級(jí)內(nèi)存(最大4T),停頓時(shí)間不超過(guò)10ms。

優(yōu)點(diǎn):低停頓,高吞吐量, ZGC 收集過(guò)程中額外耗費(fèi)的內(nèi)存小

缺點(diǎn):浮動(dòng)垃圾

目前使用的非常少,真正普及還是需要寫時(shí)間的。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

  • 項(xiàng)目地址:https://github.com/YunaiV/yudao-cloud
  • 視頻教程:https://doc.iocoder.cn/video/

如何選擇垃圾收集器?

在真實(shí)場(chǎng)景中應(yīng)該如何去選擇呢,下面給出幾種建議,希望對(duì)你有幫助:

1、如果你的堆大小不是很大(比如 100MB ),選擇串行收集器一般是效率最高的。參數(shù):-XX:+UseSerialGC 。

2、如果你的應(yīng)用運(yùn)行在單核的機(jī)器上,或者你的虛擬機(jī)核數(shù)只有 單核,選擇串行收集器依然是合適的,這時(shí)候啟用一些并行收集器沒有任何收益。參數(shù):-XX:+UseSerialGC 。

3、如果你的應(yīng)用是“吞吐量”優(yōu)先的,并且對(duì)較長(zhǎng)時(shí)間的停頓沒有什么特別的要求。選擇并行收集器是比較好的。參數(shù):-XX:+UseParallelGC 。

4、如果你的應(yīng)用對(duì)響應(yīng)時(shí)間要求較高,想要較少的停頓。甚至 1 秒的停頓都會(huì)引起大量的請(qǐng)求失敗,那么選擇 G1 、 ZGC 、 CMS 都是合理的。雖然這些收集器的 GC 停頓通常都比較短,但它需要一些額外的資源去處理這些工作,通常吞吐量會(huì)低一些。參數(shù):-XX:+UseConcMarkSweepGC 、 -XX:+UseG1GC 、 -XX:+UseZGC 等。從上面這些出發(fā)點(diǎn)來(lái)看,我們平常的 Web 服務(wù)器,都是對(duì)響應(yīng)性要求非常高的。

選擇性其實(shí)就集中在 CMS、G1、ZGC 上。而對(duì)于某些定時(shí)任務(wù),使用并行收集器,是一個(gè)比較好的選擇。

Hotspot為什么使用元空間替換了永久代?

什么是元空間?什么是永久代?為什么用元空間代替永久代?

我們先回顧一下方法區(qū) 吧,看看虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)內(nèi)存圖,如下:

d2dfdb9c-c02f-11ed-bfe3-dac502259ad0.jpg

方法區(qū)和堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯后的代碼等數(shù)據(jù)。

什么是永久代?它和方法區(qū)有什么關(guān)系呢?

如果在HotSpot虛擬機(jī)上開發(fā)、部署,很多程序員都把方法區(qū)稱作永久代。

可以說(shuō)方法區(qū)是規(guī)范,永久代是Hotspot針對(duì)該規(guī)范進(jìn)行的實(shí)現(xiàn)。

在Java7及以前的版本,方法區(qū)都是永久代實(shí)現(xiàn)的。

什么是元空間?它和方法區(qū)有什么關(guān)系呢?

對(duì)于Java8,HotSpots取消了永久代,取而代之的是元空間(Metaspace)。

換句話說(shuō),就是方法區(qū)還是在的,只是實(shí)現(xiàn)變了,從永久代變?yōu)樵臻g了。

為什么使用元空間替換了永久代?

永久代的方法區(qū),和堆使用的物理內(nèi)存是連續(xù)的。

d2ee07e4-c02f-11ed-bfe3-dac502259ad0.jpg

永久代 是通過(guò)以下這兩個(gè)參數(shù)配置大小的~

  • -XX:PremSize:設(shè)置永久代的初始大小
  • -XX:MaxPermSize: 設(shè)置永久代的最大值,默認(rèn)是64M

對(duì)于永久代 ,如果動(dòng)態(tài)生成很多class的話,就很可能出現(xiàn)java.lang.OutOfMemoryError:PermGen space錯(cuò)誤 ,因?yàn)橛谰么臻g配置有限嘛。最典型的場(chǎng)景是,在web開發(fā)比較多jsp頁(yè)面的時(shí)候。

JDK8之后,方法區(qū)存在于元空間(Metaspace)。

物理內(nèi)存不再與堆連續(xù),而是直接存在于本地內(nèi)存中,理論上機(jī)器內(nèi)存有多大,元空間就有多大 。

d3049450-c02f-11ed-bfe3-dac502259ad0.jpg

可以通過(guò)以下的參數(shù)來(lái)設(shè)置元空間的大?。?/p>

  • -XX:MetaspaceSize,初始空間大小,達(dá)到該值就會(huì)觸發(fā)垃圾收集進(jìn)行類型卸載,同時(shí)GC會(huì)對(duì)該值進(jìn)行調(diào)整:如果釋放了大量的空間,就適當(dāng)降低該值;如果釋放了很少的空間,那么在不超過(guò)MaxMetaspaceSize時(shí),適當(dāng)提高該值。
  • -XX:MaxMetaspaceSize,最大空間,默認(rèn)是沒有限制的。
  • -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空間容量的百分比,減少為分配空間所導(dǎo)致的垃圾收集
  • -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空間容量的百分比,減少為釋放空間所導(dǎo)致的垃圾收集

所以,為什么使用元空間替換永久代?

表面上看是為了避免OOM異常。

因?yàn)橥ǔJ褂肞ermSize和MaxPermSize設(shè)置永久代的大小就決定了永久代的上限,但是不是總能知道應(yīng)該設(shè)置為多大合適, 如果使用默認(rèn)值很容易遇到OOM錯(cuò)誤。

當(dāng)使用元空間時(shí),可以加載多少類的元數(shù)據(jù)就不再由MaxPermSize控制, 而由系統(tǒng)的實(shí)際可用空間來(lái)控制啦。

什么是Stop The World ? 什么是OopMap?什么是安全點(diǎn)?

進(jìn)行垃圾回收的過(guò)程中,會(huì)涉及對(duì)象的移動(dòng)。

為了保證對(duì)象引用更新的正確性,必須暫停所有的用戶線程,像這樣的停頓,虛擬機(jī)設(shè)計(jì)者形象描述為Stop The World 。也簡(jiǎn)稱為STW。

在HotSpot中,有個(gè)數(shù)據(jù)結(jié)構(gòu)(映射表)稱為OopMap

一旦類加載動(dòng)作完成的時(shí)候,HotSpot就會(huì)把對(duì)象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)計(jì)算出來(lái),記錄到OopMap。

在即時(shí)編譯過(guò)程中,也會(huì)在特定的位置 生成 OopMap,記錄下棧上和寄存器里哪些位置是引用。

這些特定的位置主要在:

1.循環(huán)的末尾(非 counted 循環(huán))

2.方法臨返回前 / 調(diào)用方法的call指令后

3.可能拋異常的位置

這些位置就叫作安全點(diǎn)(safepoint)。

用戶程序執(zhí)行時(shí)并非在代碼指令流的任意位置都能夠在停頓下來(lái)開始垃圾收集,而是必須是執(zhí)行到安全點(diǎn)才能夠暫停。

審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    3028

    瀏覽量

    74081
  • JVM
    JVM
    +關(guān)注

    關(guān)注

    0

    文章

    158

    瀏覽量

    12236
  • 收集器
    +關(guān)注

    關(guān)注

    0

    文章

    30

    瀏覽量

    3147

原文標(biāo)題:阿里:每天100w次登陸請(qǐng)求, 8G 內(nèi)存該如何設(shè)置JVM參數(shù)?

文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    容器JVM內(nèi)存配置最佳實(shí)踐

    Killer)機(jī)制,此時(shí)系統(tǒng)會(huì)終止內(nèi)存占用較多的進(jìn)程以保證系統(tǒng)的正常運(yùn)行。特別是在容器環(huán)境下,不合理的JVM參數(shù)設(shè)置會(huì)導(dǎo)致各種異?,F(xiàn)象產(chǎn)生,例如應(yīng)用堆大小還未到達(dá)JVM
    發(fā)表于 06-20 09:45 ?896次閱讀
    容器<b class='flag-5'>JVM</b><b class='flag-5'>內(nèi)存</b>配置最佳實(shí)踐

    我想請(qǐng)問下 手機(jī)可以登陸 網(wǎng)站嗎

    我想請(qǐng)問下 手機(jī)可以登陸 網(wǎng)站嗎、我用手機(jī)找到論壇了 但是登陸時(shí)出現(xiàn)的問題
    發(fā)表于 05-28 20:14

    100W擴(kuò)音機(jī)電路

    100W擴(kuò)音機(jī)電路
    發(fā)表于 10-19 14:27 ?5839次閱讀

    100W逆變電源電路

    100W逆變電源電路原理圖
    發(fā)表于 11-03 19:18 ?1617次閱讀
    <b class='flag-5'>100W</b>逆變電源電路

    100W RMS的放大器電路 (100W rms ampli

    100W RMS的放大器電路 (100W rms amplifier) Circuit Description: This is a 100 watt basic power amp that was designed t
    發(fā)表于 12-23 18:04 ?3327次閱讀
    <b class='flag-5'>100W</b> RMS的放大器電路 (<b class='flag-5'>100W</b> rms ampli

    100W音頻放大器電路 (100 W Audio Ampli

    100W音頻放大器電路 (100 W Audio Amplifier) General Description
    發(fā)表于 12-24 10:09 ?1995次閱讀
    <b class='flag-5'>100W</b>音頻放大器電路 (<b class='flag-5'>100</b> <b class='flag-5'>W</b> Audio Ampli

    傳iPhone8將一破保守的傳統(tǒng)采用8G超速內(nèi)存?

    隨著6G內(nèi)存智能手機(jī)在市場(chǎng)上廣泛出現(xiàn),今年我們將更加普遍地看到8G內(nèi)存的智能設(shè)備。據(jù)最新消息,三星Galaxy S8 將是首個(gè)采用
    發(fā)表于 01-13 09:08 ?952次閱讀

    在4G內(nèi)存的機(jī)器上,申請(qǐng)8G內(nèi)存會(huì)怎么樣?

    這篇文章其實(shí)之前發(fā)過(guò),但是最近有位讀者跟我反饋,我文章中的實(shí)驗(yàn)在 64 位操作系統(tǒng)、2 G 物理內(nèi)存的場(chǎng)景,申請(qǐng) 8G 內(nèi)存是沒問題的,而他也是這個(gè)環(huán)境,為什么他就無(wú)法申請(qǐng)成功呢?
    的頭像 發(fā)表于 01-31 16:41 ?943次閱讀

    jvm內(nèi)存溢出如何定位解決

    在Java應(yīng)用程序中,JVM(Java虛擬機(jī))內(nèi)存溢出是指Java應(yīng)用程序試圖分配的內(nèi)存超過(guò)了JVM所允許的最大內(nèi)存大小,導(dǎo)致程序無(wú)法正常執(zhí)
    的頭像 發(fā)表于 12-05 11:05 ?1348次閱讀

    jvm調(diào)優(yōu)參數(shù)

    和類元數(shù)據(jù)等方面的參數(shù)設(shè)置。下面我們將詳細(xì)介紹這些參數(shù)以及如何進(jìn)行優(yōu)化。 首先,堆內(nèi)存JVM中用于存放對(duì)象實(shí)例的內(nèi)存區(qū)域。通過(guò)調(diào)整堆
    的頭像 發(fā)表于 12-05 11:29 ?638次閱讀

    jvm參數(shù)設(shè)置jvm調(diào)優(yōu)

    JVM(Java虛擬機(jī))參數(shù)設(shè)置和調(diào)優(yōu)對(duì)于提高Java應(yīng)用程序的性能和穩(wěn)定性非常重要。在本文中,我們將詳細(xì)介紹JVM參數(shù)
    的頭像 發(fā)表于 12-05 11:36 ?1572次閱讀

    jvm配置metaspace最大值的參數(shù)

    內(nèi)存限制):參數(shù)用于設(shè)置JVM堆的最大大小。在JVM啟動(dòng)時(shí),可以使用以下命令來(lái)配置Metas
    的頭像 發(fā)表于 12-05 14:21 ?2146次閱讀

    weblogic jvm參數(shù)配置

    ,讓我們來(lái)了解一些常用的JVM參數(shù): -Xms 和 -Xmx參數(shù):這些參數(shù)分別用于設(shè)置Java虛擬機(jī)的初始堆大小和最大堆大小。-Xms
    的頭像 發(fā)表于 12-05 14:31 ?1440次閱讀

    weblogic設(shè)置jvm內(nèi)存大小

    WebLogic是一種Java EE應(yīng)用服務(wù)器,用于構(gòu)建和部署企業(yè)級(jí)Java應(yīng)用程序。在配置WebLogic服務(wù)器時(shí),設(shè)置JVM內(nèi)存大小非常重要,這可以提高應(yīng)用程序的性能和可靠性。本文將詳細(xì)介紹
    的頭像 發(fā)表于 12-05 14:44 ?3077次閱讀

    eclipse設(shè)置jvm內(nèi)存大小

    Eclipse是一個(gè)功能強(qiáng)大的集成開發(fā)環(huán)境(IDE),常用于Java開發(fā)。為了保證Eclipse的性能和穩(wěn)定性,我們可以根據(jù)需要來(lái)設(shè)置JVM內(nèi)存大小。本文將詳細(xì)介紹如何在Eclipse中設(shè)置
    的頭像 發(fā)表于 12-06 11:43 ?1892次閱讀