Java生產環境下性能監控與調優詳解完整學習筆記

本文主要內容包含

 

JVM的參數類型

標準參數(各版本中保持穩定)

-help

-server -client

-version -showversion

-cp -classpath

 

X 參數(非標準化參數)

-Xint:解釋執行

-Xcomp:第一次使用就編譯成本地代碼

-Xmixed:混合模式,JVM 自己決定是否編譯成本地代碼

示例:

java -version(默認是混合模式)

Java HotSpot(TM) 64-Bit Server VM (build 25.40-b25, mixed mode)

java -Xint -version

Java HotSpot(TM) 64-Bit Server VM (build 25.40-b25, interpreted mode)

 

XX 參數(非標準化參數)

主要用於 JVM調優和 debug

  • Boolean類型

格式:-XX:[+-]<name>表示啓用或禁用 name 屬性
如:-XX:+UseConcMarkSweepGC
-XX:+UseG1GC
  • 非Boolean類型

格式:-XX:<name>=<value>表示 name 屬性的值是 value
如:-XX:MaxGCPauseMillis=500
-xx:GCTimeRatio=19
-Xmx -Xms屬於 XX 參數
-Xms 等價於-XX:InitialHeapSize
-Xmx 等價於-XX:MaxHeapSize
-xss 等價於-XX:ThreadStackSize

查看

jinfo -flag MaxHeapSize <pid>

-XX:+PrintFlagsInitial

-XX:+PrintFlagsFinal

-XX:+UnlockExperimentalVMOptions 解鎖實驗參數

-XX:+UnlockDiagnosticVMOptions 解鎖診斷參數

-XX:+PrintCommandLineFlags 打印命令行參數

輸出結果中=表示默認值,:=表示被用戶或 JVM 修改後的值

示例:java -XX:+PrintFlagsFinal -version

 

補充:測試中需要用到 Tomcat,CentOS 7安裝示例如下

sudo yum -y install java-1.8.0-openjdk*
wget  http://mirror.bit.edu.cn/apache/tomcat/tomcat-8/v8.5.32/bin/apache-tomcat-8.5.32.tar.gz
tar -zxvf apache-tomcat-8.5.32.tar.gz 
mv apache-tomcat-8.5.32 tomcat
cd tomcat/bin/
sh startup.sh

pid 可通過類似 ps -ef|grep tomcat或 jps來進行查看

jps

詳情參考 jps官方文檔

-l

jinfo

jinfo -flag MaxHeapSize <pid>

jinfo -flags <pid>

 

jstat

詳情參考 jstat 官方文檔

jstat 使用示例

類加載

# 以下1000表每隔1000ms 即1秒,共輸出10次
jstat -class <pid> 1000 10

垃圾收集

-gc, -gcutil, -gccause, -gcnew, -gcold

jstat -gc <pid> 1000 10

以下大小的單位均爲 KB

S0C, S1C, S0U, S1U: S0和 S1的總量和使用量

EC, EU: Eden區總量與使用量

OC, OU: Old區總量與使用量

MC, MU: Metacspace區(jdk1.8前爲 PermGen)總量與使用量

CCSC, CCSU: 壓縮類區總量與使用量

YGC, YGCT: YoungGC 的次數與時間

FGC, FGCT: FullGC 的次數與時間

GCT: 總的 GC 時間

JIT 編譯

-compiler, -printcompilation

jmap+MAT

詳情參考jmap 官方文檔

內存溢出演示:

https://start.spring.io/生成初始代碼

最終代碼:monitor_tuning

爲快速產生內存溢出,右擊 Run As>Run Configurations, Arguments 標籤VM arguments 中填入

-Xmx32M -Xms32M

訪問 http://localhost:8080/heap

Exception in thread "http-nio-8080-exec-2" Exception in thread "http-nio-8080-exec-1" java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: Java heap space

-XX:MetaspaceSize=32M -XX:MaxMetaspaceSize=32M(同時在 pom.xml 中加入 asm 的依賴)

訪問 http://localhost:8080/nonheap

Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
Exception in thread "ContainerBackgroundProcessor[StandardEngine[Tomcat]]" java.lang.OutOfMemoryError: Metaspace

內存溢出自動導出

-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=./

右擊 Run As>Run Configurations, Arguments 標籤VM arguments 中填入

-Xmx32M -Xms32M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./

可以看到自動在當前目錄中生成了一個java_pid660.hprof文件

java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to ./java_pid660.hprof ...

另一種導出溢出也更推薦的方式是jmap

option: -heap, -clstats, -dump:<dump-options>, -F

jmap -dump:format=b,file=heap.hprof <pid>

 

jmap 導出溢出文件

MAT下載地址:http://www.eclipse.org/mat/

找開上述導出的內存溢出文件即可進行分析,如下圖的溢出源頭分析:

Memory Analyzer 內存溢出分析

jstack

詳情參考 jstack 官方文檔

jstack <pid>

可查看其中包含java.lang.Thread.State: WAITING (parking),JAVA 線程包含的狀態有:

NEW:線程尚未啓動

RUNNABLE:線程正在 JVM 中執行

BLOCKED:線程在等待監控鎖(monitor lock)

WAITING:線程在等待另一個線程進行特定操作(時間不確定)

TIMED_WAITING:線程等待另一個線程進行限時操作

TERMINATED:線程已退出

monitor_tuning中新增CpuController.java

mvn clean package -Dmaven.test.skip

mvn 打包提速參考 CSDN

此時會生成一個monitor_tuning-0.0.1-SNAPSHOT.jar的 jar包,爲避免本地的 CPU 消耗過多導致死機,建議上傳上傳到虛擬機進行測試

nohup java -jar monitor_tuning-0.0.1-SNAPSHOT.jar &

訪問 http://xx.xx.xx.xx:12345/loop(端口12345在application.properties文件中定義)

top -p <pid> -H可以查看線程及 CPU 消耗情況

top 命令打出線程 CPU 消耗

使用 jstack <pid>可以導出追蹤文件,文件中 PID 在 jstack 中顯示的對應 nid 爲十六進制(命令行可執行 print '%x' <pid>可以進行轉化,如1640對應的十六進制爲668)

"http-nio-12345-exec-3" #18 daemon prio=5 os_prio=0 tid=0x00007f10003fb000 nid=0x668 runnable [0x00007f0fcf8f9000]
   java.lang.Thread.State: RUNNABLE
	at org.alanhou.monitor_tuning.chapter2.CpuController.getPartneridsFromJson(CpuController.java:77)
...

訪問http://xx.xx.xx.xx:12345/deadlock(如上jstack <pid>導出追蹤記錄會發現如下這樣的記錄)

Java stack information for the threads listed above:
===================================================
"Thread-5":
	at org.alanhou.monitor_tuning.chapter2.CpuController.lambda$deadlock$1(CpuController.java:41)
	- waiting to lock <0x00000000edcf3470> (a java.lang.Object)
	- locked <0x00000000edcf3480> (a java.lang.Object)
	at org.alanhou.monitor_tuning.chapter2.CpuController$$Lambda$337/547045985.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)
"Thread-4":
	at org.alanhou.monitor_tuning.chapter2.CpuController.lambda$deadlock$0(CpuController.java:33)
	- waiting to lock <0x00000000edcf3480> (a java.lang.Object)
	- locked <0x00000000edcf3470> (a java.lang.Object)
	at org.alanhou.monitor_tuning.chapter2.CpuController$$Lambda$336/1704575158.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

JVisualVM

詳情參考官方文檔

Mac命令行直接輸入jvisualvm命令,Windows 找到對應的 exe 文件雙擊即可打開

插件安裝Tools>Plugins>Settings根據自身版本(java -version)更新插件中心地址,各版本查詢地址:

http://visualvm.github.io/pluginscenters.html

建議安裝:Visual GC, BTrace Workbench

VisualVM

以上是本地的JAVA進程監控,還可以進行遠程的監控,在上圖左側導航的 Applications 下的 Remote 處右擊Add Remote Host...,輸入主機 IP 即可添加,在 IP 上右擊會發現有兩種連接 JAVA 進程進行監控的方式:JMX, jstatd

bin/catalina.sh(以192.168.0.5爲例)

JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9004 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.net.preferIPv4Stack=true -Djava.rmi.server.hostname=192.168.0.5"

啓動tomcat,以 JMX 爲例,在 IP 上右擊點擊Add JMX Connection...,輸入 IP:PORT

Add JMX Connection

以上爲 Tomcat,其它 JAVA 進程也是類似的,如:

nohup java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9005 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.net.preferIPv4Stack=true -Djava.rmi.server.hostname=192.168.0.5 -jar monitor_tuning-0.0.1-SNAPSHOT.jar &

 BTrace

BTrace 可以動態地向目標應用程序的字節碼注入追蹤代碼,使用的技術有 JavaCompilerApi, JVMTI, Agent, Instrumentation+ASM

使用方法:JVisualVM中添加 BTrace 插件

方法二:btrace <pid> <trace_script>

monitor_tuning中新增包org.alanhou.monitor_tuning.chapter4

安裝BTrace 要記得配置環境變量,以 Mac 爲例

# vi ~/.bash_profile
BTRACE_HOME=/Applications/btrace
PATH=$PATH:$BTRACE_HOME/bin
export PATH
# source ~/.bash_profile

pom.xml 中添加 btrace-agent, btrace-boot, btrace-client的依賴

訪問:http://localhost:12345/ch4/arg1?name=Java

# 示例輸出
192:chapter4 alan$ btrace 2247 PrintArgSimple.java 
[Java, ]
org.alanhou.monitor_tuning.chapter4.Ch4Controller,arg1

常見問題:Please set JAVA_HOME before running this script

# vi ~/.bash_profile
export JAVA_HOME=$(/usr/libexec/java_home)
# source ~/.bash_profile

攔截方法

普通方法:@OnMethod( clazz="", method=""),如上例(PrintArgSimple.java)

構造函數:@OnMethod( clazz="", method="<init> ")(PrintContructor.java)

192:chapter4 alan$ btrace 3682 PrintConstructor.java 
org.alanhou.monitor_tuning.chapter2.User,<init>
[1, Java, ]

攔截同名函數:用參數區分(PrintSame.java)

如下例中雖然方法名相同,但分別有一個和兩個參數

@RequestMapping("/same1")
public String same(@RequestParam("name")String name) {
	// 訪問地址: http://localhost:12345/ch4/same1?name=Java
	return "Hello, "+name;
}

@RequestMapping("/same2")
public String same(@RequestParam("name")String name, @RequestParam("id")int id) {
	// 訪問地址: http://localhost:12345/ch4/same2?name=Java&id=1
	return "Hello, "+name+", "+id;
}

攔截時機

Kind.ENTRY: 入口,默認值(上述例子均爲這種情況)

Kind.RETURN: 返回(PrintReturn.java)

192:chapter4 alan$ btrace 3981 PrintReturn.java 
org.alanhou.monitor_tuning.chapter4.Ch4Controller,arg1,Hello, Java

Kind.THROW: 異常(PrintOnThrow.java)

192:chapter4 alan$ btrace 4041 PrintOnThrow.java 
java.lang.ClassNotFoundException: org.apache.catalina.webresources.WarResourceSet
...
java.lang.ArithmeticException: / by zero
	org.alanhou.monitor_tuning.chapter4.Ch4Controller.exception(Ch4Controller.java:40)
...

Kind.Line: 行(PrintLine.java)

# 打印指定行號是否執行
192:chapter4 alan$ btrace 4149 PrintLine.java 
org.alanhou.monitor_tuning.chapter4.Ch4Controller,exception,39

攔截 this、入參、返回值

this:@self

入參:可以用 AnyType,也可以用真實類型,同名的用真實的

返回:@Return

獲取對象的值

簡單類型:直接獲取

複雜類型:反射,類名+屬性名(PrintArgComplex.java)

192:chapter4 alan$ btrace -cp "/Users/alan/Desktop/demo/java-code/monitor_tuning/target/classes" 4337 PrintArgComplex.java 
{id=1, name=Java, }
Java
org.alanhou.monitor_tuning.chapter4.Ch4Controller,arg2

攔截函數中還可以使用正則表達式,如method="/.*/"匹配指定類下的所有方法(PrintRegex.java)

打印環境變量(PrintJinfo.java)

Tomcat 性能監控與調優

Tomcat 遠程 Debug

JDWP

bin/startup.sh 修改最後一行(添加 jpda)

exec "$PRGDIR"/"$EXECUTABLE" jpda start "$@"

bin/catalina.sh 爲便於遠程調試進行如下修改

JPDA_ADDRESS="localhost:8000"
# 修改爲
JPDA_ADDRESS="54321"

若發現54321端口啓動存在問題可嘗試bin/catalina.sh jpda start

本地添加包org.alanhou.monitor_tuning.chapter5,修改打包方式爲 war,並重寫configure,進入monitor_tuning文件夾,執行mvn clean package 進行打包,target 目錄下默認生成的包名爲monitor_tuning-0.0.1-SNAPSHOT.war,爲便於訪問修改爲monitor_tuning.war再上傳到服務器的webapps目錄下

http://192.168.0.5:8080/monitor_tuning/ch5/hello

使用 Eclipse 遠程調試,右擊 Debug As > Debug Configurations... > Remote Java Application > 右擊 New 新建

tomcat-manager 監控

1.conf/tomcat-users.xml添加用戶

  <role rolename="tomcat"/>
  <role rolename="manager-status"/>
  <role rolename="manager-gui"/>
  <user username="tomcat" password="123456" roles="tomcat,manager-gui,manager-status"/>

2.conf/Catalina/localhost/manager.xml配置允許的遠程連接

<?xml version="1.0" encoding="UTF-8"?>
<Context privileged="true" antiResourceLocking="false"
        docBase="$(catalina.home)/webapps/manager">
  <Valve className="org.apache.catalina.valves.RemoteAddrValve"
        allow="127\.0\.0\.1" />
</Context>

遠程連接將allow="127\.0\.0\.1"修改爲allow="^.*$",瀏覽器中輸入http://127.0.0.1:8080/manage或對應的 IP,用戶名密碼爲tomcat-users.xml中所設置的

3.重啓 Tomcat 服務

Tomcat Manager

psi-probe 監控

下載地址:https://github.com/psi-probe/psi-probe,

下載後進入psi-probe-master目錄,執行:

mvn clean package -Dmaven.test.skip

將 web/target/probe.war放到 Tomcat 的 webapps 目錄下,同樣需要conf/tomcat-users.xml和conf/Catalina/localhost/manager.xml中的配置(可保持不變),啓動 Tomcat 服務

瀏覽器中輸入http://127.0.0.1:8080/probe或對應的 IP,用戶名密碼爲tomcat-users.xml中所設置的

PSI Probe演示

Tomcat 調優

線程優化(webapps/docs/config/http.html):

maxConnections

acceptCount

maxThreads

minSpareThreads

配置優化(webapps/docs/config/host.html):

autoDeploy

enableLookups(http.html)

reloadable(context.html)

protocol="org.apache.coyote.http11.Http11AprProtocol"

Session 優化:

如果是 JSP, 可以禁用 Session

補充:APR 配置

yum install -y apr-devel openssl-devel
cd tomcat/bin
tar -zxvf tomcat-native.tar.gz
cd tomcat-native-1.2.17-src/native/
./configure --with-apr=/usr/bin/apr-1-config --with-java-home=/usr/lib/jvm/java-1.8.0 --with-ssl=yes
make && make install

# vi tomcat/bin/setenv.sh
export CATALINA_OPTS=”$CATALINA_OPTS -Djava.library.path=/usr/local/apr/lib”
#vi tomcat/conf/server.xml
<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
# 修改爲
<Connector port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol"
               connectionTimeout="20000"
               redirectPort="8443" />


<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />

Nginx 性能監控與調優

Nginx 安裝

添加 yum 源(/etc/yum.repos.d/nginx.repo)

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/7/$basesearch/
gpgcheck=0
enabled=1

安裝及常用命令

yum install -y nginx
systemctl status|start|stop|reload|restart nginx
nginx -s stop|reload|quit|reopen
cat default.conf | grep -v "#'
nginx -V
nginx -t

配置反向代理 setenforce 0

ngx_http_stub_status 監控連接信息

 

 

location = /nginx_status {
    stub_status on;
    access_log off;
    allow 127.0.0.1;
    deny all;
}

可通過curl http://127.0.0.1/nginx_status 進行查看或註釋掉 allow 和 deny 兩行使用 IP 進行訪問

ngxtop監控請求信息

 

查看官方使用方法:https://github.com/lebinh/ngxtop

 

# 安裝 python-pip
yum install epel-release
yum install python-pip
# 安裝 ngxtop
pip install ngxtop

使用示例

指定配置文件:ngxtop -c /etc/nginx/nginx.conf

查詢狀態是200:ngxtop -c /etc/nginx/nginx.conf -i 'status == 200'

查詢訪問最多 ip:ngxtop -c /etc/nginx/nginx.conf -g remote_addr

ngxtop查詢訪問最多 ip

 

nginx-rrd 圖形化監控

 

nginx-rrd 依賴於前面的ngx_http_stub_status

# 安裝 php
yum install php php-gd php-soap php-mbstring php-xmlrpc php-dom php-fpm -y
# Ngnix融合 php-fpm(/etc/php-fpm.d/www.conf)
user = nginx
group = nginx
# 啓動 php-fpm
systemctl start php-fpm
# 修改 Nginx 配置文件

location ~ \.php$ {
    root           /usr/share/nginx/html;
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root/$fastcgi_script_name;
    include        fastcgi_params;
}

# 添加 index.php(/usr/share/nginx/html)
<?php phpinfo(); ?>

訪問 http://your.ip.address/index.php 檢測配置是否成功

# 安裝 rddtool 相關依賴
yum install perl rrdtool perl-libwww-perl libwww-perl perl-rrdtool -y
# 安裝 nginx-rdd
wget http://soft.vpser.net/status/nginx-rrd/nginx-rrd-0.1.4.tgz
tar zxvf nginx-rrd-0.1.4.tgz  
cd nginx-rrd-0.1.4  
cp usr/sbin/* /usr/sbin     # 複製主程序文件到 /usr/sbin 下  
cp etc/nginx-rrd.conf /etc  # 複製配置文件到 /etc 下  
cp html/index.php /usr/share/nginx/html/
# 修改配置(/etc/nginx-rrd.conf)
RRD_DIR="/usr/share/nginx/html/nginx-rrd";  
WWW_DIR="/usr/share/nginx/html";  
# 添加定時任務(crontab -e)
*  * * * * /bin/sh /usr/sbin/nginx-collect
*/1 * * * * /bin/sh /usr/sbin/nginx-graph
# 查看定時任務執行情況
tail -f /var/log/cron

# ab 壓測(未安裝 yum -y install httpd-tools)
ab -n 10000 -c 10 http://127.0.0.1/index.html

訪問 http://your.ip.address/index.php 即可得到如下這種圖形化界面:

nginx-rrd 圖形化監控

Nginx 優化

增加工作線程數和併發連接數

worker_processes  4; # 一般CPU 是幾核就設置爲幾
events {
    worker_connections  1024; # 每個進程打開的最大連接數,包含了 Nginx 與客戶端和 Nginx 與 upstream 之間的連接
    multi_accept on; # 可以一次建立多個連接
    use epoll;
}

啓用長連接

upstream server_pool{
    server localhost:8080 weight=1 max_fails=2 fail_timeout=30s;
    server localhost:8081 weight=1 max_fails=2 fail_timeout=30s;
    keepalive 300; # 300個長連接
}
location / {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_pass http://server_pool;
}

啓用緩存壓縮

gzip on;
gzip_http_version 1.1;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
gzip_proxied any;
gzip_types text/plain text/css application/javascript application/x-javascript application/json application/xml application/vnd.ms-fontobject application/x-font-ttf application/svg+xml application/x-icon;
gzip_vary on;
gzip_static on;

操作系統優化

# 配置文件/etc/sysctl.conf
sysctl -w net.ipv4.tcp_syncookies=1 # 防止一個套接字在有過多試圖連接到時引起過載
sysctl -w net.core.somaxconn=1024 # 默認128,連接隊列
sysctl -w net.ipv4.tcp_fin_timeout=10 # timewait 的超時時間
sysctl -w net.ipv4.tcp_tw_reuse=1 # os 直接使用 timewait的連接
sysctl -w net.ipv4.tcp_tw_recycle=0 # 回收禁用

# /etc/security/limits.conf
*               hard    nofile            204800
*               soft    nofile             204800
*               soft    core             unlimited
*               soft    stack             204800

其它優化

sendfile	on; # 減少文件在應用和內核之間拷貝
tcp_nopush	on; # 當數據包達到一定大小再發送
tcp_nodelay	off; # 有數據隨時發送

JVM層GC調優

https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/toc.html

JVM的內存結構

JVM的內存結構

運行時數據區:

程序計數器 PC Register

虛擬機棧 JVM Stacks

堆 Heap

方法區 Method Area

     常量池 Run-Time Constant Pool

本地方法棧 Native Method Stacks

 

常用參數:

-Xms -Xmx

-XX:NewSize -XX:MaxNewSize

-XX:NewRatio -XX:SurvivorRatio

-XX:MetaspaceSize -XX:MaxMetaspaceSize

-XX:+UseCompressedClassPointers

-XX:CompressedClassSpaceSize

-XX:InitialCodeCacheSize

-XX:ReservedCodeCacheSize

 

垃圾回收算法

 

枚舉根節點,做可達性分析

根節點:類加載器、Thread、虛擬機棧的本地變量表、static 成員、常量引用、本地方法棧的變量

 

算法:標記清除、複製、標記整理、分帶垃圾回收

對象分配:對象優先在 Eden 區分配、大對象直接進入Old 區(-XX:PretenureSizeThreshold)、長期存活對象進入 Old 區(-XX:MaxTenuringThreshold, -XX:+PrintTenuringDistribution, -XX:TargetSurvivorRatio

 

垃圾收集器

 

# 常見配置示例(bin/catalina.sh)
PARALLEL_OPTION="-XX:+UseParallelGC -XX:+UseParallelOldGC -XX:MaxGCPauseMillis=200 -XX:GCTimeRatio=99"
CMS_OPTION="-XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5"
G1_OPTION="-XX:+UseG1GC -XX:+UseStringDeduplication -XX:StringDeduplicationAgeThreshold=3 -XX:+UseCompressedClassPointers -XX:MaxGCPauseMillis=200"

JAVA_OPTS="$JAVA_OPTS $CMS_OPTION -Xms128M -Xmx128M -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M -XX:+UseCompressedClassPointers"

串行收集器 Serial::Serial, Serial Old (-XX:+UseSerialGC -XX:+UseSerialOldGC)

並行收集器 Parallel(吞吐量優先, Server 模式默認收集器):Parallel Scavenge, Parallel Old (-XX:+UseParallelGC, -XX:+UseParallelOldGC)

 

-XX:ParallelGCThreads=<N> 多少個 GC 線程(CPU> 8 N=5/8; CPU<8 N=CPU)

Parallel Collector Ergonomics:

-XX:MaxGCPauseMillis=<N>

-XX:GCTimeRatio=<N>

-Xmx<N>

動態內存調整

-XX:YoungGenerationSizeIncrement=<Y>

-XX:TenuredGenerationSizeIncrement=<T>

-XX:AdaptiveSizeDecrementScaleFactor=<D>

 

併發收集器 Concurent(停頓時間優先):CMS (-XX:+UseConcMarkSweepGC -XX:+UseParNewGC), G1(-XX:UseG1GC)

 

CMS

1. CMS initial mark: 初始標記 Root, STW

2. CMS concurrent mark:併發標記

3. CMS-concurrent-preclean:併發預清理

4. CMS remark:重新標記,STW

5. CMS concurrent sweep:併發清除

6. CMS-concurrent-reset:併發重置

缺點:CPU 敏感、產生垃圾和空間碎片

相關參數:

-XX:ConcGCThreads:併發的 GC 線程數

-XX:+UseCMSCompactAtFullCollection:FullGC 之後做壓縮

-XX:CMSFullGCsBeforeCompaction:多少次 FullGC之後壓縮一次

-XX:CMSInitiatingOccupancyFraction:觸發 FullGC

-XX:+UseCMSInitiatingOccupancyOnly:是否動態可調

-XX:+CMSScavengeBeforeRemark:FullGC之前先做 YGC

-XX:+CMSClassUnloadingEnable:啓用回收Perm 區

 

iCMS

適用於單核或者雙核

 

G1 Collector(JDK 8開始,推薦使用)

 

G1的幾個概念

Region

SATB:Snapshot-At-The-Beginning,它是通過 Root Tracing 得到的,GC 開始時候存活對象的快照。

RSet:記錄了其他 Region中的對象引用本 Region 中對象的關係,屬於 points-into 結構

YoungGC

新對象進入 Eden 區

存活對象拷貝到Survivor 區

存活時間達到年齡閾值時,對象晉升到 Old 區

MixedGC

不是 FullGC,回收所有的 Young和所有的 Old

global concurrent marking

1. Initial marking phase: 標記 GC Root, STW

2. Root region scanning phase:標記存活 Region

3. Concurrent marking phase:標記存活的對象

4. Remark phase:重新標記,STW

5. Cleanup phase:部分 STW

 

MixedGC時機

InitiatingHeapOccupancyPercent

G1HeapWastePercent

G1MixedGCLiveThresholdPercent

G1MixedGCCountTarget

G1OldGCSetRegionThresholdPercent

-XX:+UseG1GC 開啓 G1

-XX:G1HeapRegionSize=n, Region 的大小,1-32M,最多2048個

-XX:MaxGCPauseMillis=200 最大停頓時間

-XX:G1NewSizePercent、-XX:G1MaxNewSizePercent

-XX:G1ReservePercent=10 保留防止 to space溢出

-XX:ParallelGCThreads=n SWT線程數

-XX:ConcGCThreads=n 併發線程數=1/4*並行

 

最佳實踐

年輕代大小:避免使用-Xmn, -XX:NewRatio 等顯式 Young 區大小,會覆蓋暫停時間目標

暫停時間目標:暫停時間不要太嚴苛,其吞吐量目標是90%的應用程序時間和10%的垃圾回收時間,太嚴苛會直接影響到吞吐量

 

需要切換到 G1的情況:

1. 50%以上的堆被存活對象佔用

2. 對象分配和晉升的速度變化非常大

3. 垃圾回收時間特別長,超過了1秒

 

查看方法:jinfo -flag xxx <pid>

 

並行:多條垃圾收集線程並行工作,但用戶線程處於等待狀態

併發:用戶線程與垃圾收集線程同時執行(或交替執行)

 

停頓時間:垃圾收集器做垃圾回收中斷應用執行的時間 -XX:MaxGCPauseMillis

吞吐量:花在垃圾收集的時間和花在應用時間的佔比 -XX:GCTimeRatio=<n>,垃圾收集時間佔1/(1+n)

 

垃圾收集器搭配:

垃圾收集器搭配

 

如何選擇垃圾收集器?

1. 優先調整堆的大小讓服務器自己來選擇

2. 如果內存小於100M,使用串行收集器

3。 如果是單核,並且沒有停頓時間的要求,選擇串行或者 JVM 自己選

4. 如果允許停頓時間超過1秒,選擇並行或 JVM 自己選

5. 如果響應時間最重要,並且不能超過1秒,使用併發收集器

 

可視化 GC 日誌分析工具

打印日誌相關參數:

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:$CATALINA_HOME/logs/gc.log -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution

例(默認爲 ParallelGC, 其它的添加-XX:+UseConcMarkSweepGC或-XX:+UseG1GC即可):

JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:$CATALINA_HOME/logs/gc.log"

CMS日誌格式
G1日誌格式

在線工具:http://gceasy.io/

訪問 GCeasy 官網導入日誌即可獲取可視化分析及優先建議

GCeasy 日誌

 

GCViewer

 

mvn clean package -Dmaven.test.skip 生成 jar包,雙擊執行,導入日誌即可進入圖形化分析頁面

 

GCViewer

 

Tomcat 的 GC 調優實戰

ParallelGC調優

設置 Metaspace 大小 -XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=64M

添加吞吐量和停頓時間參數 -XX:GCTimeRatio=99 -XX:MaxGCPauseMillis=100

修改動態擴容增量 -XX:YoungGenerationSizeIncrement=30

G1 GC 最佳實踐

-XX:InitiatingHeapOccupancyPercent: Use to change the marking threshold.

-XX:G1MixedGCLiveThresholdPercent and -XX:G1HeapWastePercent: Use to change the mixed garbage collection decisions.

-XX:G1MixedGCCountTarget and -XX:G1OldCSetRegionThresholdPercent: Use to adjust the CSet for old regions.

JAVA代碼層調優

最終代碼:monitor_tuning

 JVM字節碼指令與 javap

javap <options> <classes>

cd monitor_tuning/target/classes/org/alanhou/monitor_tuning/chapter8/

javap -verbose Test1.class > Test1.txt 即可保存字節碼文件

常量池

字段描述符

方法描述符

字節碼指令

i++與++i,字符串拼接+原理

javap -verbose SelfAdd.class > SelfAdd.txt

通過對 f1()和 f2()的字節碼,我們得出結論 i++和++i 的執行效果完全相同

  public static void f1();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=0
         0: iconst_0
         1: istore_0
         2: goto          15
         5: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
         8: iload_0
         9: invokevirtual #31                 // Method java/io/PrintStream.println:(I)V
        12: iinc          0, 1
        15: iload_0
        16: bipush        10
        18: if_icmplt     5
        21: return

其他代碼優先方法

字符串拼接+

javap -verbose StringAdd.class >StringAdd.txt

通過字節碼可以看出+拼接符效率要低於 append

Try-Finally

javap -verbose TryFinally.class >TryFinally.txt

Constant variable(final)

javap -verbose StringConstant.class >StringConstant.txt

常用代碼優化方法

1. 儘量重用對象,不要循環創建對象,比如:for 循環字符串拼接(不在 for中使用+拼接,先new 一個StringBuilder再在 for 裏 append)

2. 容器類初始化的地時候指定長度

List<String> collection = new ArrayLIst<String>(5);

Map<String, String> map = new HashMap<String, String>(32);

3. ArrayList(底層數組)隨機遍歷快,LinkedList(底層雙向鏈表)添加刪除快

4. 集合遍歷儘量減少重複計算

5. 使用 Entry 遍歷 Map

6. 大數組複製使用System.arraycopy

7. 儘量使用基本類型而不是包裝類型

8. 不要手動調用 System.gc()

9. 及時消除過期對象的引用,防止內存泄漏

10. 儘量使用局部變量,減小變量的作用域

11. 儘量使用非同步的容器ArraryList vs. Vector

12. 儘量減小同步作用範圍, synchronized 方法 vs. 代碼塊

13. 用ThreadLocal 緩存線程不安全的對象,SimpleDateFormat

14. 儘量使用延遲加載

15. 儘量減少使用反射,必須用加緩存

16. 儘量使用連接池、線程池、對象池、緩存

17. 及時釋放資源, I/O 流、Socket、數據庫連接

18. 慎用異常,不要用拋異常來表示正常的業務邏輯

19. String 操作儘量少用正則表達式

20. 日誌輸出注意使用不同的級別

21. 日誌中參數拼接使用佔位符

log.info("orderId:" + orderId); 不推薦

log.info("orderId:{}", orderId); 推薦

 

課程鏈接:https://coding.imooc.com/class/241.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章