Tomcat參數優化

image-20210622154519229

從這個圖中可以得出,限制Tomcat請求數量的因素四個方面。

當前服務器系統資源

我想可能大家遇到過類似“Socket/File:Can't open so many files”的異常,這個就是表示Linux系統中的文件句柄限制。

在Linux中,每一個TCP連接會佔用一個文件描述符(fd),一旦文件描述符超過Linux系統當前的限制,就會提示這個錯誤。

我們可以通過下面這條命令來查看一個進程可以打開的文件數量

ulimit -a 或者 ulimit -n

open files (-n) 1024 是linux操作系統對一個進程打開的文件句柄數量的限制(也包含打開的套接字數量)

這裏只是對用戶級別的限制,其實還有個是對系統的總限制,查看系統總線制:

cat /proc/sys/fs/file-max

file-max是設置系統所有進程一共可以打開的文件數量 。同時一些程序可以通過setrlimit調用,設置每個進程的限制。如果得到大量使用完文件句柄的錯誤信息,是應該增加這個值。

當出現上述異常時,我們可以通過下面的方式來進行修改(針對單個進程的打開數量限制)

vi /etc/security/limits.conf
  root soft nofile 65535
  root hard nofile 65535
  * soft nofile 65535
  * hard nofile 65535
  • *代表所有用戶、root表示root用戶。
  • noproc 表示最大進程數量
  • nofile代表最大文件打開數量。
  • soft/hard,前者當達到閾值時,製作警告,後者會報錯。

另外還要注意,要確保針對進程級別的文件打開數量反問是小於或者等於系統的總限制,否則,我們需要修改系統的總限制。

vi /proc/sys/fs/file-max

TCP連接對於系統資源最大的開銷就是內存。

因爲tcp連接歸根結底需要雙方接收和發送數據,那麼就需要一個讀緩衝區和寫緩衝區,這兩個buffer在linux下最小爲4096字節,可通過cat /proc/sys/net/ipv4/tcp_rmem和cat /proc/sys/net/ipv4/tcp_wmem來查看。

所以,一個tcp連接最小佔用內存爲4096+4096 = 8k,那麼對於一個8G內存的機器,在不考慮其他限制下,最多支持的併發量爲:810241024/8 約等於100萬。此數字爲純理論上限數值,在實際中,由於linux kernel對一些資源的限制,加上程序的業務處理,所以,8G內存是很難達到100萬連接的,當然,我們也可以通過增加內存的方式增加併發量。

Tomcat依賴的JVM的配置

我們知道Tomcat是Java程序,運行在JVM上,因此我們還需要對JVM做優化,才能更好的提升Tomcat的性能,簡單帶大家瞭解一下JVM,如下圖所示。

image-20210623204411021

在JVM中,內存劃分爲堆、程序計數器、本地方發棧、方法區(元空間)、虛擬機棧。

堆空間說明

其中,堆內存是JVM內存中最大的一塊區域,幾乎所有的對象和數組都會被分配到堆內存中,它被所有線程共享。 堆空間被劃分爲新生代和老年代,新生代進一步劃分爲Eden和Surivor區,如下圖所示。

image-20210623205840226

新生代和老年代的比例是1:2,也就是新生代會佔1/3的堆空間,老年代會佔2/3的堆空間。 另外,在新生代中,空間佔比爲Eden:Surivor0:Surivor1=8:1:1 。 舉個例子來說,如果eden區內存大小是40M,那麼兩個Survivor區分別是佔5M,整個新生代就是50M,然後計算出老年代的內存大小是100M,也就是說堆空間的總內存大小是150M。

可以通過 java -XX:PrintFlagsFinal -version查看默認參數

uintx InitialSurvivorRatio                      = 8
uintx NewRatio                                  = 2

InitialSurvivorRatio: 新生代Eden/Survivor空間的初始比例

NewRatio : Old區/Young區的內存比例

堆內存的具體工作原理是:

  • 絕大部分的對象被創建之後,會保存在Eden區,當Eden區滿了的時候,就會觸發YGC(Young GC),大部分對象會被回收掉,如果還有活着的對象,就拷貝到Survivor0,這時Eden區被清空。
  • 如果後續再次觸發YGC,活着的對象Eden+Survivor0中的對象拷貝到Survivor1區, 這時Eden和Survivor0都會被清空
  • 接着再觸發YGC,Eden+Survivor1中的對象會被拷貝到Survivor0區,一直這麼循環,直到對象的年齡達到閾值,則放入到老年代。(之所以這麼設計,是因爲Eden區的大部分對象會被回收)
  • Survivor區裝不下的對象會直接進入到老年代
  • 老年代滿了,會觸發Full GC。

GC標記-清除算法 在執行過程中暫停其他線程??

image-20210623214030533

程序計數器

程序計數器是用來記錄各個線程執行的字節碼地址等,當線程發生上下文切換時,需要依靠這個來記住當前執行的位置,當下次恢復執行後要沿着上一次執行的位置繼續執行。

方法區

方法區是邏輯上的概念,在HotSpot虛擬機的1.8版本中,它的具體實現就是元空間。

方法區主要用來存放已經被虛擬機加載的類相關信息,包括類元信息、運行時常量池、字符串常量池,類信息又包括類的版本、字段、方法、接口和父類信息等。

方法區和堆空間類似,它是一個共享內存區域,所以方法區是屬於線程共享的。

本地方發棧和虛擬機棧

Java虛擬機棧是線程私有的內存空間,當創建一個線程時,會在虛擬機中申請一個線程棧,用來保存方法的局部變量、操作數棧、動態鏈接方法等信息。每一個方法的調用都伴隨這棧幀的入棧操作,當一個方法返回之後,就是棧幀的出棧操作。

本地方法棧和虛擬機棧類似,本地方法棧是用來管理本地方法的調用,也就是native方法。

JVM內存應該怎麼設置

瞭解了上述基本信息之後,那麼JVM中內存應該如何設置呢?有哪些參數來設置?

而在JVM中,要配置的幾個核心參數無非是。

  • -Xms,Java堆內存大小

  • -Xmx,Java最大堆內存大小

  • -Xmn,Java堆內存中的新生代大小,扣除新生代剩下的就是老年代內存

    新生代內存設置過小會頻繁觸發Minor GC,頻繁觸發GC會影響系統的穩定性

  • -XX:MetaspaceSize,元空間大小, 128M

  • -XX:MaxMetaspaceSize,最大雲空間大小 (如果沒有指定這兩個參數,元空間會在運行時根據需要動態調整。) 256M

    一個新系統的元空間,基本上沒辦法有一個測算的方法,一般設置幾百兆就夠用,因爲這裏面主要存放一些類信息。

  • -Xss,線程棧內存大小,這個基本上不需要預估,設置512KB到1M就行,因爲值越小,能夠分配的線程數越多。

JVM內存的大小,取決於機器的配置,比如一個2核4G的服務器,能夠分配給JVM進程也就2G左右,因爲機器本身也需要內存,而且機器上還運行了其他的進程也需要佔內存。而這2G還得分配給棧內存、堆內存、元空間,那堆內存能夠得到的也就1G左右,然後堆內存還要分新生代、老年代。

Tomcat本身的配置

http://tomcat.apache.org/tomcat-8.0-doc/config/http.html

The maximum number of request processing threads to be created by this Connector, which therefore determines the maximum number of simultaneous requests that can be handled. If not specified, this attribute is set to 200. If an executor is associated with this connector, this attribute is ignored as the connector will execute tasks using the executor rather than an internal thread pool. Note that if an executor is configured any value set for this attribute will be recorded correctly but it will be reported (e.g. via JMX) as -1 to make clear that it is not used.

server:
  tomcat:
    uri-encoding: UTF-8
    #最大工作線程數,默認200, 4核8g內存,線程數經驗值800
    #操作系統做線程之間的切換調度是有系統開銷的,所以不是越多越好。
    max-threads: 1000
    # 等待隊列長度,默認100,
    accept-count: 1000
    max-connections: 20000
    # 最小工作空閒線程數,默認10, 適當增大一些,以便應對突然增長的訪問量
    min-spare-threads: 100
    • accept-count: 最大等待數,當調用HTTP請求數達到tomcat的最大線程數時,還有新的HTTP請求到來,這時tomcat會將該請求放在等待隊列中,這個acceptCount就是指能夠接受的最大等待數,默認100。如果等待隊列也被放滿了,這個時候再來新的請求就會被tomcat拒絕(connection refused)

    • maxThreads:最大線程數,每一次HTTP請求到達Web服務,tomcat都會創建一個線程來處理該請求,那麼最大線程數決定了Web服務容器可以同時處理多少個請求。maxThreads默認200,肯定建議增加。但是,增加線程是有成本的,更多的線程,不僅僅會帶來更多的線程上下文切換成本,而且意味着帶來更多的內存消耗。JVM中默認情況下在創建新線程時會分配大小爲1M的線程棧,所以,更多的線程異味着需要更多的內存。線程數的經驗值爲:1核2g內存爲200,線程數經驗值200;4核8g內存,線程數經驗值800。

    • maxConnections,最大連接數,這個參數是指在同一時間,tomcat能夠接受的最大連接數。對於Java的阻塞式BIO,默認值是maxthreads的值;如果在BIO模式使用定製的Executor執行器,默認值將是執行器中maxthreads的值。對於Java 新的NIO模式,maxConnections 默認值是10000。對於windows上APR/native IO模式,maxConnections默認值爲8192

      如果設置爲-1,則禁用maxconnections功能,表示不限制tomcat容器的連接數。
      maxConnections和accept-count的關係爲:當連接數達到最大值maxConnections後,系統會繼續接收連接,但不會超過acceptCount的值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章