查看Linux環(huán)境信息
1、查看cpu的情況
2個(gè)插槽,每個(gè)8核
cat /proc/cpuinfo
physical id : 0 //物理ID
siblings : 8
core id : 0
cpu cores : 8 //8核
或者
# lscpu //cpu統(tǒng)計(jì)信息
# fdisk -l //查看硬盤及分區(qū)信息
# lsblk //查看硬盤與分區(qū)信息
# cat /proc/meminfo //查看內(nèi)存使用量和交換區(qū)使用量
#free –h //查看統(tǒng)計(jì)內(nèi)存信息
2、查看內(nèi)存的情況
free -h
used + free + buff 基本等于 total
- used是被使用的
- free是完全沒(méi)有被使用的
- shared是被程序之間可以(已經(jīng)被)共享使用的
- buffers是指用來(lái)給塊設(shè)備做的緩沖大小,它只記錄文件系統(tǒng)的metadata以及 tracking in-flight pages
- cached是用來(lái)給文件做緩沖
也就是 buffers是用來(lái)存儲(chǔ)目錄里面有什么內(nèi)容,權(quán)限等等。
而cached直接用來(lái)緩存我們打開(kāi)的文件
available才是你的"可用內(nèi)存" , 而不是像過(guò)去那樣簡(jiǎn)單的把free和buffer加起來(lái)
available 小于 free+buffer 是一定的了
配置調(diào)整
配置tomcat內(nèi)存
修改/data/apps/tomcat-linux/bin/catalina.sh
添加上
配置成一樣的, 省得沒(méi)事兒就去動(dòng)態(tài)調(diào)整堆大小, 一定程度上可以提高一點(diǎn)性能
JAVA_OPTS="$JAVA_OPTS -Xms32000M -Xmx32000M"
配置gc日志,在catalina.sh上增加
JAVA_OPTS="$JAVA_OPTS -Xms32000M -Xmx32000M -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:$CATALINA_HOME/logs/gc.%p.log"
在JVM的啟動(dòng)參數(shù)中加入-XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime,按照參數(shù)的順序分別輸出GC的簡(jiǎn)要信息,GC的詳細(xì)信息、GC的時(shí)間信息及GC造成的應(yīng)用暫停的時(shí)間。
要設(shè)置CATALINA_HOME的環(huán)境變量
安裝完成后需要配置一下環(huán)境變量,編輯/etc/profile文件:
在文件尾部添加如下配置:
JAVA_HOME=/data/apps/jdk1.8.0_111
JRE_HOME=/data/apps/jdk1.8.0_111/jre
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib
PATH=$JAVA_HOME/bin:$PATH
CATALINA_HOME=/data/apps/apache-tomcat-8.5.39
export PATH JAVA_HOME CLASSPATH CATALINA_HOME
執(zhí)行
source /etc/profile
查看:export
使用catalina命令重啟項(xiàng)目
./catalina.sh start
參考配置
#-Xms16384m:設(shè)置JVM初始內(nèi)存為16384m。此值可以設(shè)置與-Xmx相同,以避免每次垃圾回收完成后JVM重新分配內(nèi)存。
#-Xmx16384m:設(shè)置JVM最大可用內(nèi)存為16384M。
#-Xmn6144m:設(shè)置年輕代大小為6144M。整個(gè)JVM內(nèi)存大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小為64m,所以增大年輕代后,將會(huì)減小年老代大小。此值對(duì)系統(tǒng)性能影響較大,Sun官方推薦配置為整個(gè)堆的3/8。
#-Xss128k:設(shè)置每個(gè)線程的堆棧大小。JDK5.0以后每個(gè)線程堆棧大小為1M,以前每個(gè)線程堆棧大小為256K。更具應(yīng)用的線程所需內(nèi)存大小進(jìn)行調(diào)整。在相同物理內(nèi)存下,減小這個(gè)值能生成更多的線程。但是操作系統(tǒng)對(duì)一個(gè)進(jìn)程內(nèi)的線程數(shù)還是有限制的,不能無(wú)限生成,經(jīng)驗(yàn)值在3000~5000左右。
#-XX:+UseParallelGC:選擇垃圾收集器為并行收集器。此配置僅對(duì)年輕代有效。即上述配置下,年輕代使用并發(fā)收集,而年老代仍舊使用串行收集。
#-XX:ParallelGCThreads=20:配置并行收集器的線程數(shù),即:同時(shí)多少個(gè)線程一起進(jìn)行垃圾回收。此值最好配置與處理器數(shù)目相等。
#JAVA_OPTS="-Xms16384m -Xmx16384m -Xmn6144m -Xss5m -XX:+UseParallelGC -XX:ParallelGCThreads=8"
JAVA_OPTS="-Xms22528m -Xmx22528m -Xmn5632m -Xss5m -XX:+UseParallelGC -XX:ParallelGCThreads=8"
訪問(wèn):GC分析網(wǎng)站 可以上傳GC日志來(lái)分析系統(tǒng)的GC情況
配置tomcat的線程數(shù)
打開(kāi)%Tomcat_HOME%/conf/server.xml文檔,找到<Connector port="8080"....>一欄。
在Connector port = "8080"后面加上相應(yīng)地參數(shù)控制線程數(shù),控制參數(shù)如下:
| 參數(shù) | 含義 | 默認(rèn)值 |
|---|---|---|
| maxThreads | tomcat起動(dòng)的最大線程數(shù),即同時(shí)處理的任務(wù)個(gè)數(shù) | 200 |
| acceptCount | 當(dāng)tomcat起動(dòng)的線程數(shù)達(dá)到最大時(shí),接受排隊(duì)的請(qǐng)求個(gè)數(shù) | 100 |
要調(diào)整Tomcat的默認(rèn)最大連接數(shù),可以增加這兩個(gè)屬性的值,并且使acceptCount大于等于maxThreads,設(shè)置完成后如下:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" acceptCount="500" maxThreads="400" />
監(jiān)控工具
uptime
`[root@centos7_template ~]``# uptime 10:31:42 up 4 days, 1:01, 1 user,load average: 0.02, 0.02, 0.05`
該命令將顯示目前服務(wù)器持續(xù)運(yùn)行的時(shí)間,以及負(fù)載情況。
10:31:42 ``//``當(dāng)前系統(tǒng)時(shí)間
up 4 days, 1:01 ``//``持續(xù)運(yùn)行時(shí)間,時(shí)間越大,說(shuō)明你的機(jī)器越穩(wěn)定。
1 user ``//``用戶連接數(shù),是總連接數(shù)而不是用戶數(shù)
load average: 0.02, 0.02, 0.05 ``//``系統(tǒng)平均負(fù)載,統(tǒng)計(jì)最近1,5,15分鐘的系統(tǒng)平均負(fù)載
通過(guò)這個(gè)命令,可以最簡(jiǎn)便的看出系統(tǒng)當(dāng)前基本狀態(tài)信息,這里面最有用是負(fù)載指標(biāo),如果你還想查看當(dāng)前系統(tǒng)的CPU/內(nèi)存以及相關(guān)的進(jìn)程狀態(tài),可以使用TOP命令。
TOP
通過(guò)TOP命令可以詳細(xì)看出當(dāng)前系統(tǒng)的CPU、內(nèi)存、負(fù)載以及各進(jìn)程狀態(tài)(PID、進(jìn)程占用CPU、內(nèi)存、用戶)等。從上面的結(jié)果看出該系統(tǒng)上安裝了MySQL、java,可以看到他們各自的進(jìn)程ID,假如這時(shí)Java進(jìn)程占用較高的CPU和內(nèi)存,那么你就要留心了,如果程序中沒(méi)有計(jì)算量特別大、占用內(nèi)存特別多的代碼,可能你的java程序出現(xiàn)了未知的問(wèn)題,可以根據(jù)進(jìn)程ID做進(jìn)一步的跟蹤。除了通過(guò)TOP命令找到進(jìn)程信息以外,還可以通過(guò)jdk自帶的工具JPS直接找到j(luò)ava程序的進(jìn)程號(hào)。
JPS
可以看到j(luò)ps命令直接羅列出了當(dāng)前系統(tǒng)中存在的java進(jìn)程,通過(guò)這種方法查詢到j(luò)ava程序的進(jìn)程ID后,可以進(jìn)一步通過(guò):
top -p 3618 // 這里的3618就是上面查詢到的java程序的進(jìn)程ID
過(guò)此方法可以準(zhǔn)確的查看指定java進(jìn)程的CPU/內(nèi)存使用情況。
除此之外,vmstat命令也可以查看系統(tǒng)CPU/內(nèi)存、swap、io等情況
vmstat
vmstat 1 4
上面的命令每隔1秒采樣一次,一共采樣四次。
CPU占用率很高,上下文切換頻繁,說(shuō)明系統(tǒng)有線程正在頻繁切換,這可能是你的程序開(kāi)啟了大量的線程存在資源競(jìng)爭(zhēng)的情況。另外swap也是值得關(guān)注的指標(biāo),如果swpd過(guò)高則可能系統(tǒng)能使用的物理內(nèi)存不足,不得不使用交換區(qū)內(nèi)存,還有一個(gè)例外就是某些程序優(yōu)先使用swap,導(dǎo)致swap飆升,而物理內(nèi)存還有很多空余,這些情況是需要注意的。
pidstat
查看系統(tǒng)指標(biāo),還有一個(gè)第三方工具:pidstat,這個(gè)工具還是很好用的,需要先安裝:
yum install sysstat
運(yùn)行
pidstat -p 3618 -u 1 4
該命令監(jiān)控進(jìn)程id為3618的CPU狀態(tài),每隔1秒采樣一次,一共采樣四次?!?CPU”表示CPU使用情況,“CPU”則表示正在使用哪個(gè)核的CPU,這里為0表示正在使用第一個(gè)核。如果還要顯示線程ID,則可以使用:
pidstat -p 3618 -u -t 1 4
如果要監(jiān)控磁盤讀寫情況,這可以使用:
pidstat -p 3618 -d 1 4
pidstat還有其他的參數(shù),可以通過(guò)pidstat --help獲取,再次不再贅述。
JDK自帶工具
jstat
jstat:用于輸出java程序內(nèi)存使用情況,包括新生代、老年代、元數(shù)據(jù)區(qū)容量、垃圾回收情況。
可以實(shí)時(shí)監(jiān)測(cè)系統(tǒng)資源占用與jvm運(yùn)行情況
jstat -gcutil 3618 2000 20
上述命令輸出進(jìn)程ID為3618的內(nèi)存使用情況(每2000毫秒輸出一次,一共輸出20次)
- S0:幸存1區(qū)當(dāng)前使用比例
- S1:幸存2區(qū)當(dāng)前使用比例
- E:伊甸園區(qū)使用比例
- O:老年代使用比例
- M:元數(shù)據(jù)區(qū)使用比例
- CCS:壓縮使用比例
- YGC:年輕代垃圾回收次數(shù)
- FGC:老年代垃圾回收次數(shù)
- FGCT:老年代垃圾回收消耗時(shí)間
- GCT:垃圾回收消耗總時(shí)間
jmap
jmap:用于輸出java程序中內(nèi)存對(duì)象的情況,包括有哪些對(duì)象,對(duì)象的數(shù)量。
jmap -histo 3618
上述命令打印出進(jìn)程ID為3618的內(nèi)存情況。但我們常用的方式是將指定進(jìn)程的內(nèi)存heap輸出到外部文件,再由專門的heap分析工具進(jìn)行分析,例如mat(Memory Analysis Tool),所以我們常用的命令是:
jmap -dump:live,format=b,file=heap.hprof 3618
將heap.hprof傳輸出來(lái)到window電腦上使用mat工具分析:
或者使用命令生成dump文件
jmap -dump:format=b,file=文件名 pid
可采用
jmap -histo pid > a.log
日志將其保存到文件中,在一段時(shí)間后,使用文本對(duì)比工具,可以對(duì)比出GC回收了哪些對(duì)象
-finalizerinfo 打印正等候回收的對(duì)象的信息
$jmap -finalizerinfo 3772
Attaching to process ID 3772, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 20.0-b11
Number of objects pending for finalization: 0 (等候回收的對(duì)象為0個(gè))
? -heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情況.
$jmap –heap 3772
using parallel threads in the new generation. ##新生代采用的是并行線程處理方式
using thread-local object allocation.
Concurrent Mark-Sweep GC ##同步并行垃圾回收
Heap Configuration: ##堆配置情況
MinHeapFreeRatio = 40 ##最小堆使用比例
MaxHeapFreeRatio = 70 ##最大堆可用比例
MaxHeapSize = 2147483648 (2048.0MB) ##最大堆空間大小
NewSize = 268435456 (256.0MB) ##新生代分配大小
MaxNewSize = 268435456 (256.0MB) ##最大可新生代分配大小
OldSize = 5439488 (5.1875MB) ##老生代大小
NewRatio = 2 ##新生代比例
SurvivorRatio = 8 ##新生代與suvivor的比例
PermSize = 134217728 (128.0MB) ##perm區(qū)大小
MaxPermSize = 134217728 (128.0MB) ##最大可分配perm區(qū)大小
Heap Usage: ##堆使用情況
New Generation (Eden + 1 Survivor Space): ##新生代(伊甸區(qū) + survior空間)
capacity = 241631232 (230.4375MB) ##伊甸區(qū)容量
used = 77776272 (74.17323303222656MB) ##已經(jīng)使用大小
free = 163854960 (156.26426696777344MB) ##剩余容量
32.188004570534986% used ##使用比例
Eden Space: ##伊甸區(qū)
capacity = 214827008 (204.875MB) ##伊甸區(qū)容量
used = 74442288 (70.99369812011719MB) ##伊甸區(qū)使用
free = 140384720 (133.8813018798828MB) ##伊甸區(qū)當(dāng)前剩余容量
34.65220164496263% used ##伊甸區(qū)使用情況
From Space: ##survior1區(qū)
capacity = 26804224 (25.5625MB) ##survior1區(qū)容量
used = 3333984 (3.179534912109375MB) ##surviror1區(qū)已使用情況
free = 23470240 (22.382965087890625MB) ##surviror1區(qū)剩余容量
12.43827838477995% used ##survior1區(qū)使用比例
To Space: ##survior2 區(qū)
capacity = 26804224 (25.5625MB) ##survior2區(qū)容量
used = 0 (0.0MB) ##survior2區(qū)已使用情況
free = 26804224 (25.5625MB) ##survior2區(qū)剩余容量
0.0% used ## survior2區(qū)使用比例
concurrent mark-sweep generation: ##老生代使用情況
capacity = 1879048192 (1792.0MB) ##老生代容量
used = 30847928 (29.41887664794922MB) ##老生代已使用容量
free = 1848200264 (1762.5811233520508MB) ##老生代剩余容量
1.6416783843721663% used ##老生代使用比例
Perm Generation: ##perm區(qū)使用情況 永久代
capacity = 134217728 (128.0MB) ##perm區(qū)容量
used = 47303016 (45.111671447753906MB) ##perm區(qū)已使用容量
free = 86914712 (82.8883285522461MB) ##perm區(qū)剩余容量
35.24349331855774% used ##perm區(qū)使用比例
? -histo[:live] 打印每個(gè)class的實(shí)例數(shù)目,內(nèi)存占用,類全名信息. VM的內(nèi)部類名字開(kāi)頭會(huì)加上前綴”*”. 如果live子參數(shù)加上后,只統(tǒng)計(jì)活的對(duì)象數(shù)量.
$jmap–histo:live 3772
num #instances #bytes class name
1: 65220 9755240 <constMethodKlass>
2: 65220 8880384 <methodKlass>
3: 11721 8252112 [B
4: 6300 6784040 <constantPoolKlass>
5: 75224 6218208 [C
jstack
jstack:用戶輸出java程序線程棧的情況,常用于定位因?yàn)槟承┚€程問(wèn)題造成的故障或性能問(wèn)題。
jstack 3618 > jstack.out
上述命令將進(jìn)程ID為3618的棧信息輸出到外部文件,便于傳輸?shù)絯indows電腦上進(jìn)行分析。
Windows環(huán)境下的監(jiān)控工具
Windows環(huán)境下的監(jiān)控工具也有很多,但是本文主要推薦jvisualvm.exe、MemoryAnalyzer.exe,有了他們其他工具幾乎不需要了。
jvisualvm
jvisualvm.exe在JDK安裝目錄下的bin目錄下面,雙擊即可打開(kāi)。
雙擊左側(cè)你需要監(jiān)控的java程序即可對(duì)它進(jìn)行監(jiān)控,這個(gè)工具包括對(duì)CPU、內(nèi)存、線程、類都做了監(jiān)控,功能非常強(qiáng)大,上文中介紹的所有功能,其他在這個(gè)工具上都已經(jīng)有了。當(dāng)然怎么用、如何分析它需要花時(shí)間去一點(diǎn)點(diǎn)積累。
MemoryAnalyzer
MemoryAnalyzer.exe:上文我們已經(jīng)提到,常用于分析內(nèi)存堆使用情況,也是非常強(qiáng)大的工具。詳細(xì)使用方法,這里就不再贅述,可以下載下來(lái)嘗試一下。
上述介紹了基于Linux、Windows環(huán)境的監(jiān)控工具,有了這些工具我們就要利用他們做對(duì)應(yīng)的事情,下面將通過(guò)一個(gè)簡(jiǎn)單的案例,說(shuō)明如何使用他們。
其他
打堆棧
1、查出進(jìn)程
ps -ef | grep tomcat
2、打堆棧,一般每隔3秒打一個(gè),打三至四個(gè)
jstack pid > a1.txt
jstack pid > a2.txt
jstack pid > a3.txt
/data/apps/tomcat-linux-jdk/jdk1.8.0_192/bin/jstack 17973
while true; do (echo "%CPU %MEM ARGS $(date)" && ps -e -o pcpu,pmem,args --sort=pcpu | cut -d" " -f1-5 | tail) >> ps.log; sleep 5; done
while true; do uptime >> uptime.log; sleep 1; done
JVM調(diào)優(yōu)總結(jié) -Xms -Xmx -Xmn -Xss
-
堆大小設(shè)置
JVM 中最大堆大小有三方面限制:相關(guān)操作系統(tǒng)的數(shù)據(jù)模型(32-bt還是64-bit)限制;系統(tǒng)的可用虛擬內(nèi)存限制;系統(tǒng)的可用物理內(nèi)存限制。32位系統(tǒng)下,一般限制在1.5G~2G;64為操作系統(tǒng)對(duì)內(nèi)存無(wú)限制。我在Windows Server 2003 系統(tǒng),3.5G物理內(nèi)存,JDK5.0下測(cè)試,最大可設(shè)置為1478m。
典型設(shè)置:
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-****Xmx3550m:設(shè)置JVM最大可用內(nèi)存為3550M。
-Xms3550m:設(shè)置JVM初始內(nèi)存為3550m。此值可以設(shè)置與-Xmx相同,以避免每次垃圾回收完成后JVM重新分配內(nèi)存。
-Xmn2g:設(shè)置年輕代大小為2G。整個(gè)JVM內(nèi)存大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小為64m,所以增大年輕代后,將會(huì)減小年老代大小。此值對(duì)系統(tǒng)性能影響較大,Sun官方推薦配置為整個(gè)堆的3/8。
-Xss128k:設(shè)置每個(gè)線程的堆棧大小。JDK5.0以后每個(gè)線程堆棧大小為1M,以前每個(gè)線程堆棧大小為256K。更具應(yīng)用的線程所需內(nèi)存大小進(jìn)行調(diào)整。在相同物理內(nèi)存下,減小這個(gè)值能生成更多的線程。但是操作系統(tǒng)對(duì)一個(gè)進(jìn)程內(nèi)的線程數(shù)還是有限制的,不能無(wú)限生成,經(jīng)驗(yàn)值在3000~5000左右。 - java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-XX:NewRatio=4:設(shè)置年輕代(包括Eden和兩個(gè)Survivor區(qū))與年老代的比值(除去持久代)。設(shè)置為4,則年輕代與年老代所占比值為1:4,年輕代占整個(gè)堆棧的1/5
-XX:SurvivorRatio=4:設(shè)置年輕代中Eden區(qū)與Survivor區(qū)的大小比值。設(shè)置為4,則兩個(gè)Survivor區(qū)與一個(gè)Eden區(qū)的比值為2:4,一個(gè)Survivor區(qū)占整個(gè)年輕代的1/6
-XX:MaxPermSize=16m:設(shè)置持久代大小為16m。
-XX:MaxTenuringThreshold=0:設(shè)置垃圾最大年齡。如果設(shè)置為0的話,則年輕代對(duì)象不經(jīng)過(guò)Survivor區(qū),直接進(jìn)入年老代。對(duì)于年老代比較多的應(yīng)用,可以提高效率。如果將此值設(shè)置為一個(gè)較大值,則年輕代對(duì)象會(huì)在Survivor區(qū)進(jìn)行多次復(fù)制,這樣可以增加對(duì)象再年輕代的存活時(shí)間,增加在年輕代即被回收的概論。
-
回收器選擇
JVM給了三種選擇:
串行收集器、并行收集器、并發(fā)收集器
,但是串行收集器只適用于小數(shù)據(jù)量的情況,所以這里的選擇主要針對(duì)并行收集器和并發(fā)收集器。默認(rèn)情況下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在啟動(dòng)時(shí)加入相應(yīng)參數(shù)。JDK5.0以后,JVM會(huì)根據(jù)當(dāng)前
系統(tǒng)配置
進(jìn)行判斷。
-
吞吐量?jī)?yōu)先
的并行收集器
如上文所述,并行收集器主要以到達(dá)一定的吞吐量為目標(biāo),適用于科學(xué)技術(shù)和后臺(tái)處理等。
典型配置
:
java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelGC:選擇垃圾收集器為并行收集器。此配置僅對(duì)年輕代有效。即上述配置下,年輕代使用并發(fā)收集,而年老代仍舊使用串行收集。****-XX:ParallelGCThreads=20:配置并行收集器的線程數(shù),即:同時(shí)多少個(gè)線程一起進(jìn)行垃圾回收。此值最好配置與處理器數(shù)目相等。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC****-XX:+UseParallelOldGC:配置年老代垃圾收集方式為并行收集。JDK6.0支持對(duì)年老代并行收集。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100****-XX:MaxGCPauseMillis=100:設(shè)置每次年輕代垃圾回收的最長(zhǎng)時(shí)間,如果無(wú)法滿足此時(shí)間,JVM會(huì)自動(dòng)調(diào)整年輕代大小,以滿足此值。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy-XX:+UseAdaptiveSizePolicy:設(shè)置此選項(xiàng)后,并行收集器會(huì)自動(dòng)選擇年輕代區(qū)大小和相應(yīng)的Survivor區(qū)比例,以達(dá)到目標(biāo)系統(tǒng)規(guī)定的最低相應(yīng)時(shí)間或者收集頻率等,此值建議使用并行收集器時(shí),一直打開(kāi)。
-
響應(yīng)時(shí)間優(yōu)先
的并發(fā)收集器
如上文所述,并發(fā)收集器主要是保證系統(tǒng)的響應(yīng)時(shí)間,減少垃圾收集時(shí)的停頓時(shí)間。適用于應(yīng)用服務(wù)器、電信領(lǐng)域等。
典型配置
:
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC****-XX:+UseConcMarkSweepGC:設(shè)置年老代為并發(fā)收集。測(cè)試中配置這個(gè)以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此時(shí)年輕代大小最好用-Xmn設(shè)置。** -XX:+UseParNewGC**:設(shè)置年輕代為并行收集??膳cCMS收集同時(shí)使用。JDK5.0以上,JVM會(huì)根據(jù)系統(tǒng)配置自行設(shè)置,所以無(wú)需再設(shè)置此值。
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction:由于并發(fā)收集器不對(duì)內(nèi)存空間進(jìn)行壓縮、整理,所以運(yùn)行一段時(shí)間以后會(huì)產(chǎn)生“碎片”,使得運(yùn)行效率降低。此值設(shè)置運(yùn)行多少次GC以后對(duì)內(nèi)存空間進(jìn)行壓縮、整理。 -XX:+UseCMSCompactAtFullCollection:打開(kāi)對(duì)年老代的壓縮??赡軙?huì)影響性能,但是可以消除碎片
-
-
輔助信息
JVM提供了大量命令行參數(shù),打印信息,供調(diào)試使用。主要有以下一些:
-
-XX:+PrintGC
輸出形式
:[GC 118250K->113543K(130112K), 0.0094143 secs]
[Full GC 121376K->10414K(130112K), 0.0650971 secs]
-
-XX:+PrintGCDetails
輸出形式
:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
[GC DefNew: 8614K->8614K(9088K), 0.0000665 secs 121376K->10414K(130112K), 0.0436268 secs]
-XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps可與上面兩個(gè)混合使用 輸出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
-XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中斷的執(zhí)行時(shí)間??膳c上面混合使用 輸出形式:Application time: 0.5291524 seconds
-XX:+PrintGCApplicationStoppedTime:打印垃圾回收期間程序暫停的時(shí)間??膳c上面混合使用 輸出形式:Total time for which application threads were stopped: 0.0468229 seconds
-XX:PrintHeapAtGC:打印GC前后的詳細(xì)堆棧信息
-Xloggc:filename:與上面幾個(gè)配合使用,把相關(guān)日志信息記錄到文件以便分析。
-
-
常見(jiàn)配置匯總
-
堆設(shè)置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:設(shè)置年輕代大小
-XX:NewRatio=n:設(shè)置年輕代和年老代的比值。如:為3,表示年輕代與年老代比值為1:3,年輕代占整個(gè)年輕代年老代和的1/4
-XX:SurvivorRatio=n:年輕代中Eden區(qū)與兩個(gè)Survivor區(qū)的比值。注意Survivor區(qū)有兩個(gè)。如:3,表示Eden:Survivor=3:2,一個(gè)Survivor區(qū)占整個(gè)年輕代的1/5
-XX:MaxPermSize=n:設(shè)置持久代大小
-
收集器設(shè)置
-XX:+UseSerialGC:設(shè)置串行收集器
-XX:+UseParallelGC:設(shè)置并行收集器
-XX:+UseParalledlOldGC:設(shè)置并行年老代收集器
-XX:+UseConcMarkSweepGC:設(shè)置并發(fā)收集器
-
垃圾回收統(tǒng)計(jì)信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
-
并行收集器設(shè)置
-XX:ParallelGCThreads=n:設(shè)置并行收集器收集時(shí)使用的CPU數(shù)。并行收集線程數(shù)。
-XX:MaxGCPauseMillis=n:設(shè)置并行收集最大暫停時(shí)間
-XX:GCTimeRatio=n:設(shè)置垃圾回收時(shí)間占程序運(yùn)行時(shí)間的百分比。公式為1/(1+n)
-
并發(fā)收集器設(shè)置
-XX:+CMSIncrementalMode:設(shè)置為增量模式。適用于單CPU情況。
-XX:ParallelGCThreads=n:設(shè)置并發(fā)收集器年輕代收集方式為并行收集時(shí),使用的CPU數(shù)。并行收集線程數(shù)。
-
四、調(diào)優(yōu)總結(jié)
-
年輕代大小選擇
響應(yīng)時(shí)間優(yōu)先的應(yīng)用:盡可能設(shè)大,直到接近系統(tǒng)的最低響應(yīng)時(shí)間限制(根據(jù)實(shí)際情況選擇)。在此種情況下,年輕代收集發(fā)生的頻率也是最小的。同時(shí),減少到達(dá)年老代的對(duì)象。
吞吐量?jī)?yōu)先的應(yīng)用:盡可能的設(shè)置大,可能到達(dá)Gbit的程度。因?yàn)閷?duì)響應(yīng)時(shí)間沒(méi)有要求,垃圾收集可以并行進(jìn)行,一般適合8CPU以上的應(yīng)用。
-
年老代大小選擇
-
響應(yīng)時(shí)間優(yōu)先的應(yīng)用
:年老代使用并發(fā)收集器,所以其大小需要小心設(shè)置,一般要考慮
并發(fā)會(huì)話率
和
會(huì)話持續(xù)時(shí)間
等一些參數(shù)。如果堆設(shè)置小了,可以會(huì)造成內(nèi)存碎片、高回收頻率以及應(yīng)用暫停而使用傳統(tǒng)的標(biāo)記清除方式;如果堆大了,則需要較長(zhǎng)的收集時(shí)間。最優(yōu)化的方案,一般需要參考以下數(shù)據(jù)獲得:
并發(fā)垃圾收集信息
持久代并發(fā)收集次數(shù)
傳統(tǒng)GC信息
花在年輕代和年老代回收上的時(shí)間比例
減少年輕代和年老代花費(fèi)的時(shí)間,一般會(huì)提高應(yīng)用的效率
吞吐量?jī)?yōu)先的應(yīng)用:一般吞吐量?jī)?yōu)先的應(yīng)用都有一個(gè)很大的年輕代和一個(gè)較小的年老代。原因是,這樣可以盡可能回收掉大部分短期對(duì)象,減少中期的對(duì)象,而年老代盡存放長(zhǎng)期存活對(duì)象。
-
-
較小堆引起的碎片問(wèn)題
因?yàn)槟昀洗牟l(fā)收集器使用標(biāo)記、清除算法,所以不會(huì)對(duì)堆進(jìn)行壓縮。當(dāng)收集器回收時(shí),他會(huì)把相鄰的空間進(jìn)行合并,這樣可以分配給較大的對(duì)象。但是,當(dāng)堆空間較小時(shí),運(yùn)行一段時(shí)間以后,就會(huì)出現(xiàn)“碎片”,如果并發(fā)收集器找不到足夠的空間,那么并發(fā)收集器將會(huì)停止,然后使用傳統(tǒng)的標(biāo)記、清除方式進(jìn)行回收。如果出現(xiàn)“碎片”,可能需要進(jìn)行如下配置:
-XX:+UseCMSCompactAtFullCollection:使用并發(fā)收集器時(shí),開(kāi)啟對(duì)年老代的壓縮。
-XX:CMSFullGCsBeforeCompaction=0:上面配置開(kāi)啟的情況下,這里設(shè)置多少次Full GC后,對(duì)年老代進(jìn)行壓縮
案例分析
首先通過(guò)上述的介紹,我們對(duì)故障排查流程應(yīng)該有了一個(gè)印象,這里先梳理出來(lái):
案例:
一個(gè)java應(yīng)用啟動(dòng)以后,使用人員發(fā)現(xiàn)應(yīng)用不可用,針對(duì)該現(xiàn)象我們做以下分析排查:
1、首先查看服務(wù)器上的應(yīng)用狀態(tài)。使用jps命令查詢當(dāng)前在運(yùn)行中的java進(jìn)程:
這里進(jìn)程ID為6400的java應(yīng)用就是我們剛啟用的,說(shuō)明應(yīng)用并沒(méi)有掛掉,還在運(yùn)行中。
2、通過(guò)進(jìn)程ID查詢所占用的CPU、內(nèi)存以及當(dāng)前負(fù)載情況,top -p 6400。
從以上結(jié)果看出該應(yīng)用并沒(méi)有引起系統(tǒng)負(fù)載過(guò)高,CPU、內(nèi)存也沒(méi)有出現(xiàn)異常情況。
3、通過(guò)上述結(jié)果我們推測(cè)因?yàn)閮?nèi)存原因引起的故障可性能較小,所以我們優(yōu)先排查線程棧,使用jstack命令,導(dǎo)出線程棧。
jstack 6400 > stack.out
我們將該文件傳輸出來(lái)便于查看。

查看線程??梢钥闯?,主線程處于運(yùn)行狀態(tài),而子線程ThreadA、ThreadB、ThreadC、ThreadD一邊在等待一個(gè)鎖,同時(shí)又持有另外一個(gè)鎖,其實(shí)看到這里我們基本推斷該應(yīng)用程序存在死鎖,因此造成線程等待,應(yīng)用不可用。通過(guò)以上棧的信息,我們就可以到程序代碼中詳細(xì)查看代碼了,并且修改bug解決此問(wèn)題。
造成死鎖的原因是線程之間存在相互制約的情況,而任一線程都無(wú)法繼續(xù)執(zhí)行。
實(shí)戰(zhàn)
一、模擬OutOfMemoryError
內(nèi)存溢出
內(nèi)存溢出 out of memory,是指程序在申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的內(nèi)存空間供其使用,出現(xiàn)out of memory;
public static void main(String[] args) {
List list = new ArrayList<Map>();
int i = 0;
while (true){
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Random random = new Random();
Map map= new HashMap();
map.put(i, random.nextInt(100000));
list.add(map);
System.out.println(i++);
}
}
-
在IDEA中指定JVM內(nèi)存的大小image.png
-
打開(kāi)JDK中的JMC,查看內(nèi)存的情況jmc圖片1
jmc圖片2 -
打開(kāi)JDK中的jvisualvm,查看內(nèi)存情況
image.png 最后結(jié)果:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
二、模擬StackOverflowError
棧溢出
如果一個(gè)線程在計(jì)算時(shí)所需要用到棧大小 > 配置允許最大的棧大小,那么Java虛擬機(jī)將拋出StackOverflowError
public static void main(String[] args) {
int n = 100000;
long sum=sum(n);
System.out.println(sum);
}
public static long sum(int n) {
System.out.println(n);
if (n > 0) {
return n + sum(n - 1);
} else {
return 0;
}
}
設(shè)置 -Xss10m 默認(rèn)是1m的
當(dāng)棧內(nèi)存超過(guò)系統(tǒng)配置的棧內(nèi)存-Xss,就會(huì)出現(xiàn)java.lang.StackOverflowError異常。這也是為什么對(duì)于需要謹(jǐn)慎使用遞歸調(diào)用的原因!
三、內(nèi)存泄漏
內(nèi)存泄露 memory leak,是指程序在申請(qǐng)內(nèi)存后,無(wú)法釋放已申請(qǐng)的內(nèi)存空間,一次內(nèi)存泄露危害可以忽略,但內(nèi)存泄露堆積后果很嚴(yán)重,無(wú)論多少內(nèi)存,遲早會(huì)被占光。
Java中的內(nèi)存泄露,廣義并通俗的說(shuō),就是:不再會(huì)被使用的對(duì)象的內(nèi)存不能被回收,就是內(nèi)存泄露。
memory leak會(huì)最終會(huì)導(dǎo)致out of memory!
Java內(nèi)存回收機(jī)制
不論哪種語(yǔ)言的內(nèi)存分配方式,都需要返回所分配內(nèi)存的真實(shí)地址,也就是返回一個(gè)指針到內(nèi)存塊的首地址。Java中對(duì)象是采用new或者反射的方法創(chuàng)建的,這些對(duì)象的創(chuàng)建都是在堆(Heap)中分配的,所有對(duì)象的回收都是由Java虛擬機(jī)通過(guò)垃圾回收機(jī)制完成的。GC為了能夠正確釋放對(duì)象,會(huì)監(jiān)控每個(gè)對(duì)象的運(yùn)行狀況,對(duì)他們的申請(qǐng)、引用、被引用、賦值等狀況進(jìn)行監(jiān)控,Java會(huì)使用有向圖的方法進(jìn)行管理內(nèi)存,實(shí)時(shí)監(jiān)控對(duì)象是否可以達(dá)到,如果不可到達(dá),則就將其回收,
Java內(nèi)存泄露引起原因
內(nèi)存泄露是指無(wú)用對(duì)象(不再使用的對(duì)象)持續(xù)占有內(nèi)存或無(wú)用對(duì)象的內(nèi)存得不到及時(shí)釋放,從而造成的內(nèi)存空間的浪費(fèi)稱為內(nèi)存泄露。內(nèi)存泄露有時(shí)不嚴(yán)重且不易察覺(jué),這樣開(kāi)發(fā)者就不知道存在內(nèi)存泄露,但有時(shí)也會(huì)很嚴(yán)重,會(huì)提示你Out of memory。
Java內(nèi)存泄露根本原因是什么呢?長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的引用就很可能發(fā)生內(nèi)存泄露,盡管短生命周期對(duì)象已經(jīng)不再需要,但是因?yàn)殚L(zhǎng)生命周期對(duì)象持有它的引用而導(dǎo)致不能被回收,這就是java中內(nèi)存泄露的發(fā)生場(chǎng)景。具體主要有如下幾大類:
1、靜態(tài)集合類引起內(nèi)存泄露:
像HashMap、Vector等的使用最容易出現(xiàn)內(nèi)存泄露,這些靜態(tài)變量的生命周期和應(yīng)用程序一致,他們所引用的所有的對(duì)象Object也不能被釋放,因?yàn)樗麄円矊⒁恢北籚ector等引用著。
Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}
在這個(gè)例子中,循環(huán)申請(qǐng)Object 對(duì)象,并將所申請(qǐng)的對(duì)象放入一個(gè)Vector 中,如果僅僅釋放引用本身(o=null),那么Vector 仍然引用該對(duì)象,所以這個(gè)對(duì)象對(duì)GC 來(lái)說(shuō)是不可回收的。因此,如果對(duì)象加入到Vector 后,還必須從Vector 中刪除,最簡(jiǎn)單的方法就是將Vector對(duì)象設(shè)置為null。
2、當(dāng)集合里面的對(duì)象屬性被修改后,再調(diào)用remove()方法時(shí)不起作用。
public static void main(String[] args)
{
Set<Person> set = new HashSet<Person>();
Person p1 = new Person("唐僧","pwd1",25);
Person p2 = new Person("孫悟空","pwd2",26);
Person p3 = new Person("豬八戒","pwd3",27);
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println("總共有:"+set.size()+" 個(gè)元素!"); //結(jié)果:總共有:3 個(gè)元素!
p3.setAge(2); //修改p3的年齡,此時(shí)p3元素對(duì)應(yīng)的hashcode值發(fā)生改變
set.remove(p3); //此時(shí)remove不掉,造成內(nèi)存泄漏
set.add(p3); //重新添加,居然添加成功
System.out.println("總共有:"+set.size()+" 個(gè)元素!"); //結(jié)果:總共有:4 個(gè)元素!
for (Person person : set)
{
System.out.println(person);
}
}
3、監(jiān)聽(tīng)器
在java 編程中,我們都需要和監(jiān)聽(tīng)器打交道,通常一個(gè)應(yīng)用當(dāng)中會(huì)用到很多監(jiān)聽(tīng)器,我們會(huì)調(diào)用一個(gè)控件的諸如addXXXListener()等方法來(lái)增加監(jiān)聽(tīng)器,但往往在釋放對(duì)象的時(shí)候卻沒(méi)有記住去刪除這些監(jiān)聽(tīng)器,從而增加了內(nèi)存泄漏的機(jī)會(huì)。
4、各種連接
比如數(shù)據(jù)庫(kù)連接(dataSourse.getConnection()),網(wǎng)絡(luò)連接(socket)和io連接,除非其顯式的調(diào)用了其close()方法將其連接關(guān)閉,否則是不會(huì)自動(dòng)被GC 回收的。對(duì)于Resultset 和Statement 對(duì)象可以不進(jìn)行顯式回收,但Connection 一定要顯式回收,因?yàn)镃onnection 在任何時(shí)候都無(wú)法自動(dòng)回收,而Connection一旦回收,Resultset 和Statement 對(duì)象就會(huì)立即為NULL。但是如果使用連接池,情況就不一樣了,除了要顯式地關(guān)閉連接,還必須顯式地關(guān)閉Resultset Statement 對(duì)象(關(guān)閉其中一個(gè),另外一個(gè)也會(huì)關(guān)閉),否則就會(huì)造成大量的Statement 對(duì)象無(wú)法釋放,從而引起內(nèi)存泄漏。這種情況下一般都會(huì)在try里面去的連接,在finally里面釋放連接。
6、單例模式
如果單例對(duì)象持有外部對(duì)象的引用,那么這個(gè)外部對(duì)象將不能被jvm正?;厥眨瑢?dǎo)致內(nèi)存泄露
不正確使用單例模式是引起內(nèi)存泄露的一個(gè)常見(jiàn)問(wèn)題,單例對(duì)象在被初始化后將在JVM的整個(gè)生命周期中存在(以靜態(tài)變量的方式),如果單例對(duì)象持有外部對(duì)象的引用,那么這個(gè)外部對(duì)象將不能被jvm正常回收,導(dǎo)致內(nèi)存泄露,考慮下面的例子:
class A{
public A(){
B.getInstance().setA(this);
}
....
}
//B類采用單例模式
class B{
private A a;
private static B instance=new B();
public B(){}
public static B getInstance(){
return instance;
}
public void setA(A a){
this.a=a;
}
//getter...
}
顯然B采用singleton模式,它持有一個(gè)A對(duì)象的引用,而這個(gè)A類的對(duì)象將不能被回收。想象下如果A是個(gè)比較復(fù)雜的對(duì)象或者集合類型會(huì)發(fā)生什么情況
參考文章
深入理解JVM(七)——性能監(jiān)控工具
JVM調(diào)優(yōu)總結(jié) -Xms -Xmx -Xmn -Xss
java內(nèi)存泄漏與內(nèi)存溢出




