JVM性能分析

查看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

  1. 堆大小設(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í)間,增加在年輕代即被回收的概論。
  1. 回收器選擇

    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)行判斷。

    1. 吞吐量?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)。

    2. 響應(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ì)影響性能,但是可以消除碎片

  2. 輔助信息

    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)日志信息記錄到文件以便分析。

  3. 常見(jiàn)配置匯總

    1. 堆設(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è)置持久代大小

    2. 收集器設(shè)置

      • -XX:+UseSerialGC:設(shè)置串行收集器

      • -XX:+UseParallelGC:設(shè)置并行收集器

      • -XX:+UseParalledlOldGC:設(shè)置并行年老代收集器

      • -XX:+UseConcMarkSweepGC:設(shè)置并發(fā)收集器

    3. 垃圾回收統(tǒng)計(jì)信息

      • -XX:+PrintGC

      • -XX:+PrintGCDetails

      • -XX:+PrintGCTimeStamps

      • -Xloggc:filename

    4. 并行收集器設(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)

    5. 并發(fā)收集器設(shè)置

      • -XX:+CMSIncrementalMode:設(shè)置為增量模式。適用于單CPU情況。

      • -XX:ParallelGCThreads=n:設(shè)置并發(fā)收集器年輕代收集方式為并行收集時(shí),使用的CPU數(shù)。并行收集線程數(shù)。

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

  1. 年輕代大小選擇

    • 響應(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)用。

  2. 年老代大小選擇

    • 響應(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ì)象。

  3. 較小堆引起的碎片問(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)便于查看。

picture

查看線程??梢钥闯?,主線程處于運(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++);
        }

    }
  1. 在IDEA中指定JVM內(nèi)存的大小
    image.png
  2. 打開(kāi)JDK中的JMC,查看內(nèi)存的情況
    jmc圖片1

    jmc圖片2
  3. 打開(kāi)JDK中的jvisualvm,查看內(nèi)存情況


    image.png
  4. 最后結(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)存溢出

?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • Java 虛擬機(jī)有自己完善的硬件架構(gòu), 如處理器、堆棧、寄存器等,還具有相應(yīng)的指令系統(tǒng)。JVM 屏蔽了與具體操作系...
    尹小凱閱讀 1,748評(píng)論 0 10
  • 原文閱讀 前言 這段時(shí)間懈怠了,罪過(guò)! 最近看到有同事也開(kāi)始用上了微信公眾號(hào)寫博客了,挺好的~給他們點(diǎn)贊,這博客我...
    碼農(nóng)戲碼閱讀 6,144評(píng)論 2 31
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理,因此不免有一些不準(zhǔn)確的地方,同時(shí)不同JDK版本的...
    高廣超閱讀 16,042評(píng)論 3 83
  • http://www.cnblogs.com/angeldevil/p/3801189.html值得一看 Clas...
    snail_knight閱讀 1,610評(píng)論 1 0
  • 內(nèi)存溢出和內(nèi)存泄漏的區(qū)別 內(nèi)存溢出:out of memory,是指程序在申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的內(nèi)存空間供其使用,...
    Aimerwhy閱讀 800評(píng)論 0 1

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