1:JVM字節碼指令與 javap
javap <options> <classes>
cd monitor_tuning/target/classes/org/alanhou/monitor_tuning/chapter8/
javap -verbose Test1.class > Test1.txt 即可保存字節碼文件
會有三個部分組成
操作數棧
LineNumberTable
LocalVariableTable
i++和++i 的執行效果完全相同 多了一個壓入棧頂操作
for(int i=0;i<10;i++) {}
for(int i=0;i<10;++i) {} 執行效果一樣
2:
public static void f1() {
String src = "";
for(int i=0;i<10;i++) {
//每一次循環都會new一個StringBuilder 然后在src.append("A");
src = src + "A";
}
System.out.println(src);
}
public static void f2() {
//只要一個StringBuilder
StringBuilder src = new StringBuilder();
for(int i=0;i<10;i++) {
src.append("A");
}
System.out.println(src);
}
3:
public static String f1() {
String str = "hello";
try{
return str;
}
finally{
str = "imooc";
}
} 返回 hello 但會執行finally 中的代碼
4:字符串拼接都會在編譯階段轉換成stringbuilder
5:字符串去重
字符串在任何應用中都占用了大量的內存。尤其數包含獨立UTF-16字符的char[]數組對JVM內存的消耗貢獻最多——因為每個字符占用2位。
內存的30%被字符串消耗其實是很常見的,不僅是因為字符串是與我們互動的最好的格式,而且是由於流行的HTTP API使用了大量的字符串。使用Java 8 Update 20,我們現在可以接觸到一個新特性,叫做字符串去重,該特性需要G1垃圾回收器,該垃圾回收器默認是被關閉的。
字符串去重利用了字符串內部實際是char數組,並且是final的特性,所以JVM可以任意的操縱他們。
對於字符串去重,開發者考慮了大量的策略,但最終的實現采用了下面的方式:
無論何時垃圾回收器訪問了String對象,它會對char數組進行一個標記。它獲取char數組的hash value並把它和一個對數組的弱引用存在一起。只要垃圾回收器發現另一個字符串,而這個字符串和char數組具有相同的hash code,那么就會對兩者進行一個字符一個字符的比對。
如果他們恰好匹配,那么一個字符串就會被修改,指向第二個字符串的char數組。第一個char數組就不再被引用,也就可以被回收了。
這整個過程當然帶來了一些開銷,但是被很緊實的上限控制了。例如,如果一個字符未發現有重復,那么一段時間之內,它會不再被檢查。
那么該特性實際上是怎么工作的呢?首先,你需要剛剛發布的Java 8 Update 20,然后按照這個配置: -Xmx256m -XX:+UseG1GC 去運行下列的代碼:
public class LotsOfStrings { private static final LinkedList<String> LOTS_OF_STRINGS = new LinkedList<>(); public static void main(String[] args) throws Exception { int iteration = 0; while (true) { for (int i = 0; i < 100; i++) { for (int j = 0; j < 1000; j++) { LOTS_OF_STRINGS.add(new String("String " + j)); } } iteration++; System.out.println("Survived Iteration: " + iteration); Thread.sleep(100); } } }
這段代碼會執行30個迭代之后報OutOfMemoryError。
現在,開啟字符串去重,使用如下配置去跑上述代碼:
-Xmx256m -XX:+UseG1GC -XX:+UseStringDeduplication -XX:+PrintStringDeduplicationStatistics
此時它已經可以運行更長的時間,而且在50個迭代之后才終止。
6:
ArrayLIst 底層是數組 擴容會拷貝
hashmap 底層也是數組+ 鏈表 擴容 重新計算key 負載因子是 0.75
linklist底層是雙向鏈表
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可以同時取出key和value
6. 大數組復制使用System.arraycopy 底層是native實現的
7. 盡量使用基本類型而不是包裝類型
public class Test03 {
public static void main(String[] args) {
Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
System.out.println(f1 == f2);
System.out.println(f3 == f4);
}
}
如果不明就里很容易認為兩個輸出要么都是true要么都是false。首先需要注意的是f1、f2、f3、f4四個變量都是Integer對象引用,所以下面的==運算比較的不是值而是引用。裝箱的本質是什么呢?當我們給一個Integer對象賦一個int值的時候,會調用Integer類的靜態方法valueOf,如果看看valueOf的源代碼就知道發生了什么。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
簡單的說,如果整型字面量的值在-128到127之間,那么不會new新的Integer對象,而是直接引用常量池中的Integer對象,所以上面的面試題中f1==f2的結果是true,而f3==f4的結果是false。
8. 不要手動調用 System.gc()
9. 及時消除過期對象的引用,防止內存泄漏
public string pop()
{
string currentValue=object[size];
//object[size]=null;如果不添加這句話就會造成內存泄漏
size--;
return currentValue;
}
10. 盡量使用局部變量,減小變量的作用域 方便出了作用域盡快垃圾回收
11. 盡量使用非同步的容器ArraryList vs. Vector
12. 盡量減小同步作用范圍, synchronized 方法 vs. 代碼塊
public class SynchronizedTest {
public static void main(String[] args) {
}
public synchronized void f1() {//在this對象上加鎖
System.out.println("f1");
}
public void f2() {//在this對象上加鎖
synchronized(this) {
System.out.println("f2");
}
}
public static synchronized void f3() {//在類上加鎖
System.out.println("f3");
}
public static void f4() {//在類上加鎖
synchronized(SynchronizedTest.class) {
System.out.println("f4");
}
}
}
13. 用ThreadLocal 緩存線程不安全的對象,SimpleDateFormat 緩存重量的對象避免重新構造
@SuppressWarnings("rawtypes")
private static ThreadLocal threadLocal = new ThreadLocal() {
protected synchronized Object initialValue() {
return new SimpleDateFormat(DATE_FORMAT);
}
};
14. 盡量使用延遲加載
15. 盡量減少使用反射,必須用加緩存,反射比較影響性能
16. 盡量使用連接池、線程池、對象池、緩存
17. 及時釋放資源, I/O 流、Socket、數據庫連接
18. 慎用異常,不要用拋異常來表示正常的業務邏輯,異常也是比較重的對象要記錄堆棧信息
19. String 操作盡量少用正則表達式 比如replaceAll是用正則 比較耗費性能 replace就不是用正則
20. 日志輸出注意使用不同的級別
21. 日志中參數拼接使用占位符
log.info("orderId:" + orderId); 不推薦 會用字符串拼接
log.info("orderId:{}", orderId); 推薦 用占位符 不會進行字符串拼接
7: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
查看
-XX:+PrintFlagsInitial 查看jvm初始值
-XX:+PrintFlagsFinal 查看jvm最終值
-XX:+UnlockExperimentalVMOptions 解鎖實驗參數
-XX:+UnlockDiagnosticVMOptions 解鎖診斷參數
-XX:+PrintCommandLineFlags 打印命令行參數
輸出結果中=表示默認值,:=表示被用戶或 JVM 修改后的值
示例:java -XX:+PrintFlagsFinal -version
補充:測試中需要用到 Tomcat,CentOS 7安裝示例如下
1
2
3
4
5
6
|
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
查看java進程 -l 可以知道完全類名
jinfo
jinfo -flag MaxHeapSize <pid>
jinfo -flags <pid> 手動賦過值的參數
jstat
可以查看jvm的統計信息 如類加載。垃圾回收信息,jit編譯信息
詳情參考 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
一個對象默認分配在堆上面 但是有個指針指向class默認是64位長指針,可以設置為用32位存儲在壓縮類空間
非堆區 即對應於虛擬機規范中的方法區 是操作系統本地內存 獨立於jvm堆區之外 jdk8后面叫metaspace jdk8前面叫performancespace
codecache 存儲的是jit即時編譯的代碼 以及native代碼
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>
MAT下載地址:http://www.eclipse.org/mat/
找開上述導出的內存溢出文件即可進行分析,如下圖的溢出源頭分析:
- Histogram可以列出內存中的對象,對象的個數以及大小。
- Dominator Tree可以列出那個線程,以及線程下面的那些對象占用的空間。
Histogram
-
Class Name : 類名稱,java類名
-
Objects : 類的對象的數量,這個對象被創建了多少個
-
Shallow Heap :一個對象內存的消耗大小,不包含對其他對象的引用
-
Retained Heap :是shallow Heap的總和,也就是該對象被GC之后所能回收到內存的總和
Dominator Tree
我們可以看到ibatis占了較多內存
快速找出某個實例沒被釋放的原因,可以右健 Path to GC Roots-->exclue all phantom/weak/soft etc. reference :

得到的結果是:

從表中可以看出 PreferenceManager -> … ->HomePage這條線路就引用着這個 HomePage實例。用這個方法可以快速找到某個對象的 GC Root,一個存在 GC Root的對象是不會被 GC回收掉的.
jstack
詳情參考 jstack 官方文檔
jstack <pid> 打印jvm內部所有的線程
jstack 15672 >15673.txt 導出當前進程文件
可查看其中包含java.lang.Thread.State: WAITING (parking),JAVA 線程包含的狀態有:
NEW:線程尚未啟動
RUNNABLE:線程正在 JVM 中執行
BLOCKED:線程在等待監控鎖(monitor lock)
WAITING:線程在等待另一個線程進行特定操作(時間不確定)
TIMED_WAITING:線程等待另一個線程進行限時操作
TERMINATED:線程已退出
此時會生成一個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 是查詢所有進程的cpu 占用率
top還可以用來顯示一個進程中各個線程CPU的占用率:top -p <pid> -H
top命令如下
top -p <pid> -H 命令如下 看的是7930的進程

使用 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.
查看后台日志,都是使用tail -f catalina.out命令來查看
jvisualvm 圖形化工具
插件安裝Tools>Plugins>Settings根據自身版本(java -version)更新插件中心地址,各版本查詢地址:
http://visualvm.github.io/pluginscenters.html
建議安裝:Visual GC, BTrace Workbench
概述 監控可以堆dump 線程可以線程dump 抽樣器可以對cpu和內存進行抽樣調查
以上是本地的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,
啟動tomcat服務
方式一:直接啟動 ./startup.sh
方式二:作為服務啟動 nohup ./startup.sh &
查看tomcat運行日志
tail -f catalina.out
tomcat設置jvm參數
修改文件 apache-tomcat-9.0.10/bin下catalina.bat文件
以 JMX 為例,在 IP 上右擊點擊Add JMX Connection...,輸入 IP:PORT
以上為 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>
btrace只能調試本地進程
btrace修改后的字節碼不能被還原
pom.xml 中添加 btrace-agent, btrace-boot, btrace-client的依賴


攔截構造方法

攔截同名方法

攔截返回值

攔截行號

攔截異常信息

攔截復雜類型

攔截正則表達式

攔截環境參數信息

常用參數:
-Xms -Xmx
-XX:NewSize -XX:MaxNewSize
-XX:NewRatio -XX:SurvivorRatio
-XX:MetaspaceSize -XX:MaxMetaspaceSize 以下幾個參數通常這樣只設置這個值即可
-XX:+UseCompressedClassPointers
-XX:CompressedClassSpaceSize
-XX:InitialCodeCacheSize
-XX:ReservedCodeCacheSize
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
使用 Eclipse 遠程調試,右擊 Debug As > Debug Configurations... > Remote Java Application > 右擊 New 新建
普通java進程可以這樣配置
java -jar -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=10001 access-10000.jar
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 服務
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中所設置的
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
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 nginx 啟動nginx
cat default.conf | grep -v "#' > default2.conf 移除配置文件中的注釋 並生成新的配置文件
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
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; # 有數據隨時發送