[討論] 這幾天我架了伺服器-如何將java最佳化?
Can't keep up! Did the system time change, or is the server overload?
我相信,這訊息是所有伺服器主一生永遠的痛。
自去年開始架 Minecraft 伺服器開始,我即不斷地在找尋如何調校java的方法,
好讓伺服器能夠更順暢的運作,那時我有發一篇名為"降低延遲與修復破損的世界"
一篇翻譯自bukkit論壇上的調校文章。
不過效果不彰,雖然 Can't keep up! 減少了很多,遊戲卻頻頻停頓,最後索性放棄了。
但是這次,為了讓這些Forge MOD安分,還真讓我找到一篇真的能用的調校文。
在開始正文之前,我必須轉達一些來自bukkit論壇上版友們的警告:
一昧地遵照所謂的"調校教學"是大錯特錯的,因為不同的硬體有它不同的特性,所以各種
指令參數並非能在所有系統上面使用。而進行所謂的"調校",不應該僅把它當作一種"行
為",更應該慎重的把它當作是一門"藝術"。
==============================================================================
以下翻譯自原文:(可能有翻譯不完整甚至是錯誤的地方,更正大歡迎)
How can I optimize Java on my server? http://goo.gl/wyalw
請記住這篇文章是針對 Java6 而寫成。(按:現在已經是Java7)然而,大部分或者所有的
參數都能在Java7中運作,雖然實際情形可能跟本篇不同。
伺服器的規格是 Windows Server 2008 R2 SP1 64Bit
Core i7 2600k 並超頻到 4.8Ghz, 16GB 的記憶體, 1GB 的 RAMDisk
這是我完整的啟動腳本(StartServer.cmd)
@Echo OFF
TITLE MINECRAFT-CRAFTBUKKIT
START "MINECRAFT-CRAFTBUKKIT" /ABOVENORMAL /B java -server -Xmx12G
-XX:PermSize=128m -XX:MaxPermSize=256m -XX:+DisableExplicitGC
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+UseNUMA
-XX:+CMSParallelRemarkEnabled -XX:MaxGCPauseMillis=50
-XX:+UseAdaptiveGCBoundary -XX:-UseGCOverheadLimit -XX:+UseBiasedLocking
-XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=15
-XX:UseSSE=3 -XX:+UseLargePages -XX:+UseFastAccessorMethods
-XX:+UseStringCache -XX:+UseCompressedStrings -XX:+UseCompressedOops
-XX:+OptimizeStringConcat -XX:+AggressiveOpts -jar craftbukkit.jar nogui
如果想得到完整的 垃圾回收 (Garbage Collection) 的紀錄,要再加入以下參數
(注:垃圾回收是一種記憶體管理技術,詳情請自行查詢 wiki)
-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
-XX:+PrintTenuringDistribution -Xloggc:memory.log
注意!即使程式只運作一小段時間,也會產生非~常大的紀錄檔案,因為程式會擷取大量
的資料。通常也會多少影響伺服器效能,所以請在有需要解析垃圾回收的行為時再使用這
些參數。市面上有很多可以分析這個紀錄的分析工具,能產生完善的分析圖表。
你可以在這裡找到一個圖表分析工具:
http://java.sun.com/developer/technicalArticles/Programming/GCPortal/index.html
(已失效,文後補注)
接下來我們來逐步看看這些參數
@ Echo OFF
TITLE MINECRAFT-CRAFTBUKKIT
START "MINECRAFT-CRAFTBUKKIT" /ABOVENORMAL /B java -server
這幾行會啟動伺服器,並擁有比一般程式還要高的優先權,並指定要啟動伺服器版的JVM
-Xmx12G -XX:PermSize=128m -XX:MaxPermSize=256m
指定JVM最多能動態分配的記憶體為12GB,這個數值必須小於實體記憶體的容量。但我不
指定JVM啟動時要配給多少記憶體給他,而是讓JVM自己決定要吃多少就吃多少。
我的伺服器有 16GB 的記憶體,所以剩下的 2GB 我留給系統。根據經驗,把最大實體記
憶體減掉 2GB 就是可以配給程式的大小。若是在獨立的Linux伺服器上,你則可以從系統
上挪用更多記憶體。
PermSize參數可以指定永久保存區域(Permanent Generation space,原文只提
Permanent)的起始與最大容量。我確信預設的大小是起始32MB與最大64MB。從垃圾回收的
紀錄來說,我常常看到他一路衝滿64MB,所以我設高一點好給JVM留點空間。雖然128MB不
怎麼節省就是了。
編按:一個jar內包含一個以上的class,從minecraft_server到forge甚至其他MOD總計的
class數量相當可觀。而這些class會先被載入到 Permanent Generation space,並不受
垃圾回收程序影響,唯有退出的class才會做垃圾處理。(根據手邊的資料,並沒有提及
PermGen滿了會做什麼事,只知道他累積到一定的量會停住並不會減少)
-XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+UseNUMA
-XX:+CMSParallelRemarkEnabled
關閉完整垃圾回收來預防遊戲內長時間的暫停。
指定 concurrent mark sweep collector 來針對 old generation 進行垃圾回收,new
generation 則指定 parallel collector 做垃圾回收。這一行同時也會讓回收垃圾的標
記過程做平行處理。最後,啟用 NUMA (這個請自己查) 做記憶體內物件放置最佳化來提
升效能。
-XX:MaxGCPauseMillis=50
這行告訴每一次進行回收而造成的暫停不能超過 50毫秒 。而現代的CPU做垃圾處理通
常平均低於 40毫秒 以下,所以設定為 50毫秒 有助於預防突然的運算高峰。而讓垃圾處
理所造成的暫停時間低於每 1 tick 的速度會有助於伺服器運作順暢。
另外,我讓 JVM 自己決定要使用多少線程來來做垃圾回收...等。因為只有他自己才知道
怎樣設定才是最好的。如果手動設定得太高或太低,只會弊大於利。
-XX:+UseAdaptiveGCBoundary -XX:-UseGCOverheadLimit -XX:+UseBiasedLocking
允許new generation與tenured generation之間的資料轉移
GCOverHeadLimit前面的減號代表把這個功能關掉。這會在輸出記憶體錯誤的訊息之前增
加垃圾處理的時間。因為伺服器不見得會照你的意思去做,所以我寧願多鞭策他個幾下
好讓他多釋放一點記憶體。
BiasedLocking should aid efficiency in multi CPU setups by biasing an objects
to use the thread which first created it. (鎖什麼的最討厭了,不翻)
-XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=15
讓物件在 new generation 中活久一點再丟進old generation。試著比對加入或移除這三
個參數所得到的結果。為了得到完善的測試結果,請進行長時間的測試,24小時或者更長
的時間。
-XX:UseSSE=3 -XX:+UseLargePages
允許使用大型記憶體分頁來增進效能。雖然這需要你的作業系統支援。Windows7支援,然
後大多數的Linux發行版也支援,但你可能需要事先在Linux上面做一些調整。沒有任何事
是Google不能幫你的。如果出現權限不足的情形,就需要以系統管理員的身分執行。
權限不足,使用系統管理員帳號把現在的帳號添加額外權限:
1. 到 控制台 -> 系統管理工具 -> 本機安全性原則
2. 選 本機原則 -> 使用者權限指定使用者權限指派
3. 在"鎖定記憶體分頁",新增 使用者 和/或 群組
4. 重新啟動電腦
-XX:+UseStringCache -XX:+UseCompressedStrings -XX:+Use CompressedOops
-XX:+OptimizeStringConcat -XX:+UseFastAccessorMethods -XX:+AggressiveOpts
(注:Java7底下只要增加綠色的部分就可以了)
最佳化選項在 Java6 裡是預設關閉的,但其中部分選項可能已經在Java7中預設開啟。
這就是我對伺服器所做的調整內容,而且也運作得相當不錯。我只有同時最多 15 名玩家
在線上,所以我想知道那些容納 30 名以上玩家的大型伺服器用了這些參數之後會有怎樣
的結果。雖然目前大部分都是正面回應。
==============================================================================
補述:
1. JVM 即是 Java Virtual Machine,簡單來說就是實際執行Java的東西叫這個名字。
2. 簡述Java的GC管理:
Java對記憶體管理分:new, tenured(old), permanent generation 這三種區,新丟進記
憶體的會先劃分到new(新人加入),經過不斷的GC垃圾回收(淘汰不適任者),沒被回收的
就會丟到old(成為正職員工)。Permanent則是那些運作本體、MOD裡的各種class、字串丟
進去保存的地方(譬如各種專業顧問),基本上是不太會被GC的。
3. 垃圾回收記錄分析工具,我用過的有下面這兩種:
(1). IBM Pattern Modeling and Analysis Tool for Java Garbage Collector:
http://goo.gl/EBEpo
(2). GCViewer:
https://github.com/chewiebug/GCViewer/wiki/Changelog
我推薦 IBM 那款,直接看 Summary 看哪個Generation滿了就加大,哪個沒怎麼用就縮小
至於 GCViewer 他 PermSize 抓不到,不過可以用"WATCH"功能即時監看GC過程,還不錯。
若要產生支援上述軟體的紀錄檔。記得配合以下參數服用:(上面介紹的就不理他了吧)
-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC
-Xloggc:memory.log
4. 附上我的電腦設備:
CPU:AMD Athlon 64 X2 4000+ 2.1GHz
記憶體:DDR2 3GB
作業系統:Windows 7 家用進階版 32bit
以下是我的啟動參數
%JAVA% -server -Xmx1G -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC
-XX:+UseParNewGC -XX:+UseNUMA -XX:+CMSParallelRemarkEnabled
-XX:+UseAdaptiveGCBoundary -XX:-UseGCOverheadLimit -XX:+UseBiasedLocking
-XX:UseSSE=3 -XX:+OptimizeStringConcat -XX:+UseFastAccessorMethods
-XX:+AggressiveOpts-jar minecraftforge-universal-1.4.6-6.5.0.471.jar nogui
跟上面的文章有差別的地方是,因為記憶體不多所以我不開大記憶體分頁,也不設定
SurvivorRatio,交給JVM自己決定。至於 PermSize 之類的參數,因為一直沒滿過所以就
沒特別設定了。跑的這幾天也證明,minecraft伺服器真的建議一定要有 1GB 以上的空閒
記憶體 (電腦至少要配備2GB以上的記憶體)
順便附上自己最近的GC紀錄:
http://dl.dropbox.com/u/7320770/BBS/Minecraft/memory1.7z
希望上面的沒翻錯,也感謝大家看完這篇 XD
至於我以前發的那篇文章就忘了吧,那不是調校,是搞笑。
參考資料:
http://goo.gl/OCMFM
--
※ 發信站: 批踢踢實業坊(ptt.cc)
留言