最近工作中,老是遇到程序假死或者宕掉,最終原因都是full gc導(dǎo)致,剛好回過頭再學(xué)習(xí)一下JVM內(nèi)存模式,以及GC垃圾回收機制,減少故障幾率。
一、JVM內(nèi)存模型及垃圾收集算法
1. JVM內(nèi)存劃分:
New(年輕代)
Tenured(年老代)
Prtm(永久代)
其中New和Tenured屬于堆內(nèi)存,堆內(nèi)存會從JVM啟動參數(shù)(-Xmx:2G)指定的內(nèi)存中分配,Perm不屬于堆內(nèi)存,有虛擬機直接分配,但可以通過-XX:PermSize -XX:MaxPermSize 等參數(shù)調(diào)整其大小。
年輕代(New):年輕代用來存放JVM剛分配的Java對象
年老代(Tenured):年輕代中經(jīng)過垃圾回收沒有回收掉的對象將被Copy到年老代
永久代(Perm):永久代存放Class、Method元信息,其大小跟項目的規(guī)模、類、方法的量有關(guān),一般設(shè)置為128M就足夠,設(shè)置原則是預(yù)留30%的空間。
HotSpot JVM把年輕代分為了三部分:
Eden:存放JVM剛分配的對象
Survivor1
Survivro2:兩個Survivor空間一樣大
新創(chuàng)建的對象都會被分配到Eden區(qū)當(dāng),經(jīng)過第一次Minor GC后,如果仍然存活,將會被移到Survivor區(qū)。對象在Survivor區(qū)中每熬過一次Minor GC,年齡就會增加1歲,當(dāng)它的年齡增加到一定程度時,就會被移動到年老代中。
因為年輕代中的對象基本都是朝生夕死的(80%以上),所以在年輕代的垃圾回收算法使用的是復(fù)制算法,復(fù)制算法的基本思想就是將內(nèi)存分為兩塊,每次只用其中一塊,當(dāng)這一塊內(nèi)存用完,就將還活著的對象復(fù)制到另外一塊上面。復(fù)制算法不會產(chǎn)生內(nèi)存碎片。
在GC開始的時候,對象只會存在于Eden區(qū)和名為“From”的Survivor區(qū),Survivor區(qū)“To”是空的。緊接著進行GC,Eden區(qū)中所有存活的對象都會被復(fù)制到“To”,而在“From”區(qū)中,仍存活的對象會根據(jù)他們的年齡值來決定去向。年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設(shè)置)的對象會被移動到年老代中,沒有達到閾值的對象會被復(fù)制到“To”區(qū)域。經(jīng)過這次GC后,Eden區(qū)和From區(qū)已經(jīng)被清空。這個時候,“From”和“To”會交換他們的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎樣,都會保證名為To的Survivor區(qū)域是空的。Minor GC會一直重復(fù)這樣的過程,直到“To”區(qū)被填滿,“To”區(qū)被填滿之后,會將所有對象移動到年老代中。

2. 典型垃圾回收算法:
1.Mark-Sweep(標記-清除典型垃圾)算法
最基礎(chǔ)的垃圾回收算法,容易實現(xiàn),思想簡單。標記-清除算法分為兩個階段:標記階段和清除階段。標記階段的任務(wù)是標記出所有需要被回收的對象,清除階段就是回收被標記的對象所占用的空間。具體過程如下圖所示:

從圖中可以很容易看出標記-清除算法實現(xiàn)起來比較容易,但是有一個比較嚴重的問題就是容易產(chǎn)生內(nèi)存碎片,碎片太多可能會導(dǎo)致后續(xù)過程中需要為大對象分配空間時無法找到足夠的空間而提前觸發(fā)新的一次垃圾收集動作。
2.Copying(復(fù)制)算法
為了解決Mark-Sweep算法的缺陷,Copying算法就被提了出來。它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面,然后再把已使用的內(nèi)存空間一次清理掉,這樣一來就不容易出現(xiàn)內(nèi)存碎片的問題。具體過程如下圖所示:

這種算法雖然實現(xiàn)簡單,運行高效且不容易產(chǎn)生內(nèi)存碎片,但是卻對內(nèi)存空間的使用做出了高昂的代價,因為能夠使用的內(nèi)存縮減到原來的一半。
很顯然,Copying算法的效率跟存活對象的數(shù)目多少有很大的關(guān)系,如果存活對象很多,那么Copying算法的效率將會大大降低。
3.Mark-Compact(標記-整理)算法
為了解決Copying算法的缺陷,充分利用內(nèi)存空間,提出了Mark-Compact算法。該算法標記階段和Mark-Sweep一樣,但是在完成標記之后,它不是直接清理可回收對象,而是將存活對象都向一端移動,然后清理掉端邊界以外的內(nèi)存。具體過程如下圖所示:

4.Generational Collection(分代收集)算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根據(jù)對象存活的生命周期將內(nèi)存劃分為若干個不同的區(qū)域。一般情況下將堆區(qū)劃分為老年代(Tenured Generation)和新生代(Young Generation),老年代的特點是每次垃圾收集時只有少量對象需要被回收,而新生代的特點是每次垃圾回收時都有大量的對象需要被回收,那么就可以根據(jù)不同代的特點采取最適合的收集算法。
目前大部分垃圾收集器對于新生代都采取Copying算法,因為新生代中每次垃圾回收都要回收大部分對象,也就是說需要復(fù)制的操作次數(shù)較少,但是實際中并不是按照1:1的比例來劃分新生代的空間的,一般來說是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當(dāng)進行回收時,將Eden和Survivor中還存活的對象復(fù)制到另一塊Survivor空間中,然后清理掉Eden和剛才使用過的Survivor空間。
而由于老年代的特點是每次回收都只回收少量對象,一般使用的是Mark-Compact算法。
注意,在堆區(qū)之外還有一個代就是永久代(Permanet Generation),它用來存儲class類、常量、方法描述等。對永久代的回收主要回收兩部分內(nèi)容:廢棄常量和無用的類。
3. 典型垃圾回收器:
下面介紹一下HotSpot(JDK 7)虛擬機提供的幾種垃圾收集器,可根據(jù)實際需求組合出各個年代使用的收集器。
1.Serial/Serial Old
Serial/Serial Old收集器是最基本最古老的收集器,它是一個單線程收集器,并且在它進行垃圾收集時,必須暫停所有用戶線程。Serial收集器是針對新生代的收集器,采用的是Copying算法,Serial Old收集器是針對老年代的收集器,采用的是Mark-Compact算法。它的優(yōu)點是實現(xiàn)簡單高效,但是缺點是會給用戶帶來停頓。
2.ParNew
ParNew收集器是Serial收集器的多線程版本,使用多個線程進行垃圾收集。
3.Parallel Scavenge
Parallel Scavenge收集器是一個新生代的多線程收集器(并行收集器),它在回收期間不需要暫停其他用戶線程,其采用的是Copying算法,該收集器與前兩個收集器有所不同,它主要是為了達到一個可控的吞吐量。
4.Parallel Old
Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多線程和Mark-Compact算法。
5.CMS
CMS(Current Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器,它是一種并發(fā)收集器,采用的是Mark-Sweep算法。
6.G1
G1收集器是當(dāng)今收集器技術(shù)發(fā)展最前沿的成果,它是一款面向服務(wù)端應(yīng)用的收集器,它能充分利用多CPU、多核環(huán)境。因此它是一款并行與并發(fā)收集器,并且它能建立可預(yù)測的停頓時間模型。
參考資料:
《深入理解Java虛擬機》
問題:
1、垃圾回收何時執(zhí)行?
當(dāng)年輕代內(nèi)存滿時,會引發(fā)一次普通GC,該GC僅回收年輕代。需要強調(diào)的時,年輕代滿是指Eden代滿,Survivor滿不會引發(fā)GC
當(dāng)年老代滿時會引發(fā)Full GC,F(xiàn)ull GC將會同時回收年輕代、年老代
當(dāng)永久代滿時也會引發(fā)Full GC,會導(dǎo)致Class、Method元信息的卸載
2、何時會拋出OutOfMemoryException?
JVM98%的時間都花費在內(nèi)存回收
每次回收的內(nèi)存小于2%
滿足這兩個條件將觸發(fā)OutOfMemoryException,這將會留給系統(tǒng)一個微小的間隙以做一些Down之前的操作,比如手動打印Heap Dump。
二、內(nèi)存泄漏及解決方法
1.系統(tǒng)崩潰前的一些現(xiàn)象:
每次垃圾回收的時間越來越長,由之前的10ms延長到50ms左右,F(xiàn)ullGC的時間也有之前的0.5s延長到4、5s
FullGC的次數(shù)越來越多,最頻繁時隔不到1分鐘就進行一次FullGC
年老代的內(nèi)存越來越大并且每次FullGC后年老代沒有內(nèi)存被釋放
之后系統(tǒng)會無法響應(yīng)新的請求,逐漸到達OutOfMemoryError的臨界值。
2.解決方式:
- 生成堆的dump文件
通過JMX的MBean生成當(dāng)前的Heap信息,大小為一個3G(整個堆的大?。┑膆prof文件,如果沒有啟動JMX可以通過Java的jmap命令來生成該文件。
分析dump文件
可借助一下工具分析dump文件
JDK:jvisualvm
Eclipse:Mat。分析內(nèi)存泄漏
通過工具看到,哪些對象被懷疑為內(nèi)存泄漏,哪些對象占的空間最大及對象的調(diào)用關(guān)系。還可以分析線程狀態(tài),可以觀察到線程被阻塞在哪個對象上,從而判斷系統(tǒng)的瓶頸。
3.回歸問題
Q:
為什么崩潰前垃圾回收的時間越來越長?
A:
根據(jù)內(nèi)存模型和垃圾回收算法,垃圾回收分兩部分:內(nèi)存標記、清除(復(fù)制),標記部分只要內(nèi)存大小固定時間是不變的,變的是復(fù)制部分,因為每次垃圾回收都有一些回收不掉的內(nèi)存,所以增加了復(fù)制量,導(dǎo)致時間延長。所以,垃圾回收的時間也可以作為判斷內(nèi)存泄漏的依據(jù)
Q:
為什么Full GC的次數(shù)越來越多?
A:
因此內(nèi)存的積累,逐漸耗盡了年老代的內(nèi)存,導(dǎo)致新對象分配沒有更多的空間,從而導(dǎo)致頻繁的垃圾回收
Q:
為什么年老代占用的內(nèi)存越來越大?
A:
因為年輕代的內(nèi)存無法被回收,越來越多地被Copy到年老代
三、性能調(diào)優(yōu)
除了上述內(nèi)存泄漏外,我們還發(fā)現(xiàn)CPU長期不足3%,系統(tǒng)吞吐量不夠,針對8core×16G、64bit的Linux服務(wù)器來說,是嚴重的資源浪費。
在CPU負載不足的同時,偶爾會有用戶反映請求的時間過長,我們意識到必須對程序及JVM進行調(diào)優(yōu)。從以下幾個方面進行:
線程池:解決用戶響應(yīng)時間長的問題
連接池
JVM啟動參數(shù):調(diào)整各代的內(nèi)存比例和垃圾回收算法,提高吞吐量
程序算法:改進程序邏輯算法提高性能
1.Java線程池(java.util.concurrent.ThreadPoolExecutor)
大多數(shù)JVM6上的應(yīng)用采用的線程池都是JDK自帶的線程池,之所以把成熟的Java線程池進行羅嗦說明,是因為該線程池的行為與我們想象的有點出入。Java線程池有幾個重要的配置參數(shù):
corePoolSize:核心線程數(shù)(最新線程數(shù))
maximumPoolSize:最大線程數(shù),超過這個數(shù)量的任務(wù)會被拒絕,用戶可以通過RejectedExecutionHandler接口自定義處理方式
keepAliveTime:線程保持活動的時間
-
workQueue:工作隊列,存放執(zhí)行的任務(wù)
Java線程池需要傳入一個Queue參數(shù)(workQueue)用來存放執(zhí)行的任務(wù),而對Queue的不同選擇,線程池有完全不同的行為:
SynchronousQueue:一個無容量的等待隊列,一個線程的insert操作必須等待另一線程的remove操作,采用這個Queue線程池將會為每個任務(wù)分配一個新線程`
LinkedBlockingQueue:無界隊列,采用該Queue,線程池將忽略maximumPoolSize參數(shù),僅用corePoolSize的線程處理所有的任務(wù),未處理的任務(wù)便在LinkedBlockingQueue中排隊
-
ArrayBlockingQueue: 有界隊列,在有界隊列和` maximumPoolSize的作用下,程序?qū)⒑茈y被調(diào)優(yōu):更大的Queue和小的maximumPoolSize將導(dǎo)致CPU的低負載;小的Queue和大的池,Queue就沒起動應(yīng)有的作用。
其實我們的要求很簡單,希望線程池能跟連接池一樣,能設(shè)置最小線程數(shù)、最大線程數(shù),當(dāng)最小數(shù)<任務(wù)<最大數(shù)時,應(yīng)該分配新的線程處理;當(dāng)任務(wù)>最大數(shù)時,應(yīng)該等待有空閑線程再處理該任務(wù)。
但線程池的設(shè)計思路是,任務(wù)應(yīng)該放到Queue中,當(dāng)Queue放不下時再考慮用新線程處理,如果Queue滿且無法派生新線程,就拒絕該任務(wù)。設(shè)計導(dǎo)致“先放等執(zhí)行”、“放不下再執(zhí)行”、“拒絕不等待”。所以,根據(jù)不同的Queue參數(shù),要提高吞吐量不能一味地增大maximumPoolSize。
當(dāng)然,要達到我們的目標,必須對線程池進行一定的封裝,幸運的是ThreadPoolExecutor中留了足夠的自定義接口以幫助我們達到目標。我們封裝的方式是:
以SynchronousQueue作為參數(shù),使maximumPoolSize發(fā)揮作用,以防止線程被無限制的分配,同時可以通過提高maximumPoolSize來提高系統(tǒng)吞吐量
自定義一個RejectedExecutionHandler,當(dāng)線程數(shù)超過maximumPoolSize時進行處理,處理方式為隔一段時間檢查線程池是否可以執(zhí)行新Task,如果可以把拒絕的Task重新放入到線程池,檢查的時間依賴keepAliveTime的大小。
2.連接池(org.apache.commons.dbcp.BasicDataSource)
在使用org.apache.commons.dbcp.BasicDataSource的時候,因為之前采用了默認配置,所以當(dāng)訪問量大時,通過JMX觀察到很多Tomcat線程都阻塞在BasicDataSource使用的Apache ObjectPool的鎖上,直接原因當(dāng)時是因為BasicDataSource連接池的最大連接數(shù)設(shè)置的太小,默認的BasicDataSource配置,僅使用8個最大連接。
當(dāng)較長的時間不訪問系統(tǒng),比如2天,DB上的Mysql會斷掉所以的連接,導(dǎo)致連接池中緩存的連接不能用。為了解決這些問題,我們充分研究了BasicDataSource,發(fā)現(xiàn)了一些優(yōu)化的點:
Mysql默認支持100個鏈接,所以每個連接池的配置要根據(jù)集群中的機器數(shù)進行,如有2臺服務(wù)器,可每個設(shè)置為60
initialSize:參數(shù)是一直打開的連接數(shù)
minEvictableIdleTimeMillis:該參數(shù)設(shè)置每個連接的空閑時間,超過這個時間連接將被關(guān)閉
timeBetweenEvictionRunsMillis:后臺線程的運行周期,用來檢測過期連接
maxActive:最大能分配的連接數(shù)
maxIdle:最大空閑數(shù),當(dāng)連接使用完畢后發(fā)現(xiàn)連接數(shù)大于maxIdle,連接將被直接關(guān)閉。只有initialSize < x < maxIdle的連接將被定期檢測是否超期。這個參數(shù)主要用來在峰值訪問時提高吞吐量。
initialSize是如何保持的?經(jīng)過研究代碼發(fā)現(xiàn),BasicDataSource會關(guān)閉所有超期的連接,然后再打開initialSize數(shù)量的連接,這個特性與minEvictableIdleTimeMillis、timeBetweenEvictionRunsMillis一起保證了所有超期的initialSize連接都會被重新連接,從而避免了Mysql長時間無動作會斷掉連接的問題。
3.JVM參數(shù)
在JVM啟動參數(shù)中,可以設(shè)置跟內(nèi)存、垃圾回收相關(guān)的一些參數(shù)設(shè)置,默認情況不做任何設(shè)置JVM會工作的很好,但對一些配置很好的Server和具體的應(yīng)用必須仔細調(diào)優(yōu)才能獲得最佳性能。通過設(shè)置我們希望達到一些目標:
GC的時間足夠的小
GC的次數(shù)足夠的少
發(fā)生Full GC的周期足夠的長
前兩個目前是相悖的,要想GC時間小必須要一個更小的堆,要保證GC次數(shù)足夠少,必須保證一個更大的堆,我們只能取其平衡。
(1)針對JVM堆的設(shè)置,一般可以通過-Xms -Xmx限定其最小、最大值,
為了防止垃圾收集器在最小、最大之間收縮堆而產(chǎn)生額外的時間,我們通常把最大、最小設(shè)置為相同的值
(2)年輕代和年老代將根據(jù)默認的比例(1:2)分配堆內(nèi)存,可以通過調(diào)整二者之間的比率NewRadio來調(diào)整二者之間的大小,也可以針對回收代,比如年輕代,通過 -XX:newSize -XX:MaxNewSize來設(shè)置其絕對大小。同樣,為了防止年輕代的堆收縮,我們通常會把-XX:newSize -XX:MaxNewSize設(shè)置為同樣大小
(3)年輕代和年老代設(shè)置多大才算合理?這個我問題毫無疑問是沒有答案的,否則也就不會有調(diào)優(yōu)。我們觀察一下二者大小變化有哪些影響
更大的年輕代必然導(dǎo)致更小的年老代,大的年輕代會延長普通GC的周期,但會增加每次GC的時間;小的年老代會導(dǎo)致更頻繁的Full GC
更小的年輕代必然導(dǎo)致更大年老代,小的年輕代會導(dǎo)致普通GC很頻繁,但每次的GC時間會更短;大的年老代會減少Full GC的頻率
如何選擇應(yīng)該依賴應(yīng)用程序對象生命周期的分布情況:如果應(yīng)用存在大量的臨時對象,應(yīng)該選擇更大的年輕代;如果存在相對較多的持久對象,年老代應(yīng)該適當(dāng)增大。但很多應(yīng)用都沒有這樣明顯的特性,在抉擇時應(yīng)該根據(jù)以下兩點:(A)本著Full GC盡量少的原則,讓年老代盡量緩存常用對象,JVM的默認比例1:2也是這個道理 (B)通過觀察應(yīng)用一段時間,看其他在峰值時年老代會占多少內(nèi)存,在不影響Full GC的前提下,根據(jù)實際情況加大年輕代,比如可以把比例控制在1:1。但應(yīng)該給年老代至少預(yù)留1/3的增長空間
(4)在配置較好的機器上(比如多核、大內(nèi)存),可以為年老代選擇并行收集算法: -XX:+UseParallelOldGC ,默認為Serial收集
(5)線程堆棧的設(shè)置:每個線程默認會開啟1M的堆棧,用于存放棧幀、調(diào)用參數(shù)、局部變量等,對大多數(shù)應(yīng)用而言這個默認值太了,一般256K就足用。理論上,在內(nèi)存不變的情況下,減少每個線程的堆棧,可以產(chǎn)生更多的線程,但這實際上還受限于操作系統(tǒng)。
(4)可以通過下面的參數(shù)打Heap Dump信息
-XX:HeapDumpPath
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-
-Xloggc:/usr/aaa/dump/heap_trace.txt
通過下面參數(shù)可以控制OutOfMemoryError時打印堆的信息
-XX:+HeapDumpOnOutOfMemoryError
請看一下一個時間的Java參數(shù)配置:(服務(wù)器:Linux 64Bit,8Core×16G)
JAVA_OPTS="$JAVA_OPTS -server -Xms3G -Xmx3G -Xss256k -XX:PermSize=128m -XX:MaxPermSize=128m -XX:+UseParallelOldGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/aaa/dump -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/usr/aaa/dump/heap_trace.txt -XX:NewSize=1G -XX:MaxNewSize=1G"
經(jīng)過觀察該配置非常穩(wěn)定,每次普通GC的時間在10ms左右,F(xiàn)ull GC基本不發(fā)生,或隔很長很長的時間才發(fā)生一次
通過分析dump文件可以發(fā)現(xiàn),每個1小時都會發(fā)生一次Full GC,經(jīng)過多方求證,只要在JVM中開啟了JMX服務(wù),JMX將會1小時執(zhí)行一次Full GC以清除引用,關(guān)于這點請參考附件文檔。
參考資料:
http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html
來源:http://blog.csdn.net/chen77716/article/details/5695893
=======================================================================================
調(diào)優(yōu)方法
一切都是為了這一步,調(diào)優(yōu),在調(diào)優(yōu)之前,我們需要記住下面的原則:
1、多數(shù)的Java應(yīng)用不需要在服務(wù)器上進行GC優(yōu)化;
2、多數(shù)導(dǎo)致GC問題的Java應(yīng)用,都不是因為我們參數(shù)設(shè)置錯誤,而是代碼問題;
3、在應(yīng)用上線之前,先考慮將機器的JVM參數(shù)設(shè)置到最優(yōu)(最適合);
4、減少創(chuàng)建對象的數(shù)量;
5、減少使用全局變量和大對象;
6、GC優(yōu)化是到最后不得已才采用的手段;
7、在實際使用中,分析GC情況優(yōu)化代碼比優(yōu)化GC參數(shù)要多得多;
**GC優(yōu)化的目的有兩個:
1、將轉(zhuǎn)移到老年代的對象數(shù)量降低到最??;
2、減少full GC的執(zhí)行時間;
為了達到上面的目的,一般地,你需要做的事情有:
1、減少使用全局變量和大對象;
2、調(diào)整新生代的大小到最合適;
3、設(shè)置老年代的大小為最合適;
4、選擇合適的GC收集器;
在上面的4條方法中,用了幾個“合適”,那究竟什么才算合適,一般的,請參考上面“收集器搭配”和“啟動內(nèi)存分配”兩節(jié)中的建議。但這些建議不是萬能的,需要根據(jù)您的機器和應(yīng)用情況進行發(fā)展和變化,實際操作中,可以將兩臺機器分別設(shè)置成不同的GC參數(shù),并且進行對比,選用那些確實提高了性能或減少了GC時間的參數(shù)。
真正熟練的使用GC調(diào)優(yōu),是建立在多次進行GC監(jiān)控和調(diào)優(yōu)的實戰(zhàn)經(jīng)驗上的,進行監(jiān)控和調(diào)優(yōu)的一般步驟為:
1,監(jiān)控GC的狀態(tài)
使用各種JVM工具,查看當(dāng)前日志,分析當(dāng)前JVM參數(shù)設(shè)置,并且分析當(dāng)前堆內(nèi)存快照和gc日志,根據(jù)實際的各區(qū)域內(nèi)存劃分和GC執(zhí)行時間,覺得是否進行優(yōu)化;
2,分析結(jié)果,判斷是否需要優(yōu)化
如果各項參數(shù)設(shè)置合理,系統(tǒng)沒有超時日志出現(xiàn),GC頻率不高,GC耗時不高,那么沒有必要進行GC優(yōu)化;如果GC時間超過1-3秒,或者頻繁GC,則必須優(yōu)化;
注:如果滿足下面的指標,則一般不需要進行GC:
Minor GC執(zhí)行時間不到50ms;
Minor GC執(zhí)行不頻繁,約10秒一次;
Full GC執(zhí)行時間不到1s;
Full GC執(zhí)行頻率不算頻繁,不低于10分鐘1次;
3,調(diào)整GC類型和內(nèi)存分配
如果內(nèi)存分配過大或過小,或者采用的GC收集器比較慢,則應(yīng)該優(yōu)先調(diào)整這些參數(shù),并且先找1臺或幾臺機器進行beta,然后比較優(yōu)化過的機器和沒有優(yōu)化的機器的性能對比,并有針對性的做出最后選擇;
4,不斷的分析和調(diào)整
通過不斷的試驗和試錯,分析并找到最合適的參數(shù)
5,全面應(yīng)用參數(shù)
如果找到了最合適的參數(shù),則將這些參數(shù)應(yīng)用到所有服務(wù)器,并進行后續(xù)跟蹤。
