Tomcat8.0.11优化相关
Tomcat 8.0.X:
要了解tomcat的優(yōu)化,我們先看看Tomcat的官方定義:The Apache Tomcat??software is an open source implementation of the Java Servlet, JavaServer Pages, Java Expression Language and Java WebSocket technologies. The Java Servlet, JavaServer Pages, Java Expression Language and Java WebSocket specifications are developed under the?Java Community Process.
Apache Tomcat軟件是一個開源的Java Servlet實(shí)現(xiàn),JavaServer Pages,Java表達(dá)式語言和Java WebSocket技術(shù)。Java Servlet、JavaServer頁面、Java表達(dá)式語言和Java WebSocket規(guī)范都是在Java Community Process下開發(fā)的。
Tomcat 系統(tǒng)架構(gòu):
Tomcat 的缺省配置是不能穩(wěn)定長期運(yùn)行的,也就是不適合生產(chǎn)環(huán)境,它會死機(jī),讓你不斷重新啟動,甚至在午夜時分喚醒你。對于操作系統(tǒng)優(yōu)化來說,是盡可能的增大可使用的內(nèi)存容量、提高CPU 的頻率,保證文件系統(tǒng)的讀寫速率等。經(jīng)過壓力測試驗(yàn)證,在并發(fā)連接很多的情況下,CPU 的處理能力越強(qiáng),系統(tǒng)運(yùn)行速度越快。
從系統(tǒng)架構(gòu)圖再結(jié)合 conf/server.xml 中的標(biāo)簽配置來說,再結(jié)合tomcat的源碼來看,每個組件都是對應(yīng)Java中的一個類或者接口,先加載 server.xml 文件,解析文件中的標(biāo)簽組裝成一個個的類,最后相互之間協(xié)同工作從而支撐起整個服務(wù)的運(yùn)行,如果要對Tomcat本身進(jìn)行優(yōu)化的話,可以通過server.xml來改變相應(yīng)組件的參數(shù)屬性及行為方式來達(dá)到優(yōu)化性能的目的,比如從架構(gòu)圖結(jié)合 server.xml我們可以得知其中比較重要的標(biāo)簽:Server,Services,Connector,Excutor,Engine,Host,Context等等。但是在官網(wǎng)中由如下介紹:
在這3個組件的介紹中都提到了該元素很少由用戶定制,所以這3個標(biāo)簽我們可以暫時不去過于的關(guān)注。Tomcat 的優(yōu)化不像其它軟件那樣,簡簡單單的修改幾個參數(shù)就可以了,由于他是由Java語言編寫的,那么他也是運(yùn)行在JVM上面的,它的優(yōu)化主要有三方面,分為系統(tǒng)優(yōu)化(機(jī)器本身的硬件性能)服務(wù)器的CPU、內(nèi)存、硬盤等對性能有決定性的影響,硬件這塊配置越高越好。,Tomcat 本身的優(yōu)化,Java 虛擬機(jī)(JVM)調(diào)優(yōu)。系統(tǒng)優(yōu)化就不在介紹了,接下來就詳細(xì)的介紹一下 Tomcat 本身與 JVM 優(yōu)化,以 Tomcat 8 為例。
從 Tomcat本身出發(fā),在conf/web.xml文件中配置了默認(rèn)的Servlet的支持以及一些靜態(tài)資源的處理,還有資源壓縮的支持,從代碼的角度,只要是執(zhí)行一段代碼片段那么他一定會耗費(fèi)一些時間,由于現(xiàn)在都采用nginx來管理靜態(tài)資源,實(shí)現(xiàn)前后端分離開發(fā),那么我們是否可以刪除一些標(biāo)簽,讓Tomcat本身盡可能少的去執(zhí)行無用的代碼也是可以提高相應(yīng)的啟動速度。
先從 conf/web.xml文件出發(fā),我們可以通過注釋掉與我們項(xiàng)目無關(guān)的組件標(biāo)簽來使得Tomcat盡可能的少執(zhí)行無用大代碼塊,再通過架構(gòu)圖結(jié)合源碼 的執(zhí)行邏輯是由外到內(nèi)的標(biāo)簽解析順序,我們可以先定位到的優(yōu)化點(diǎn)則是Connector,由官網(wǎng)的介紹我們可以得知Connector(連接器)處理與客戶機(jī)的通信。Tomcat提供了多個連接器。其中包括用于大多數(shù)HTTP通信的HTTP連接器,特別是在將Tomcat作為獨(dú)立服務(wù)器運(yùn)行時,以及實(shí)現(xiàn)將Tomcat連接到Apache HTTPD服務(wù)器等web服務(wù)器時使用的AJP協(xié)議的AJP連接器。創(chuàng)建定制的連接器是一項(xiàng)重大的工作。但是目前比較主流的是Nginx而不是Apache,我們可以根據(jù)自己的需求把 AJP協(xié)議相關(guān)的連接器注釋掉,也能起到一定的效果。從外層標(biāo)簽到內(nèi)層標(biāo)簽,一層層的來進(jìn)行優(yōu)化:
Connector標(biāo)簽:
先來看一下在 server.xml 中這個標(biāo)簽的定義:
<Connector connectionTimeout="20000" port="8081" protocol="HTTP/1.1" redirectPort="8443"/>這里一個非常重要的優(yōu)化的點(diǎn)是?protocol="HTTP/1.1",決定了Tomcat使用什么IO類型進(jìn)行數(shù)據(jù)交互。我們知道每個標(biāo)簽對應(yīng)到源碼中都會有一個類來表示。在Tomcat 8.0.x的源碼中支持4中IO類型。可以查看 AbstractEndpoint源碼發(fā)現(xiàn):
在Connector 構(gòu)造函數(shù)中會對該參數(shù)進(jìn)行設(shè)置:
public Connector(String protocol) {setProtocol(protocol);....... }在 setProtocol 方法中我們可以看到:
public void setProtocol(String protocol) {//當(dāng)開起了APR庫的支持if (AprLifecycleListener.isAprAvailable()) {if ("HTTP/1.1".equals(protocol)) {//默認(rèn)使用APR方式setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");} else if ("AJP/1.3".equals(protocol)) {setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");} else if (protocol != null) {setProtocolHandlerClassName(protocol);} else {setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");}} else {//否則默認(rèn)使用NIO來實(shí)現(xiàn)。if ("HTTP/1.1".equals(protocol)) {setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");} else if ("AJP/1.3".equals(protocol)) {setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");} else if (protocol != null) {setProtocolHandlerClassName(protocol);}}}我們可以很清晰的發(fā)現(xiàn)這個參數(shù)的值的重要性,在官網(wǎng)中也有介紹,我們可以修改成下列的指定值來指定我們需要的IO,當(dāng)我們使用APR方式的時候需要安裝相關(guān)APR支持的支持插件:
org.apache.coyote.http11.Http11Protocol - blocking Java connector org.apache.coyote.http11.Http11NioProtocol - non blocking Java NIO connector org.apache.coyote.http11.Http11Nio2Protocol - non blocking Java NIO2 connector org.apache.coyote.http11.Http11AprProtocol - the APR/native connector.然后我們通過 Jmeter 測試工具進(jìn)行壓測,當(dāng)我們進(jìn)行 BIO 與 NIO 進(jìn)行壓測會發(fā)現(xiàn),當(dāng)并發(fā)量相對來說比較小的時候,兩者并沒有很明顯的差別,甚至BIO還有略微的優(yōu)勢,當(dāng)并發(fā)量加大的時候,NIO會表現(xiàn)出很明顯的優(yōu)勢,由于BIO是阻塞的,當(dāng)并發(fā)量達(dá)到一定量的時候,可以通過工具看到會創(chuàng)建200個線程對請求進(jìn)行處理(如果沒有配置線程池會使用默認(rèn)的線程池),而其他的請求則進(jìn)入了等待隊(duì)列,而NIO在同樣的并發(fā)量下并沒有出現(xiàn)明顯的阻塞,而且對于兩者的吞吐量,以及平均響應(yīng)時間的比較發(fā)現(xiàn),NIO的吞吐量比BIO會高,而且響應(yīng)時間會較低。當(dāng)并發(fā)量只需增高,我們可以修改成APR的方式去調(diào)用系統(tǒng)的IO來執(zhí)行,可以支撐更大的并發(fā)場景。
查看架構(gòu)圖可以發(fā)現(xiàn)這里還涉及了一個 Executor 的配置。這里面一些關(guān)鍵的參數(shù)還是很影響Tomcat的性能的,可以在Connector標(biāo)簽中配置 ?executor 指定線程池:
可以實(shí)踐一下,Connector配合自定義的線程池:
<Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="150" minSpareThreads="4"/>這里我們還需要注意的一點(diǎn)是?enableLookups 這個屬性把他設(shè)置成false ,如果為true,則可以通過調(diào)用request.getRemoteHost()進(jìn)行DNS查詢來得到遠(yuǎn)程客戶端的實(shí)際主機(jī)名,若為false則不進(jìn)行DNS查詢,而是返回其ip地址。
如果這里沒有將 tomcat 與 Apache 服務(wù)器進(jìn)行整合,可以刪掉 AJP的Connector。
?Host標(biāo)簽:
<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true">在host標(biāo)簽中需要注意的一個點(diǎn)是?autoDeploy="true",Tomcat運(yùn)行時,要用一個線程拿出來進(jìn)行檢查,生產(chǎn)環(huán)境之下一定要改成false,他是開啟了一個線程對appBase目錄進(jìn)行監(jiān)控,如果改變了相關(guān)文件會進(jìn)行自動發(fā)布。
Context標(biāo)簽:
reloadable:如果這個屬性設(shè)為true,tomcat服務(wù)器在運(yùn)行狀態(tài)下會監(jiān)視在WEB-INF/classes和WEB-INF/lib目錄下class文件的改動,如果監(jiān)測到有class文件被更新的,服務(wù)器會自動重新加載Web應(yīng)用。在開發(fā)階段將reloadable屬性設(shè)為true,有助于調(diào)試servlet和其它的class文件,但這樣用加重服務(wù)器運(yùn)行負(fù)荷,建議在Web應(yīng)用的發(fā)存階段將reloadable設(shè)為false。
JVM優(yōu)化:
為什么會有JVM這塊的優(yōu)化?因?yàn)閠omcat是java語言寫的,那么對于jvm這塊的優(yōu)化在tomcat中就是適用的。比如修改一些參數(shù),調(diào)整內(nèi)存大小,選擇合適的垃圾回收算法等等。
現(xiàn)在有個問題,修改JVM參數(shù)在哪里修改會對tomcat生效?還是在bin文件夾之下,有一個catalina.sh,找到JAVA_OPTS即可,當(dāng)然不建議對此文件進(jìn)行直接修改,一般是在外面新建一個文件,然后引入進(jìn)來,我們就不這樣做了,直接修改bin/catalina.sh 文件。
運(yùn)行時數(shù)據(jù)區(qū)和內(nèi)存結(jié)構(gòu):
(1)程序計(jì)數(shù)器The pc Register,JVM支持多線程同時執(zhí)行,每一個線程都有自己的pc register,線程正在執(zhí)行的方法叫做當(dāng)前方法。如果是java代碼,pc register中存放的就是當(dāng)前正在執(zhí)行的指令的地址,如果是c代碼,則為空。
(2)Java虛擬機(jī)棧Java Virtual Machine Stacks,Java虛擬機(jī)棧是線程私有的,它的生命周期和線程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀,用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。每一個方法從調(diào)用直到執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機(jī)棧中入棧到出棧的過程。
(3)堆Heap,Java堆是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。對是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時創(chuàng)建。次內(nèi)存區(qū)域的唯一目的就是存放對象實(shí)例,幾乎所有的對象實(shí)例都在這里分配內(nèi)存。Java對可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上市連續(xù)的即可。
(4)方法區(qū)Method Area,方法區(qū)和Java堆一樣,是各個線程共享的內(nèi)存區(qū)域,也是在虛擬機(jī)啟動時創(chuàng)建。它用于存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的是與Java堆區(qū)分開來。jdk1.8中就是metaspace,jdk1.6或者1.7中就是perm space。運(yùn)行時常量池Runtime Constant Pool是方法區(qū)的一部分,Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息就是常量池,用于存放編譯時期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時常量池中存放。
(5)本地方法棧Native Method Stacks,本地方法棧和虛擬機(jī)棧鎖發(fā)揮的作用是非常相似的,它們之間的區(qū)別不過是虛擬機(jī)棧執(zhí)行Java方法服務(wù),而本地方法棧則為虛擬機(jī)使用到的native方法服務(wù)。
內(nèi)存結(jié)構(gòu):
上面對運(yùn)行時數(shù)據(jù)區(qū)描述了很多,其實(shí)重點(diǎn)存儲數(shù)據(jù)的是堆和方法區(qū)(非堆),所以我們內(nèi)存結(jié)構(gòu)的設(shè)計(jì)也是著重從這兩方面展開的。簡單的描述一下JVM內(nèi)存結(jié)構(gòu)。一塊是非堆區(qū),一塊是堆區(qū)。堆區(qū)分為兩大塊,一個是Old區(qū),一個是Young區(qū)。Young區(qū)分為兩大塊,一個是Survival區(qū)(S0+S1),一塊是Eden區(qū)。 Eden:S0:S1=8:1:1,S0和S1一樣大,也可以叫From和To。在同一個時間點(diǎn)上,S0和S1只能有一個區(qū)有數(shù)據(jù),另外一個是空的。
垃圾回收算法:
Java是做自動內(nèi)存管理的,自動垃圾回收。如何確定一個對象是否是垃圾,從而確定是否需要回收?
(1)引用計(jì)數(shù):對于某個對象而言,只要應(yīng)用程序中持有該對象的引用,就說明該對象不是垃圾,如果一個對象沒有任何指針對其引用,它就是垃圾。弊端 :AB相互持有引用,導(dǎo)致永遠(yuǎn)不能被回收。
(2)枚舉根節(jié)點(diǎn)做可達(dá)性分析。能作為根節(jié)點(diǎn)的 :類加載器、Thread、虛擬機(jī)棧的本地變量表、static成員、常量引用、本地方法棧的變量等。
常用的垃圾回收算法:能夠確定一個對象是垃圾之后,怎么回收?得要有對應(yīng)的算法
(1)標(biāo)記清除:先標(biāo)記所有需要回收的對象,然后統(tǒng)一回收。缺點(diǎn) :效率不高,標(biāo)記和清除兩個過程的效率都不高,容易產(chǎn)生碎片,碎片太多會導(dǎo)致提前GC。
(2)復(fù)制:將內(nèi)存按容量劃分為大小相等的兩塊(S0和S1),每次只使用其中一塊。當(dāng)這塊使用完了,就講還存活的對象復(fù)制到另一塊上,然后再把已經(jīng)使用過的內(nèi)存空間一次性清除掉【Young區(qū)此采用的是復(fù)制算法】優(yōu)缺點(diǎn) :實(shí)現(xiàn)簡單,運(yùn)行高效,但是空間利用率低。
(3)標(biāo)記整理:標(biāo)記需要回收的對象,然后讓所有存活的對象移動到另外一端,直接清理掉端邊界意外的內(nèi)存。
JVM中采用的是分代垃圾回收,換句話說,堆中的Old區(qū)和Young區(qū)采用的垃圾回收算法是不一樣的。
(1)Young區(qū):復(fù)制算法
(2)Old區(qū):標(biāo)記清除或標(biāo)記整理:對象在被分配之后,可能聲明周期比較短,Young區(qū)復(fù)制效率比較高。Old區(qū)對象存活時間比較長,復(fù)制來復(fù)制去沒必要,不如做個標(biāo)記。
對象分配方式:
對象優(yōu)先分配在Eden區(qū),大對象直接進(jìn)入老年代,多大的對象稱為大對象?可以通過JVM參數(shù)指定 -XX:PretenureSizeThreshold,長期存活對象進(jìn)入老年代。
垃圾收集器:
垃圾收集器從大類上分為三大類,串行收集器,并行收集器,并發(fā)收集器。
串行收集器Serial:Serial、Serial Old:一個線程跑,停止,啟動垃圾回收線程,回收完成,繼續(xù)執(zhí)行剛才暫停的線程。適用于內(nèi)存比較小的嵌入式設(shè)備中。
并行收集器Parallel:Parallel Scavenge、Parallel Old,吞吐量優(yōu)先,多條垃圾收集線程并行工作,但此時用戶線程仍然處于等待狀態(tài),適合科學(xué)計(jì)算、后臺處理等弱交互場景。
并發(fā)收集器Concurrent:CMS、G1,停頓時間優(yōu)先,用戶線程和垃圾收集線程同時執(zhí)行(但不一定是并行的,可能會交替執(zhí)行),垃圾收集線程在執(zhí)行的時候不會停頓用戶程序的運(yùn)行。適合于對相應(yīng)時間有要求的場景,比如Web。
吞吐量和停頓時間解釋:
吞吐量:花在垃圾收集的時間和花在應(yīng)用程序時間的占比。
停頓時間:垃圾收集器做垃圾回收終端應(yīng)用執(zhí)行的時間。
小結(jié): 評價一個垃圾回收器的好壞,其實(shí)調(diào)優(yōu)的時候就是在觀察者兩個變量
開啟垃圾收集器:XX參數(shù)中的+-代表啟用或者禁用該選項(xiàng)
(1)串行: -XX:+UseSerialGC -XX:+UseSerialOldGC 新老生代 (2)并行(吞吐量優(yōu)先):-XX:+UseParallelGC-XX:+UseParallelOldGC (3)并發(fā)收集器(響應(yīng)時間優(yōu)先)CMS: -XX:+UseConcMarkSweepGCG1: -XX:+UseG1GC我們可以通過命令查看當(dāng)前使用的垃圾回收器,可以根據(jù)自己的需求進(jìn)行優(yōu)化:
[root@pretty ~]# jinfo -flag UseParallelGC 6925 -XX:+UseParallelGC --->發(fā)現(xiàn)使用了ParallelGC [root@pretty ~]# jinfo -flag UseG1GC 6925 -XX:-UseG1GC --->發(fā)現(xiàn)沒有使用G1GC那么我們到底怎么查看GC的執(zhí)行呢,我該怎么由我的肉眼去看到當(dāng)前設(shè)置的GC算法及垃圾收集器的好壞呢?要想分析,得把GC日志打印出來才行,可以在tomcat中catalina.sh JAVA_OPTS配置相關(guān)參數(shù)。我們可以設(shè)置GC日志輸出:
XX:+PrintGCDetails(:打印日志詳情信息) -XX:+PrintGCTimeStamps(輸出GC的時間戳(以基準(zhǔn)時間的形式)) -XX:+PrintGCDateStamps -Xloggc:$CATALINA_HOME/logs/gc.log然后重啟tomcat,在需要的時候可以將log下載下來看看內(nèi)容,這樣直接看日志有點(diǎn)費(fèi)勁,對于高手當(dāng)然是不費(fèi)勁的,這里推薦一款GC日志分析軟件,在線:http://gceasy.io,登陸這個網(wǎng)站,將log上傳就會得到他對我們GC日志分析的結(jié)果,以及相應(yīng)一些可行性優(yōu)化建議。
內(nèi)存溢出和優(yōu)化:
通常會優(yōu)化以下參數(shù),一般將 Xms 與 Xmx設(shè)置成一樣的數(shù)值,這也是前輩們得出的最佳實(shí)踐:
-Xms 等價于-XX:InitialHeapSize -- 初始化堆區(qū)大小 -Xmx 等價于-XX:MaxHeapSize -- 堆區(qū)最大大小 -Xss 等價于-XX:ThreadStackSize -- 棧大小另外一點(diǎn),我們需要知道當(dāng)前的參數(shù)是多少才能進(jìn)行優(yōu)化不是嗎?接下去介紹幾個查看參數(shù)的命令:
jinfo 查看已經(jīng)運(yùn)行的jvm里面的參數(shù)值 jinfo -flag MaxHeapSize PID 查看最大內(nèi)存 jinfo -flag UseG1GC PID 查看垃圾回收器 jinfo -flags PID 查看曾經(jīng)賦過值的一些參數(shù)內(nèi)存不夠用主要分為兩個方面:堆和非堆。所以這時候就要去手動設(shè)置堆或者非堆的大小,然后程序中不停使用相對應(yīng)的區(qū)域,等待內(nèi)存溢出。關(guān)鍵是內(nèi)存溢出之后,怎么得到溢出信息進(jìn)行分析,有兩種做法:
參數(shù)設(shè)置自動:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./jmap手動:
查看當(dāng)前進(jìn)程id PID jmap -dump:format=b,file=heap.hprof PID jmap -heap PID 打印出堆內(nèi)存相關(guān)的信息當(dāng)內(nèi)存信息打印出來之后,發(fā)現(xiàn)看不懂,怎么辦呢?得要有工具幫助我們看這塊的信息,比如MAT。內(nèi)存大小的設(shè)置會影響GC的使用頻率,想得到最大的優(yōu)化效果需要進(jìn)行不斷地調(diào)試,取出一個最優(yōu)的選項(xiàng)。
其他優(yōu)化:
- Connector:配置壓縮屬性compression="500",文件大于500bytes才會壓縮
- 數(shù)據(jù)庫優(yōu)化:減少對數(shù)據(jù)庫訪問等待的時間,可以從數(shù)據(jù)庫的層面進(jìn)行優(yōu)化,或者加緩存等等各種方案。
- 開啟瀏覽器緩存,nginx靜態(tài)資源部署
所有的優(yōu)化都是相輔相成的,一點(diǎn)一滴的去提升服務(wù)器的性能,但是有一句話不得不說,再牛逼的服務(wù)器性能要是遇到傻逼開發(fā)寫的代碼,那也無濟(jì)于事。
轉(zhuǎn)載于:https://www.cnblogs.com/wuzhenzhao/p/10355560.html
總結(jié)
以上是生活随笔為你收集整理的Tomcat8.0.11优化相关的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【题解】Luogu P3674 小清新人
- 下一篇: BZOJ4653 尺取法 + 线段树