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