JVM實(shí)用參數(shù)(四)內(nèi)存調(diào)優(yōu)

原文地址,譯文地址,作者:PATRICK PESCHLOW,譯者:鄭旭東 校對(duì):梁海艦

理想的情況下,一個(gè)Java程序使用JVM的默認(rèn)設(shè)置也可以運(yùn)行得很好,所以一般來(lái)說(shuō),沒(méi)有必要設(shè)置任何JVM參數(shù)。然而,由于一些性能問(wèn)題(很不幸的是,這些問(wèn)題經(jīng)常出現(xiàn)),一些相關(guān)的JVM參數(shù)知識(shí)會(huì)是我們工作中得好伙伴。在這篇文章中,我們將介紹一些關(guān)于JVM內(nèi)存管理的參數(shù)。知道并理解這些參數(shù),將對(duì)開(kāi)發(fā)者和運(yùn)維人員很有幫助。

所有已制定的HotSpot內(nèi)存管理和垃圾回收算法都基于一個(gè)相同的堆內(nèi)存劃分:新生代(young generation)里存儲(chǔ)著新分配的和較年輕的對(duì)象,老年代(old generation)里存儲(chǔ)著長(zhǎng)壽的對(duì)象。在此之外,永久代(permanent generation)存儲(chǔ)著那些需要伴隨整個(gè)JVM生命周期的對(duì)象,比如,已加載的對(duì)象的類(lèi)定義或者String對(duì)象內(nèi)部Cache。接下來(lái),我們將假設(shè)堆內(nèi)存是按照新生代、老年代和永久代這一經(jīng)典策略劃分的。然而,其他的一些堆內(nèi)存劃分策略也是可行的,一個(gè)突出的例子就是新的G1垃圾回收器,它模糊了新生代和老年代之間的區(qū)別。此外,目前的開(kāi)發(fā)進(jìn)程似乎表明在未來(lái)的HotSpot JVM版本中,將不會(huì)區(qū)分老年代和永久代。

-Xms and -Xmx (or: -XX:InitialHeapSize and -XX:MaxHeapSize)

-Xms和-Xmx可以說(shuō)是最流行的JVM參數(shù),它們可以允許我們指定JVM的初始和最大堆內(nèi)存大小。一般來(lái)說(shuō),這兩個(gè)參數(shù)的數(shù)值單位是Byte,但同時(shí)它們也支持使用速記符號(hào),比如“k”或者“K”代表“kilo”,“m”或者“M”代表“mega”,“g”或者“G”代表“giga”。舉個(gè)例子,下面的命令啟動(dòng)了一個(gè)初始化堆內(nèi)存為128M,最大堆內(nèi)存為2G,名叫“MyApp”的Java應(yīng)用程序。

java -Xms128m -Xmx2g MyApp

在實(shí)際使用過(guò)程中,初始化堆內(nèi)存的大小通常被視為堆內(nèi)存大小的下界。然而JVM可以在運(yùn)行時(shí)動(dòng)態(tài)的調(diào)整堆內(nèi)存的大小,所以理論上來(lái)說(shuō)我們有可能會(huì)看到堆內(nèi)存的大小小于初始化堆內(nèi)存的大小。但是即使在非常低的堆內(nèi)存使用下,我也從來(lái)沒(méi)有遇到過(guò)這種情況。這種行為將會(huì)方便開(kāi)發(fā)者和系統(tǒng)管理員,因?yàn)槲覀兛梢酝ㄟ^(guò)將“-Xms”和“-Xmx”設(shè)置為相同大小來(lái)獲得一個(gè)固定大小的堆內(nèi)存。 -Xms和-Xmx實(shí)際上是-XX:InitialHeapSize和-XX:MaxHeapSize的縮寫(xiě)。我們也可以直接使用這兩個(gè)參數(shù),它們所起得效果是一樣的:

$ java -XX:InitialHeapSize=128m -XX:MaxHeapSize=2g MyApp

需要注意的是,所有JVM關(guān)于初始\最大堆內(nèi)存大小的輸出都是使用它們的完整名稱(chēng):“InitialHeapSize”和“InitialHeapSize”。所以當(dāng)你查詢(xún)一個(gè)正在運(yùn)行的JVM的堆內(nèi)存大小時(shí),如使用-XX:+PrintCommandLineFlags參數(shù)或者通過(guò)JMX查詢(xún),你應(yīng)該尋找“InitialHeapSize”和“InitialHeapSize”標(biāo)志而不是“Xms”和“Xmx”。

-XX:+HeapDumpOnOutOfMemoryError and -XX:HeapDumpPath

如果我們沒(méi)法為-Xmx(最大堆內(nèi)存)設(shè)置一個(gè)合適的大小,那么就有可能面臨內(nèi)存溢出(OutOfMemoryError)的風(fēng)險(xiǎn),這可能是我們使用JVM時(shí)面臨的最可怕的猛獸之一。就同另外一篇關(guān)于這個(gè)主題的博文說(shuō)的一樣,導(dǎo)致內(nèi)存溢出的根本原因需要仔細(xì)的定位。通常來(lái)說(shuō),分析堆內(nèi)存快照(Heap Dump)是一個(gè)很好的定位手段,如果發(fā)生內(nèi)存溢出時(shí)沒(méi)有生成內(nèi)存快照那就實(shí)在是太糟了,特別是對(duì)于那種JVM已經(jīng)崩潰或者錯(cuò)誤只出現(xiàn)在順利運(yùn)行了數(shù)小時(shí)甚至數(shù)天的生產(chǎn)系統(tǒng)上的情況。

幸運(yùn)的是,我們可以通過(guò)設(shè)置-XX:+HeapDumpOnOutOfMemoryError 讓JVM在發(fā)生內(nèi)存溢出時(shí)自動(dòng)的生成堆內(nèi)存快照。有了這個(gè)參數(shù),當(dāng)我們不得不面對(duì)內(nèi)存溢出異常的時(shí)候會(huì)節(jié)約大量的時(shí)間。默認(rèn)情況下,堆內(nèi)存快照會(huì)保存在JVM的啟動(dòng)目錄下名為java_pid<pid>.hprof 的文件里(在這里<pid>就是JVM進(jìn)程的進(jìn)程號(hào))。也可以通過(guò)設(shè)置-XX:HeapDumpPath=<path>來(lái)改變默認(rèn)的堆內(nèi)存快照生成路徑,<path>可以是相對(duì)或者絕對(duì)路徑。

雖然這一切聽(tīng)起來(lái)很不錯(cuò),但有一點(diǎn)我們需要牢記。堆內(nèi)存快照文件有可能很龐大,特別是當(dāng)內(nèi)存溢出錯(cuò)誤發(fā)生的時(shí)候。因此,我們推薦將堆內(nèi)存快照生成路徑指定到一個(gè)擁有足夠磁盤(pán)空間的地方。

-XX:OnOutOfMemoryError

當(dāng)內(nèi)存溢發(fā)生時(shí),我們甚至可以可以執(zhí)行一些指令,比如發(fā)個(gè)E-mail通知管理員或者執(zhí)行一些清理工作。通過(guò)-XX:OnOutOfMemoryError 這個(gè)參數(shù)我們可以做到這一點(diǎn),這個(gè)參數(shù)可以接受一串指令和它們的參數(shù)。在這里,我們將不會(huì)深入它的細(xì)節(jié),但我們提供了它的一個(gè)例子。在下面的例子中,當(dāng)內(nèi)存溢出錯(cuò)誤發(fā)生的時(shí)候,我們會(huì)將堆內(nèi)存快照寫(xiě)到/tmp/heapdump.hprof 文件并且在JVM的運(yùn)行目錄執(zhí)行腳本cleanup.sh

$ java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -XX:OnOutOfMemoryError ="sh ~/cleanup.sh" MyApp

-XX:PermSize and -XX:MaxPermSize

永久代在堆內(nèi)存中是一塊獨(dú)立的區(qū)域,它包含了所有JVM加載的類(lèi)的對(duì)象表示。為了成功運(yùn)行應(yīng)用程序,JVM會(huì)加載很多類(lèi)(因?yàn)樗鼈円蕾?lài)于大量的第三方庫(kù),而這又依賴(lài)于更多的庫(kù)并且需要從里面將類(lèi)加載進(jìn)來(lái))這就需要增加永久代的大小。我們可以使用-XX:PermSize 和-XX:MaxPermSize 來(lái)達(dá)到這個(gè)目的。其中-XX:MaxPermSize 用于設(shè)置永久代大小的最大值,-XX:PermSize 用于設(shè)置永久代初始大小。下面是一個(gè)簡(jiǎn)單的例子:

$ java -XX:PermSize=128m -XX:MaxPermSize=256m MyApp

請(qǐng)注意,這里設(shè)置的永久代大小并不會(huì)被包括在使用參數(shù)-XX:MaxHeapSize 設(shè)置的堆內(nèi)存大小中。也就是說(shuō),通過(guò)-XX:MaxPermSize設(shè)置的永久代內(nèi)存可能會(huì)需要由參數(shù)-XX:MaxHeapSize 設(shè)置的堆內(nèi)存以外的更多的一些堆內(nèi)存。

-XX:InitialCodeCacheSize and -XX:ReservedCodeCacheSize

JVM一個(gè)有趣的,但往往被忽視的內(nèi)存區(qū)域是“代碼緩存”,它是用來(lái)存儲(chǔ)已編譯方法生成的本地代碼。代碼緩存確實(shí)很少引起性能問(wèn)題,但是一旦發(fā)生其影響可能是毀滅性的。如果代碼緩存被占滿,JVM會(huì)打印出一條警告消息,并切換到interpreted-only 模式:JIT編譯器被停用,字節(jié)碼將不再會(huì)被編譯成機(jī)器碼。因此,應(yīng)用程序?qū)⒗^續(xù)運(yùn)行,但運(yùn)行速度會(huì)降低一個(gè)數(shù)量級(jí),直到有人注意到這個(gè)問(wèn)題。就像其他內(nèi)存區(qū)域一樣,我們可以自定義代碼緩存的大小。相關(guān)的參數(shù)是-XX:InitialCodeCacheSize 和-XX:ReservedCodeCacheSize,它們的參數(shù)和上面介紹的參數(shù)一樣,都是字節(jié)值。

-XX:+UseCodeCacheFlushing

如果代碼緩存不斷增長(zhǎng),例如,因?yàn)闊岵渴鹨鸬膬?nèi)存泄漏,那么提高代碼的緩存大小只會(huì)延緩其發(fā)生溢出。為了避免這種情況的發(fā)生,我們可以嘗試一個(gè)有趣的新參數(shù):當(dāng)代碼緩存被填滿時(shí)讓JVM放棄一些編譯代碼。通過(guò)使用-XX:+UseCodeCacheFlushing 這個(gè)參數(shù),我們至少可以避免當(dāng)代碼緩存被填滿的時(shí)候JVM切換到interpreted-only 模式。不過(guò),我仍建議盡快解決代碼緩存問(wèn)題發(fā)生的根本原因,如找出內(nèi)存泄漏并修復(fù)它。

轉(zhuǎn)載自
并發(fā)編程網(wǎng) – ifeve.com
轉(zhuǎn)載鏈接地址:
JVM實(shí)用參數(shù)(四)內(nèi)存調(diào)優(yōu)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容