您好,登錄后才能下訂單哦!
tomcat服務(wù)器在JavaEE項(xiàng)目中使用率非常高,所以在生產(chǎn)環(huán)境對(duì)tomcat的優(yōu)化也變得非常重要了。
對(duì)于tomcat的優(yōu)化,主要是從兩個(gè)方面入手,第一是,tomcat自身的配置,另一個(gè)是tomcat所運(yùn)行的jvm虛擬機(jī)的。
下載并安裝 :https://tomcat.apache.org/download-80.cgi?
在服務(wù)狀態(tài)頁面中可以看到,默認(rèn)狀態(tài)下會(huì)啟用AJP服務(wù),并且占用8009端口。
什么是AJP呢?
AJP(Apache JServer Protocol)
AJPv13協(xié)議是面向包的。WEB服務(wù)器和Servlet容器通過TCP連接來交互;為了節(jié)省Socket創(chuàng)建的昂貴代價(jià),WEB服務(wù)器會(huì)
嘗試維護(hù)一個(gè)永久TCP連接到servlet容器,并且在多個(gè)請(qǐng)求和響應(yīng)周期過程會(huì)重用連接。
我們一般是使用Nginx+tomcat的架構(gòu),所以用不著AJP協(xié)議,所以把AJP連接器禁用。修改conf下的server.xml文件,將AJP
服務(wù)禁用掉即可。
在tomcat中每一個(gè)用戶請(qǐng)求都是一個(gè)線程,所以可以使用線程池提高性能。
修改server.xml文件 :
保存退出,重啟tomcat,查看效果。
tomcat的運(yùn)行模式有3種 :
1. bio
默認(rèn)的模式,性能非常低下,沒有經(jīng)過任何優(yōu)化處理和支持。
2. nio
nio(new I/O),是Java SE 1.4及后續(xù)版本提供的一種新的I/O操作方式(既java.nio包及其子包)。Java nio是一個(gè)基于緩沖區(qū)、
并能提供非阻塞I/O操作的Java API,因此nio也被看成是non-blocking I/O的縮寫。它擁有比傳統(tǒng)I/O操作(bio)更好的并發(fā)運(yùn)行
性能。
3. apr
安裝起來最空難,但是從操作系統(tǒng)級(jí)別來解決異步的IO問題,大幅度的提高性能。
推薦使用nio,不過,在tomcat8中有最新的nio2,速度更快,建議使用nio2.
建議tomcat8以下使用nio,tomcat8及以上使用nio2.
Apache JMeter是開源的壓力測(cè)試工具,測(cè)量tomcat的吞吐量等信息。
下載地址 :http://jmeter.apache.org/download_jmeter.cgi
安裝 :直接將下載好的zip壓縮包進(jìn)行解壓即可。
默認(rèn)的主題是黑色風(fēng)格的主題并且語言是英語,這樣不太方便使用,所以需要修改一下主題和中文語言。
第一步 :保存測(cè)試用例
第二步 :添加線程組,使用線程模擬用戶的并發(fā)
第四步 :添加請(qǐng)求監(jiān)控
在聚合報(bào)告中,重點(diǎn)看吞吐量
通過上面測(cè)試可以看出,tomcat在不做任何調(diào)整時(shí),吞吐量為73次/秒。
可以看到,禁用AJP服務(wù)后,吞吐量會(huì)有所提升。
通過設(shè)置線程池,調(diào)整線程池相關(guān)的參數(shù)進(jìn)行測(cè)試tomcat的性能。
測(cè)試結(jié)果 :
吞吐量為128次/秒。
吞吐量為151,有所提升。
是否是線程數(shù)最多,速度越快呢?
可以看到,雖然最大線程已經(jīng)設(shè)置到5000,但是實(shí)際測(cè)試效果并不理想,并且平均的響應(yīng)時(shí)間也變長(zhǎng), 所以單純靠提升線程數(shù)量是不能一直得到性能提升的。
默認(rèn)情況下,請(qǐng)求發(fā)送到tomcat,如果tomcat正忙,那么該請(qǐng)求會(huì)一直等待。這樣雖然可以保證每個(gè)請(qǐng)求都能請(qǐng)求到,但是請(qǐng)求時(shí)間就會(huì)變長(zhǎng)。
有些時(shí)候,我們也不一定要求請(qǐng)求一定等待,可以設(shè)置最大等待隊(duì)列大小,如果超過就不等待了。這樣雖然有些請(qǐng)求是失敗的,但是請(qǐng)求時(shí)間會(huì)縮短。
測(cè)試結(jié)果 :
平均響應(yīng)時(shí)間 :2.5秒;響應(yīng)時(shí)間明顯縮短。
錯(cuò)誤率 :54%;錯(cuò)誤率提升到一半,也可以理解,最大線程為500,測(cè)試的并發(fā)為1000。
吞吐量 :281次/秒;吞吐量明顯提升。
結(jié)論 :響應(yīng)時(shí)間、吞吐量這2個(gè)指標(biāo)需要找到平衡才能達(dá)到更好的性能。
將最大線程設(shè)置為500進(jìn)行測(cè)試 :
可以看到,平均響應(yīng)時(shí)間有縮短,吞吐量有提升,可以得出結(jié)論 :nio2的性能要高于nio。
為了測(cè)試一致性,依然將最大線程數(shù)設(shè)置為500,啟用nio2運(yùn)行模式。
年輕代、老年代均使用并行收集器,初始堆內(nèi)存64M,最大堆內(nèi)存512M
JAVA_OPTS="-XX:+UseParallelGC -XX:UseParalleloldGC -Xms64 -Xmx512m -XX:+PrintGCDetails -XX:PringtGCTomeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:…/logs/gc.log"
測(cè)試結(jié)果與默認(rèn)的JVM參數(shù)結(jié)果接近。
在報(bào)告中線上,在5次GC時(shí),系統(tǒng)所消耗的時(shí)間大于用戶時(shí)間,這反應(yīng)出的服務(wù)器的性能存在瓶頸,調(diào)度CPU等資源所消耗的時(shí)間要長(zhǎng)一些。
可以從關(guān)鍵指標(biāo)中看出,吞吐量表現(xiàn)不錯(cuò),但是GC時(shí),線程的暫停時(shí)間稍有點(diǎn)長(zhǎng)。
通過GC的統(tǒng)計(jì)可以看出 :
年輕代的gc有74次,次數(shù)稍有點(diǎn)多,說明年輕代設(shè)置的大小不合適需要調(diào)整;
FullGC有8次,說明堆內(nèi)存的大小不合適,需要調(diào)整。
從GC原因可以看出,年輕代大小設(shè)置不合理,導(dǎo)致了多次GC。
JAVA_OPTS="-XX:+UseParallelGC -XX:+UseParalleloldGC -Xms128m -Xmx1024m -XX:NewSize=64m -XX:MaxNewSize=256m -XX:PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:…/logs/gc.log"
將初始堆大小設(shè)置為128m,最大為1024m
初始年輕代大小64m,年輕代最大256m
從測(cè)試結(jié)果來看,吞吐量以及響應(yīng)時(shí)間均有提升。
查看gc日志 :
可以看到GC次數(shù)要明顯減少,說明調(diào)整是有效的。
設(shè)置最大停頓時(shí)間100毫秒,初始堆內(nèi)存128m,最大堆內(nèi)存1024m
JAVA_OPTS="-XX:+UseG1GC -XX:MaxGCPauseMillis=100 -Xms128m -Xmx1024m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:…/logs/gc.log"
測(cè)試結(jié)果 :
可以看到,吞吐量有所提升,響應(yīng)時(shí)間也有所縮短。
通過上述測(cè)試,可以總結(jié)出,對(duì)tomcat性能優(yōu)化就是需要不斷的進(jìn)行調(diào)整參數(shù),然后測(cè)試結(jié)果,可能會(huì)調(diào)優(yōu)也可能會(huì)調(diào)差,這時(shí)就需要借助于gc的可視化工具來看gc的情況。再幫助我們做出決策應(yīng)用調(diào)整那些參數(shù)。
通過tomcat本身的參數(shù)以及jvm的參數(shù)對(duì)tomcat做了優(yōu)化,其實(shí)要想將應(yīng)用程序跑的更快、效率更高,除了對(duì)tomcat容器以及jvm優(yōu)化外,應(yīng)用程序代碼本身如果寫的效率不高,那么也是不行的,所以,對(duì)于程序本身的優(yōu)化也就很重要的。
對(duì)于程序本身的優(yōu)化,需要通過查看編譯好的class文件中字節(jié)碼。
java編寫應(yīng)用,需要先通過javac命令編譯成class文件,再通過jvm執(zhí)行,jvm執(zhí)行時(shí)是需要將class文件中的字節(jié)碼載入到j(luò)vm進(jìn)行運(yùn)行的。
首先,看一個(gè)簡(jiǎn)單的test1類的代碼 :
通過javap命令查看class文件中的字節(jié)碼內(nèi)容 :
內(nèi)容大致分為四個(gè)部分 :
第一部分 :顯示了生成這個(gè)class的java源文件、版本信息、生成時(shí)間等。
第二部分 :顯示了該類中所涉及到常量池,共35個(gè)常量。
第三部分 :顯示該類的構(gòu)造器,編譯器自動(dòng)插入的。
第四部分 :顯示了main方法的信息。(這個(gè)需要重點(diǎn)關(guān)注)
測(cè)試代碼 :
區(qū)別 :
1++
只是在本地變量中對(duì)數(shù)字做了相加,并沒有將數(shù)據(jù)壓入到操作棧。
將前面拿到的數(shù)字1,再次從操作棧中拿到,壓入到本地變量中。
++i
將本地變量中的數(shù)字做了相加,并且將數(shù)據(jù)壓入到操作棧。
將操作棧中的數(shù)據(jù),再次壓入到本地變量中。
小結(jié) :可以通過查看字節(jié)碼的方式對(duì)代碼的底層做研究,探究其原理。
字符串的拼接在開發(fā)過程中使用是非常頻繁的,常用的方式有三種 :
+號(hào)拼接 :str + “456”
StringBuilder拼接
StringBuffer拼接
StringBuffer是保證線程安全的,效率是比較低的,但是我們更多情況下是線程安全的,所以更多時(shí)候選擇StringBuilder,效率會(huì)高一些。
那么,問題來了,StringBuilder和“+”號(hào)拼接,那個(gè)效率高呢?可以通話字節(jié)碼的方式進(jìn)行探究。
示例 :
從解字節(jié)碼中可以看出,m1()方法源碼中是使用+號(hào)拼接,但是在字節(jié)碼中也被編譯成了StringBuilder方式。所以,可以得出結(jié)論,字符串拼接,+號(hào)和StringBuilder是相等的,效率一樣。
可以看到,m1()方法中的循環(huán)體內(nèi),每次循環(huán)都會(huì)創(chuàng)建StringBuilder對(duì)象,效率低于m2()方法。
使用字節(jié)碼的方式可以很好查看代碼底層的執(zhí)行,從而可以看出哪些實(shí)現(xiàn)效率高,哪些實(shí)現(xiàn)效率低??梢愿玫膶?duì)我們的代碼做優(yōu)化。讓程序執(zhí)行效率更高。
不僅僅在運(yùn)行環(huán)境進(jìn)行優(yōu)化,還需要在代碼本身做優(yōu)化,如果代碼本身存在性能問題,那么在其他方面再怎么優(yōu)化也不可能達(dá)到最優(yōu)效果。
調(diào)用方法時(shí)傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時(shí)變量都保存在棧中速度較快,其他變量,如靜態(tài)變量、實(shí)例變量等,都在堆中創(chuàng)建,速度較慢。另外,棧中創(chuàng)建的變量,隨著方法的運(yùn)行結(jié)束,這些內(nèi)容就沒了,不需要額外的垃圾回收。
明確一個(gè)概念,對(duì)方法的調(diào)用,即使方法中只有一條語句,也是有消耗的。例如 :
這樣,在list.size()很大的時(shí)候,就減少了很多的消耗。
異常對(duì)性能不利。拋出異常首先要?jiǎng)?chuàng)建一個(gè)新的對(duì)象,Throwable接口的構(gòu)造函數(shù)調(diào)用名為fillInStackTrace()的本地同步方法,
filllnStackTrace()方法檢查堆棧,收集調(diào)用跟蹤信息。只要有異常拋出,Java虛擬機(jī)就必須調(diào)整調(diào)用堆棧,因?yàn)樵谔幚磉^程中創(chuàng)建了一個(gè)新的對(duì)象。異常只能用于錯(cuò)誤處理,不應(yīng)該用來控制程序流程。
因?yàn)楹翢o意義,這樣只是定義了引用為static final,數(shù)組的內(nèi)容還是可以隨意改變的,將數(shù)組聲明為public更是一個(gè)安全漏洞,這意味著這個(gè)數(shù)組可以被外部類所改變。
這毫無意義,如果代碼中出現(xiàn)“The value of local variable i is not used”、“The import java.util is never used”,那么請(qǐng)刪除這些無用的內(nèi)容。
反射是Java提供給用戶一個(gè)很強(qiáng)大的功能,功能強(qiáng)大往往意味著效率不高。不建議在程序運(yùn)行過程中使用尤其是頻繁使用返回機(jī)制,特別是Method的invoke方法。
如果確實(shí)有必要,一種建議性的做法是將那些需要通過反射加載的類在項(xiàng)目啟動(dòng)的時(shí)候通過反射實(shí)例化出一個(gè)對(duì)象并放入內(nèi)存。
1、使用數(shù)據(jù)庫連接池和線程池,這樣可以重用對(duì)象,避免頻繁地打開和關(guān)閉連接,后者可以避免頻繁地創(chuàng)建和消耗線程。
2、容器初始化時(shí)盡可能指定長(zhǎng)度,如 :new ArrayList<>(10);new HashMap<>(32);避免容器長(zhǎng)度不足時(shí),擴(kuò)容帶來性能損耗。
3、ArrayList隨機(jī)遍歷快,LinkedList添加刪除快。
4、避免使用這種方式 :
Map<String, String> map = new HashMap<>();
for (String key : map.keySet()) {
String value = map.get(key);
}
盡量使用這種方式 :
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
}
5、不要手動(dòng)調(diào)用System.gc();
6、String盡量少用正則表達(dá)式 :
正則表達(dá)式雖然功能強(qiáng)大,但是其效率較低,除非是有需要,否則盡可能少用。
replace() : 不支持正則
replaceAll() : 支持正則
如果僅僅是字符的替換建議使用replace();
7、日志的輸出要注意級(jí)別;
8、對(duì)資源的close()建議分開操作。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。