JVM調優2-遠端監控
監控遠端JVM
VisualJVM不僅是可以監控本地jvm程序,還可以監控遠端的jvm程序,需要藉助於JMX技術實現。
什麼是JMX
JMX(Java Management Extensions,即Java管理擴充套件)是一個為應用程式、裝置、系統等植入管理功能的框架。JMX可以跨越一系列異構作業系統平臺、系統體系結構和網路傳輸協議,靈活地開發無縫整合的系統、網路和服務管理應用。
監控Tomcat
想要監控遠端的tomcat,就需要在遠端的tomcat進行對JMX配置,方法如下:
#在tomcat的bin目錄下,修改catalina.sh,新增如下的引數 JAVA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false" #這幾個引數的意思是: #-Dcom.sun.management.jmxremote :允許使用JMX遠端管理 #-Dcom.sun.management.jmxremote.port=9999 :JMX遠端連線埠 #-Dcom.sun.management.jmxremote.authenticate=false :不進行身份認證,任何使用者都可以連線 #-Dcom.sun.management.jmxremote.ssl=false :不使用ssl
使用VisualJVM遠端連線Tomcat
新增主機
在一個主機下可能會有很多的jvm需要監控,所以接下來要在該主機上新增需要監控的jvm:連線成功。使用方法和前面就一樣了,就可以和監控本地jvm程序一樣,監控遠端的tomcat程序。
3.6 視覺化GC日誌分析工具
GC日誌輸出引數
前面通過-XX:+PrintGCDetails可以對GC日誌進行列印,我們就可以在控制檯檢視,這樣雖然可以檢視GC的資訊,但是並不直觀,可以藉助於第三方的GC日誌分析工具進行檢視。
在日誌列印輸出涉及到的引數如下:
-XX:+PrintGC 輸出GC日誌 -XX:+PrintGCDetails 輸出GC的詳細日誌 -XX:+PrintGCTimeStamps 輸出GC的時間戳(以基準時間的形式) -XX:+PrintGCDateStamps 輸出GC的時間戳(以日期的形式,如 2013-05-04T21:53:59.234+0800) -XX:+PrintHeapAtGC 在進行GC的前後打印出堆的資訊 -Xloggc:../logs/gc.log 日誌檔案的輸出路徑 1.2.3.4.5.6.7.8.9.10.11.
測試:
-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -Xmx256m -XX:+PrintGCDetails
-XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC
-Xloggc:F://test//gc.log
執行後就可以在E盤下生成gc.log檔案。
使用GC Easy
它是一款線上的視覺化工具,易用、功能強大,網站:http://gceasy.io/
3.7 調優實戰
先部署一個web專案(自行準備)
壓測
下面我們通過jmeter進行壓力測試,先測得在初始狀態下的併發量等資訊,然後我們在對jvm做調優處理,再與初始狀態測得的資料進行比較,看調好了還是調壞了。
首先需要對jmeter本身的引數調整,jmeter預設的的記憶體大小隻有1g,如果併發數到達300以上時,將無法
正常執行,會丟擲記憶體溢位等異常,所以需要對記憶體大小做出調整。
修改jmeter.bat檔案:
set HEAP=-Xms1g -Xmx4g -XX:MaxMetaspaceSize=512m
在該檔案中可以看到,jmeter預設使用的垃圾收集器是G1.
Defaults to '-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:G1ReservePercent=20'
新增gc相關引數
#記憶體設定較小是為了更頻繁的gc,方便觀察效果,實際要比此設定的更大 JAVA_OPTS="-XX:+UseParallelGC -XX:+UseParallelOldGC -Xms64m -Xmx128m - XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC - Xloggc:../logs/gc.log -Dcom.sun.management.jmxremote - Dcom.sun.management.jmxremote.port=9999 - Dcom.sun.management.jmxremote.authenticate=false - Dcom.sun.management.jmxremote.ssl=false"
1。
重啟tomcat
建立測試用例進行壓測
調優方向主要從以下幾個方面:
- 調整記憶體
- 更換垃圾收集器
對於JVM的調優,給出大家幾條建議:
- 生產環境的JVM一定要進行引數設定,不能全部預設上生產。
- 對於引數的設定,不能拍腦袋,需要通過實際併發情況或壓力測試得出結論。
- 對於記憶體中物件臨時存在居多的情況,將年輕代調大一些。如果是G1或ZGC,不需要設定。
- 仔細分析gceasy給出的報告,從中分析原因,找出問題。
- 對於低延遲的應用建議使用G1或ZGC垃圾收集器。
- 不要將焦點全部聚焦jvm引數上,影響效能的因素有很多,比如:作業系統、tomcat本身的引數等。
PerfMa
PerfMa提供了JVM引數分析、執行緒分析、堆記憶體分析功能,介面美觀,功能強大,我們在做jvm調優時,可以作為一個輔助工具。官網:https://www.perfma.com/
04 Tomcat8優化
4.1 禁用AJP連線
在服務狀態頁面中可以看到,預設狀態下會啟用AJP服務,並且佔用8009埠。
什麼是AJP呢?
AJP(Apache JServer Protocol) AJPv13協議是面向包的。WEB伺服器和Servlet容器通過TCP連線來互動;為了節省SOCKET建立的昂貴代價,WEB伺服器會嘗試維護一個永久TCP連線到servlet容器,並且在多個請求和響應週期過程會重用連線。
我們一般是使用Nginx+tomcat的架構,所以用不著AJP協議,所以把AJP聯結器禁用。
修改conf下的server.xml檔案,將AJP服務禁用掉即可。
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
1.
4.2 執行器(執行緒池)
在tomcat中每一個使用者請求都是一個執行緒,所以可以使用執行緒池提高效能。
修改server.xml檔案:
<!--將註釋開啟--> <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="500" minSpareThreads="50" prestartminSpareThreads="true" maxQueueSize="100"/>
<!-- 引數說明: maxThreads:最大併發數,預設設定 200,一般建議在 500 ~ 1000,根據硬體設施和業務來判斷 minSpareThreads:Tomcat 初始化時建立的執行緒數,預設設定 25 prestartminSpareThreads: 在 Tomcat 初始化的時候就初始化 minSpareThreads 的引數值,如果不等於 true,minSpareThreads 的值就沒啥效果了 maxQueueSize,最大的等待佇列數,超過則拒絕請求 -->
<!--在Connector中設定executor屬性指向上面的執行器-->
<Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
儲存退出,重啟tomcat,檢視效果。
4.3 三種執行模式
tomcat的執行模式有3種:
\1. bio 預設的模式,效能非常低下,沒有經過任何優化處理和支援.
\2. nio nio(new I/O),是Java SE 1.4及後續版本提供的一種新的I/O操作方式(即java.nio包及其子包)。Java nio是一個基於緩衝區、並能提供非阻塞I/O操作的Java API,因此nio也被看成是non-blocking I/O的縮寫。它擁有比傳統I/O操作(bio)更好的併發執行效能。
\3. apr 安裝起來最困難,但是從作業系統級別來解決非同步的IO問題,大幅度的提高效能.
推薦使用nio,不過,在tomcat8中有最新的nio2,速度更快,建議使用nio2.
設定nio2
<Connector executor="tomcatThreadPool" port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol" connectionTimeout="20000" redirectPort="8443" />
1.
05 程式碼優化建議
(1)儘可能使用區域性變數
呼叫方法時傳遞的引數以及在呼叫中建立的臨時變數都儲存在棧中速度較快,其他變數,如靜態變數、例項變數等,都在堆中建立,速度較慢。另外,棧中建立的變數,隨著方法的執行結束,這些內容就沒了,不需要額外的垃圾回收。
(2)儘量減少對變數的重複計算
明確一個概念,對方法的呼叫,即使方法中只有一句語句,也是有消耗的。所以例如下面的操作:
for (int i = 0; i < list.size(); i++) {...}
建議替換為:
int length = list.size(); for (int i = 0, i < length; i++) {...}
這樣,在list.size()很大的時候,就減少了很多的消耗。
(3)儘量採用懶載入的策略,即在需要的時候才建立
String str = "aaa";
if (i == 1){
list.add(str);
}//建議替換成
if (i == 1){
String str = "aaa";
list.add(str);
}
(4)異常不應該用來控制流程
異常對效能不利。丟擲異常首先要建立一個新的物件,Throwable介面的建構函式呼叫名為fifillInStackTrace()的本地同步方 法,fifillInStackTrace()方法檢查堆疊,收集呼叫跟蹤資訊。只要有異常被丟擲,Java虛擬機器就必須調整呼叫堆疊,因為在處理過程中建立 了一個新的物件。異常只能用於錯誤處理,不應該用來控制程式流程。
(5)不要將陣列宣告為public static final
因為這毫無意義,這樣只是定義了引用為static final,陣列的內容還是可以隨意改變的,將陣列宣告為public更是一個安全漏洞,這意味著這個陣列可以被外部類所改變。
(6)不要建立一些不使用的物件,不要匯入一些不使用的類
這毫無意義,如果程式碼中出現"The value of the local variable i is not used"、"The import java.util is never used",那麼請刪除這些無用的內容
(7)程式執行過程中避免使用反射
反射是Java提供給使用者一個很強大的功能,功能強大往往意味著效率不高。不建議在程式執行過程中使用尤其是頻繁使用反射機制,特別是 Method的invoke方法。
如果確實有必要,一種建議性的做法是將那些需要通過反射載入的類在專案啟動的時候通過反射例項化出一個物件並放入記憶體。
(8)使用資料庫連線池和執行緒池
這兩個池都是用於重用物件的,前者可以避免頻繁地開啟和關閉連線,後者可以避免頻繁地建立和銷燬執行緒。
(9)容器初始化時儘可能指定長度
容器初始化時儘可能指定長度,如:new ArrayList<>(10); new HashMap<>(32); 避免容器長度不足時,擴容帶來的效能損耗。
(10)ArrayList隨機遍歷快,LinkedList新增刪除快
(11)使用Entry遍歷map
(12)不要手動呼叫System().gc;
(13)String儘量少用正則表示式
正則表示式雖然功能強大,但是其效率較低,除非是有需要,否則儘可能少用。
replace() 不支援正則 replaceAll() 支援正則
如果僅僅是字元的替換建議使用replace()。
(14)日誌的輸出要注意級別
(15)對資源的close()建議分開操作
向掙他一個億的小目標奮鬥