国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频

Tomcat 高并發(fā)之道原理拆解與性能調(diào)優(yōu)

共 22214字,需瀏覽 45分鐘

 ·

2020-08-20 14:21

高并發(fā)拆解核心準備

這回,再次拆解,專注 Tomcat 高并發(fā)設(shè)計之道與性能調(diào)優(yōu),讓大家對整個架構(gòu)有更高層次的了解與感悟。其中設(shè)計的每個組件思路都是將 Java 面向?qū)ο?、面向接口、如何封裝變與不變,如何根據(jù)實際需求抽象不同組件分工合作,如何設(shè)計類實現(xiàn)單一職責(zé),怎么做到將相似功能高內(nèi)聚低耦合,設(shè)計模式運用到極致的學(xué)習(xí)借鑒。

這次主要涉及到的是 I/O 模型,以及線程池的基礎(chǔ)內(nèi)容。

一起來看 Tomcat 如何實現(xiàn)并發(fā)連接處理以及任務(wù)處理,性能的優(yōu)化是每一個組件都起到對應(yīng)的作用,如何使用最少的內(nèi)存,最快的速度執(zhí)行是我們的目標。

設(shè)計模式


?模板方法模式:抽象算法流程在抽象類中,封裝流程中的變化與不變點。將變化點延遲到子類實現(xiàn),達到代碼復(fù)用,開閉原則。

?觀察者模式:針對事件不同組件有不同響應(yīng)機制的需求場景,達到解耦靈活通知下游。

?責(zé)任鏈模式:將對象連接成一條鏈,將沿著這條鏈傳遞請求。在 Tomcat 中的 Valve 就是該設(shè)計模式的運用。

I/O 模型

Tomcat 實現(xiàn)高并發(fā)接收連接,必然涉及到 I/O 模型的運用,了解同步阻塞、異步阻塞、I/O 多路復(fù)用,異步非阻塞相關(guān)概念以及 Java NIO 包的運用很有必要。本文也會帶大家著重說明 I/O 是如何在 Tomcat 運用實現(xiàn)高并發(fā)連接。大家通過本文我相信對 I/O 模型也會有一個深刻認識。

Java 并發(fā)編程

實現(xiàn)高并發(fā),除了整體每個組件的優(yōu)雅設(shè)計、設(shè)計模式的合理、I/O 的運用,還需要線程模型,如何高效的并發(fā)編程技巧。在高并發(fā)過程中,不可避免的會出現(xiàn)多個線程對共享變量的訪問,需要加鎖實現(xiàn),如何高效的降低鎖沖突。因此作為程序員,要有意識的盡量避免鎖的使用,比如可以使用原子類 CAS 或者并發(fā)集合來代替。如果萬不得已需要用到鎖,也要盡量縮小鎖的范圍和鎖的強度。


對于并發(fā)相關(guān)的基礎(chǔ)知識,如果讀者感興趣后面也給大家安排上,目前也寫了部分并發(fā)專輯,大家可移步到歷史文章或者專輯翻閱,主要講解了并發(fā)實現(xiàn)的原理、什么是內(nèi)存可見性,JMM 內(nèi)存模模型、讀寫鎖等并發(fā)知識點。


Tomcat 總體架構(gòu)

再次回顧下 Tomcat 整體架構(gòu)設(shè)計,主要設(shè)計了?connector 連接器處理 TCP/IP 連接,container 容器作為 Servlet 容器,處理具體的業(yè)務(wù)請求。對外對內(nèi)分別抽象兩個組件實現(xiàn)拓展。

  • 一個 Tomcat 實例默認會有一個 Service,而一個 Service 可以包含多個連接器。連接器主要有 ProtocalHandler 和 Adapter 兩個組件共同完成連接器核心功能。
  • ProtocolHandler?主要由?Acceptor?以及?SocketProcessor?構(gòu)成,實現(xiàn)了 TCP/IP 層 的 Socket 讀取并轉(zhuǎn)換成?TomcatRequest?和?TomcatResponse,最后根據(jù) http 或者 ajp 協(xié)議獲取合適的?Processor?解析為應(yīng)用層協(xié)議,并通過 Adapter 將 TomcatRequest、TomcatResponse 轉(zhuǎn)化成 標準的 ServletRequest、ServletResponse。通過?getAdapter().service(request, response);將請求傳遞到 Container 容器。
  • adapter.service()實現(xiàn)將請求轉(zhuǎn)發(fā)到容器?org.apache.catalina.connector.CoyoteAdapter
//?Calling?the?container
connector.getService().getContainer().getPipeline().getFirst().invoke(
????????????????????????request,?response);

這個調(diào)用會觸發(fā) getPipeline 構(gòu)成的責(zé)任鏈模式將請求一步步走入容器內(nèi)部,每個容器都有一條 Pipeline,通過 First 開始到 Basic 結(jié)束并進入容器內(nèi)部持有的子類容器,最后到 Servlet,這里就是責(zé)任鏈模式的經(jīng)典運用。具體的源碼組件是 Pipeline 構(gòu)成一條請求鏈。如下圖所示,整個 Tomcat 的架構(gòu)設(shè)計重要組件清晰可見,希望大家將這個全局架構(gòu)圖深深印在腦海里,掌握全局思路才能更好地分析細節(jié)之美。

Tomcat 架構(gòu)

啟動流程:startup.sh 腳本到底發(fā)生了什么

Tomcat 啟動流程
  • Tomcat 本生就是一個 Java 程序,所以 startup.sh 腳本就是啟動一個 JVM 來運行 Tomcat 的啟動類 Bootstrap。
  • Bootstrap 主要就是實例化 Catalina 和初始化 Tomcat 自定義的類加載器。熱加載與熱部署就是靠他實現(xiàn)。
  • Catalina: 解析 server.xml 創(chuàng)建 Server 組件,并且調(diào)用 Server.start() 方法。
  • Server:管理 Service 組件,調(diào)用 Server 的 start() 方法。
  • Service:主要職責(zé)就是管理連接器和頂層容器 Engine,分別調(diào)用?Connector?和?Engine?的?start?方法。

Engine 容器主要就是組合模式將各個容器根據(jù)父子關(guān)系關(guān)聯(lián),并且 Container 容器繼承了 Lifecycle 實現(xiàn)各個容器的初始化與啟動。Lifecycle 定義了?init()、start()、stop()?控制整個容器組件的生命周期實現(xiàn)一鍵啟停。

這里就是一個面向接口、單一職責(zé)的設(shè)計思想?,Container 利用組合模式管理容器,LifecycleBase 抽象類繼承 Lifecycle 將各大容器生命周期統(tǒng)一管理這里便是,而實現(xiàn)初始化與啟動的過程又 LifecycleBase 運用了?模板方法設(shè)計模式抽象出組件變化與不變的點,將不同組件的初始化延遲到具體子類實現(xiàn)。并且利用觀察者模式發(fā)布啟動事件解耦。

具體的 init 與 start 流程如下泳道圖所示:這是我在閱讀源碼 debug 所做的筆記,讀者朋友們不要怕筆記花費時間長,自己跟著 debug 慢慢記錄,相信會有更深的感悟。

init 流程

Tomcat Init

start 流程

Tomcat start

讀者朋友根據(jù)我的兩篇內(nèi)容,抓住主線組件去 debug,然后跟著該泳道圖閱讀源碼,我相信都會有所收獲,并且事半功倍。在讀源碼的過程中,切勿進入某個細節(jié),一定要先把各個組件抽象出來,了解每個組件的職責(zé)即可。最后在了解每個組件的職責(zé)與設(shè)計哲學(xué)之后再深入理解每個組件的實現(xiàn)細節(jié),千萬不要一開始就想著深入理解具體一篇葉子。

每個核心類我在架構(gòu)設(shè)計圖以及泳道圖都標識出來了,給大家分享下如何高效閱讀源碼,以及保持學(xué)習(xí)興趣的心得體會。

如何正確閱讀源碼

切勿陷入細節(jié),不看全局:我還沒弄清楚森林長啥樣,就盯著葉子看?,看不到全貌和整體設(shè)計思路。所以閱讀源碼學(xué)習(xí)的時候不要一開始就進入細節(jié),而是宏觀看待整體架構(gòu)設(shè)計思想,模塊之間的關(guān)系。

1.閱讀源碼之前,需要有一定的技術(shù)儲備

比如常用的設(shè)計模式,這個必須掌握,尤其是:模板方法、策略模式、單例、工廠、觀察者、動態(tài)代理、適配器、責(zé)任鏈、裝飾器。大家可以看 關(guān)于設(shè)計模式的歷史文章,打造好的基礎(chǔ)。

2.必須會使用這個框架/類庫,精通各種變通用法

魔鬼都在細節(jié)中,如果有些用法根本不知道,可能你能看明白代碼是什么意思,但是不知道它為什么這些寫。

3.先去找書,找資料,了解這個軟件的整體設(shè)計。

從全局的視角去看待,上帝視角理出主要核心架構(gòu)設(shè)計,先森林后樹葉。都有哪些模塊?模塊之間是怎么關(guān)聯(lián)的?怎么關(guān)聯(lián)的?

可能一下子理解不了,但是要建立一個整體的概念,就像一個地圖,防止你迷航。

在讀源碼的時候可以時不時看看自己在什么地方。就像「碼哥字節(jié)」給大家梳理好了 Tomcat 相關(guān)架構(gòu)設(shè)計,然后自己再嘗試跟著 debug,這樣的效率如虎添翼。

4. 搭建系統(tǒng),把源代碼跑起來!

Debug 是非常非常重要的手段, 你想通過只看而不運行就把系統(tǒng)搞清楚,那是根本不可能的!合理運用調(diào)用棧(觀察調(diào)用過程上下文)。

5.筆記

一個非常重要的工作就是記筆記(又是寫作!),畫出系統(tǒng)的類圖(不要依靠 IDE 給你生成的), 記錄下主要的函數(shù)調(diào)用, 方便后續(xù)查看。

文檔工作極為重要,因為代碼太復(fù)雜,人的大腦容量也有限,記不住所有的細節(jié)。文檔可以幫助你記住關(guān)鍵點, 到時候可以回想起來,迅速地接著往下看。

要不然,你今天看的,可能到明天就忘個差不多了。所以朋友們記得收藏后多翻來看看,嘗試把源碼下載下來反復(fù)調(diào)試。

錯誤方式

  • 陷入細節(jié),不看全局:我還沒弄清楚森林長啥樣,就盯著葉子看?,看不到全貌和整體設(shè)計思路。所以閱讀源碼學(xué)習(xí)的時候不要一開始就進入細節(jié),而是宏觀看待整體架構(gòu)設(shè)計思想,模塊之間的關(guān)系。
  • 還沒學(xué)會用就研究如何設(shè)計:首先基本上框架都運用了設(shè)計模式,我們最起碼也要了解常用的設(shè)計模式,即使是“背”,也得了然于胸。在學(xué)習(xí)一門技術(shù),我推薦先看官方文檔,看看有哪些模塊、整體設(shè)計思想。然后下載示例跑一遍,最后才是看源碼。
  • 看源碼深究細節(jié):到了看具體某個模塊源碼的時候也要下意識的不要去深入細節(jié),重要的是學(xué)習(xí)設(shè)計思路,而不是具體一個方法實現(xiàn)邏輯。除非自己要基于源碼做二次開發(fā),而且二次開發(fā)也是基于在了解整個架構(gòu)的情況下才能深入細節(jié)。

組件設(shè)計-落實單一職責(zé)、面向接口思想

當我們接到一個功能需求的時候,最重要的就是抽象設(shè)計,將功能拆解主要核心組件,然后找到需求的變化與不變點,將相似功能內(nèi)聚,功能之間若耦合,同時對外支持可拓展,對內(nèi)關(guān)閉修改。努力做到一個需求下來的時候我們需要合理的抽象能力抽象出不同組件,而不是一鍋端將所有功能糅合在一個類甚至一個方法之中,這樣的代碼牽一發(fā)而動全身,無法拓展,難以維護和閱讀。

帶著問題我們來分析 Tomcat 如何設(shè)計組件完成連接與容器管理。

看看 Tomcat 如何實現(xiàn)將 Tomcat 啟動,并且又是如何接受請求,將請求轉(zhuǎn)發(fā)到我們的 Servlet 中。

Catalina

主要任務(wù)就是創(chuàng)建 Server,并不是簡單創(chuàng)建,而是解析 server.xml 文件把文件配置的各個組件意義創(chuàng)建出來,接著調(diào)用 Server 的 init() 和 start() 方法,啟動之旅從這里開始…,同時還要兼顧異常,比如關(guān)閉 Tomcat 還需要做到優(yōu)雅關(guān)閉啟動過程創(chuàng)建的資源需要釋放,Tomcat 則是在 JVM 注冊一個「關(guān)閉鉤子」,源碼我都加了注釋,省略了部分無關(guān)代碼。同時通過?await()?監(jiān)聽停止指令關(guān)閉 Tomcat。

????/**
?????*?Start?a?new?server?instance.
?????*/

????public?void?start()?{
????//?若?server?為空,則解析?server.xml?創(chuàng)建
????????if?(getServer()?==?null)?{
????????????load();
????????}
????//?創(chuàng)建失敗則報錯并退出啟動
????????if?(getServer()?==?null)?{
????????????log.fatal("Cannot?start?server.?Server?instance?is?not?configured.");
????????????return;
????????}

????????//?開始啟動?server
????????try?{
????????????getServer().start();
????????}?catch?(LifecycleException?e)?{
????????????log.fatal(sm.getString("catalina.serverStartFail"),?e);
????????????try?{
????????????????//?異常則執(zhí)行?destroy?銷毀資源
????????????????getServer().destroy();
????????????}?catch?(LifecycleException?e1)?{
????????????????log.debug("destroy()?failed?for?failed?Server?",?e1);
????????????}
????????????return;
????????}

????????//?創(chuàng)建并注冊?JVM?關(guān)閉鉤子
????????if?(useShutdownHook)?{
????????????if?(shutdownHook?==?null)?{
????????????????shutdownHook?=?new?CatalinaShutdownHook();
????????????}
????????????Runtime.getRuntime().addShutdownHook(shutdownHook);
????????}
????//?通過?await?方法監(jiān)聽停止請求
????????if?(await)?{
????????????await();
????????????stop();
????????}
????}

通過「關(guān)閉鉤子」,就是當 JVM 關(guān)閉的時候做一些清理工作,比如說釋放線程池,清理一些零時文件,刷新內(nèi)存數(shù)據(jù)到磁盤中…...

「關(guān)閉鉤子」本質(zhì)就是一個線程,JVM 在停止之前會嘗試執(zhí)行這個線程。我們來看下 CatalinaShutdownHook 這個鉤子到底做了什么。

????/**
?????*?Shutdown?hook?which?will?perform?a?clean?shutdown?of?Catalina?if?needed.
?????*/

????protected?class?CatalinaShutdownHook?extends?Thread?{

????????@Override
????????public?void?run()?{
????????????try?{
????????????????if?(getServer()?!=?null)?{
????????????????????Catalina.this.stop();
????????????????}
????????????}?catch?(Throwable?ex)?{
???????????????...
????????}
????}

????/**
?????*?關(guān)閉已經(jīng)創(chuàng)建的?Server?實例
?????*/

????public?void?stop()?{

????????try?{
????????????//?Remove?the?ShutdownHook?first?so?that?server.stop()
????????????//?doesn't?get?invoked?twice
????????????if?(useShutdownHook)?{
????????????????Runtime.getRuntime().removeShutdownHook(shutdownHook);
????????????}
????????}?catch?(Throwable?t)?{
????????????......
????????}

????????//?關(guān)閉?Server
????????try?{
????????????Server?s?=?getServer();
????????????LifecycleState?state?=?s.getState();
???????????//?判斷是否已經(jīng)關(guān)閉,若是在關(guān)閉中,則不執(zhí)行任何操作
????????????if?(LifecycleState.STOPPING_PREP.compareTo(state)?<=?0
????????????????????&&?LifecycleState.DESTROYED.compareTo(state)?>=?0)?{
????????????????//?Nothing?to?do.?stop()?was?already?called
????????????}?else?{
????????????????s.stop();
????????????????s.destroy();
????????????}
????????}?catch?(LifecycleException?e)?{
????????????log.error("Catalina.stop",?e);
????????}

????}

實際上就是執(zhí)行了 Server 的 stop 方法,Server 的 stop 方法會釋放和清理所有的資源。

Server 組件

來體會下面向接口設(shè)計美,看 Tomcat 如何設(shè)計組件與接口,抽象 Server 組件,Server 組件需要生命周期管理,所以繼承 Lifecycle 實現(xiàn)一鍵啟停。

它的具體實現(xiàn)類是 StandardServer,如下圖所示,我們知道 Lifecycle 主要的方法是組件的 初始化、啟動、停止、銷毀,和 監(jiān)聽器的管理維護,其實就是觀察者模式的設(shè)計,當觸發(fā)不同事件的時候發(fā)布事件給監(jiān)聽器執(zhí)行不同業(yè)務(wù)處理,這里就是如何解耦的設(shè)計哲學(xué)體現(xiàn)。

而 Server 自生則是負責(zé)管理 Service 組件。

接著,我們再看 Server 組件的具體實現(xiàn)類是 StandardServer 有哪些功能,又跟哪些類關(guān)聯(lián)?

StandardServer

在閱讀源碼的過程中,我們一定要多關(guān)注接口與抽象類,接口是組件全局設(shè)計的抽象;而抽象類基本上是模板方法模式的運用,主要目的就是抽象整個算法流程,將變化點交給子類,將不變點實現(xiàn)代碼復(fù)用。

StandardServer 繼承了 LifeCycleBase,它的生命周期被統(tǒng)一管理,并且它的子組件是 Service,因此它還需要管理 Service 的生命周期,也就是說在啟動時調(diào)用 Service 組件的啟動方法,在停止時調(diào)用它們的停止方法。Server 在內(nèi)部維護了若干 Service 組件,它是以數(shù)組來保存的,那 Server 是如何添加一個 Service 到數(shù)組中的呢?

????/**
?????*?添加?Service?到定義的數(shù)組中
?????*
?????*?@param?service?The?Service?to?be?added
?????*/

????@Override
????public?void?addService(Service?service)?{

????????service.setServer(this);

????????synchronized?(servicesLock)?{
???????????//?創(chuàng)建一個?services.length?+?1?長度的?results?數(shù)組
????????????Service?results[]?=?new?Service[services.length?+?1];
???????????//?將老的數(shù)據(jù)復(fù)制到?results?數(shù)組
????????????System.arraycopy(services,?0,?results,?0,?services.length);
????????????results[services.length]?=?service;
????????????services?=?results;
??????//?啟動?Service?組件
????????????if?(getState().isAvailable())?{
????????????????try?{
????????????????????service.start();
????????????????}?catch?(LifecycleException?e)?{
????????????????????//?Ignore
????????????????}
????????????}

????????????//?觀察者模式運用,觸發(fā)監(jiān)聽事件
????????????support.firePropertyChange("service",?null,?service);
????????}

????}

從上面的代碼可以知道,并不是一開始就分配一個很長的數(shù)組,而是在新增過程中動態(tài)拓展長度,這里就是為了節(jié)省空間,對于我們平時開發(fā)是不是也要主要空間復(fù)雜度帶來的內(nèi)存損耗,追求的就是極致的美。

除此之外,還有一個重要功能,上面 Caralina 的啟動方法的最后一行代碼就是調(diào)用了 Server 的 await 方法。

這個方法主要就是監(jiān)聽停止端口,在?await?方法里會創(chuàng)建一個 Socket 監(jiān)聽 8005 端口,并在一個死循環(huán)里接收 Socket 上的連接請求,如果有新的連接到來就建立連接,然后從 Socket 中讀取數(shù)據(jù);如果讀到的數(shù)據(jù)是停止命令“SHUTDOWN”,就退出循環(huán),進入 stop 流程。

Service

同樣是面向接口設(shè)計,Service 組件的具體實現(xiàn)類是 StandardService,Service 組件依然是繼承 Lifecycle 管理生命周期,這里不再累贅展示圖片關(guān)系圖。我們先來看看 Service 接口主要定義的方法以及成員變量。通過接口我們才能知道核心功能,在閱讀源碼的時候一定要多關(guān)注每個接口之間的關(guān)系,不要急著進入實現(xiàn)類。

public?interface?Service?extends?Lifecycle?{

??//?----------主要成員變量

????//Service?組件包含的頂層容器?Engine
????public?Engine?getContainer();

????//?設(shè)置?Service?的?Engine?容器
????public?void?setContainer(Engine?engine);

????//?該?Service?所屬的?Server?組件
????public?Server?getServer();

????//?---------------------------------------------------------?Public?Methods

???//?添加?Service?關(guān)聯(lián)的連接器
????public?void?addConnector(Connector?connector);

????public?Connector[]?findConnectors();

???//?自定義線程池
????public?void?addExecutor(Executor?ex);

???//?主要作用就是根據(jù)?url?定位到?Service,Mapper?的主要作用就是用于定位一個請求所在的組件處理
????Mapper?getMapper();
}

接著再來細看 Service 的實現(xiàn)類:

public?class?StandardService?extends?LifecycleBase?implements?Service?{
????//?名字
????private?String?name?=?null;

????//Server?實例
????private?Server?server?=?null;

????//?連接器數(shù)組
????protected?Connector?connectors[]?=?new?Connector[0];
????private?final?Object?connectorsLock?=?new?Object();

????//?對應(yīng)的?Engine?容器
????private?Engine?engine?=?null;

????//?映射器及其監(jiān)聽器,又是觀察者模式的運用
????protected?final?Mapper?mapper?=?new?Mapper();
????protected?final?MapperListener?mapperListener?=?new?MapperListener(this);
}

StandardService 繼承了 LifecycleBase 抽象類,抽象類定義了 三個 final 模板方法定義生命周期,每個方法將變化點定義抽象方法讓不同組件實現(xiàn)自己的流程。這里也是我們學(xué)習(xí)的地方,利用模板方法抽象變與不變。

此外 StandardService 中還有一些我們熟悉的組件,比如 Server、Connector、Engine 和 Mapper。

那為什么還有一個 MapperListener?這是因為 Tomcat 支持熱部署,當 Web 應(yīng)用的部署發(fā)生變化時,Mapper 中的映射信息也要跟著變化,MapperListener 就是一個監(jiān)聽器,它監(jiān)聽容器的變化,并把信息更新到 Mapper 中,這是典型的觀察者模式。下游服務(wù)根據(jù)多上游服務(wù)的動作做出不同處理,這就是?觀察者模式的運用場景,實現(xiàn)一個事件多個監(jiān)聽器觸發(fā),事件發(fā)布者不用調(diào)用所有下游,而是通過觀察者模式觸發(fā)達到解耦。

Service 管理了 連接器以及 Engine 頂層容器,所以繼續(xù)進入它的 startInternal 方法,其實就是 LifecycleBase 模板定義的 抽象方法??纯此窃趺磫用總€組件順序。

protected?void?startInternal()?throws?LifecycleException?{

????//1.?觸發(fā)啟動監(jiān)聽器
????setState(LifecycleState.STARTING);

????//2. 先啟動 Engine,Engine 會啟動它子容器,因為運用了組合模式,所以每一層容器在會先啟動自己的子容器。
????if?(engine?!=?null)?{
????????synchronized?(engine)?{
????????????engine.start();
????????}
????}

????//3.?再啟動?Mapper?監(jiān)聽器
????mapperListener.start();

????//4.?最后啟動連接器,連接器會啟動它子組件,比如?Endpoint
????synchronized?(connectorsLock)?{
????????for?(Connector?connector:?connectors)?{
????????????if?(connector.getState()?!=?LifecycleState.FAILED)?{
????????????????connector.start();
????????????}
????????}
????}
}

Service 先啟動了 Engine 組件,再啟動 Mapper 監(jiān)聽器,最后才是啟動連接器。這很好理解,因為內(nèi)層組件啟動好了才能對外提供服務(wù),才能啟動外層的連接器組件。而 Mapper 也依賴容器組件,容器組件啟動好了才能監(jiān)聽它們的變化,因此 Mapper 和 MapperListener 在容器組件之后啟動。組件停止的順序跟啟動順序正好相反的,也是基于它們的依賴關(guān)系。

Engine

作為 Container 的頂層組件,所以 Engine 本質(zhì)就是一個容器,繼承了 ContainerBase ,看到抽象類再次運用了模板方法設(shè)計模式。ContainerBase 使用一個?HashMap children = new HashMap<>();?成員變量保存每個組件的子容器。同時使用?protected final Pipeline pipeline = new StandardPipeline(this);?Pipeline 組成一個管道用于處理連接器傳過來的請求,責(zé)任鏈模式構(gòu)建管道。

?public?class?StandardEngine?extends?ContainerBase?implements?Engine?{
?}

Engine 的子容器是 Host,所以 children 保存的就是 Host。

我們來看看 ContainerBase 做了什么...

  • initInternal 定義了容器初始化,同時創(chuàng)建了專門用于啟動停止容器的線程池。
  • startInternal:容器啟動默認實現(xiàn),通過組合模式構(gòu)建容器父子關(guān)系,首先獲取自己的子容器,使用 startStopExecutor 啟動子容器。
public?abstract?class?ContainerBase?extends?LifecycleMBeanBase
????????implements?Container?
{

???//?提供了默認初始化邏輯
????@Override
????protected?void?initInternal()?throws?LifecycleException?{
????????BlockingQueue?startStopQueue?=?new?LinkedBlockingQueue<>();
???????//?創(chuàng)建線程池用于啟動或者停止容器
????????startStopExecutor?=?new?ThreadPoolExecutor(
????????????????getStartStopThreadsInternal(),
????????????????getStartStopThreadsInternal(),?10,?TimeUnit.SECONDS,
????????????????startStopQueue,
????????????????new?StartStopThreadFactory(getName()?+?"-startStop-"));
????????startStopExecutor.allowCoreThreadTimeOut(true);
????????super.initInternal();
????}

??//?容器啟動
????@Override
????protected?synchronized?void?startInternal()?throws?LifecycleException?{

????????//?獲取子容器并提交到線程池啟動
????????Container?children[]?=?findChildren();
????????List>?results?=?new?ArrayList<>();
????????for?(Container?child?:?children)?{
????????????results.add(startStopExecutor.submit(new?StartChild(child)));
????????}
????????MultiThrowable?multiThrowable?=?null;
????????//?獲取啟動結(jié)果
????????for?(Future?result?:?results)?{
????????????try?{
????????????????result.get();
????????????}?catch?(Throwable?e)?{
????????????????log.error(sm.getString("containerBase.threadedStartFailed"),?e);
????????????????if?(multiThrowable?==?null)?{
????????????????????multiThrowable?=?new?MultiThrowable();
????????????????}
????????????????multiThrowable.add(e);
????????????}

????????}
???????......

????????//?啟動?pipeline?管道,用于處理連接器傳遞過來的請求
????????if?(pipeline?instanceof?Lifecycle)?{
????????????((Lifecycle)?pipeline).start();
????????}
?????//?發(fā)布啟動事件
????????setState(LifecycleState.STARTING);
????????//?Start?our?thread
????????threadStart();
????}


}

繼承了 LifecycleMBeanBase 也就是還實現(xiàn)了生命周期的管理,提供了子容器默認的啟動方式,同時提供了對子容器的 CRUD 功能。

Engine 在啟動 Host 容器就是 使用了 ContainerBase 的 startInternal 方法。Engine 自己還做了什么呢?

我們看下 構(gòu)造方法,pipeline 設(shè)置了 setBasic,創(chuàng)建了 StandardEngineValve。

/**
?????*?Create?a?new?StandardEngine?component?with?the?default?basic?Valve.
?????*/

????public?StandardEngine()?{

????????super();
????????pipeline.setBasic(new?StandardEngineValve());
????????.....

????}

容器主要的功能就是處理請求,把請求轉(zhuǎn)發(fā)給某一個 Host 子容器來處理,具體是通過 Valve 來實現(xiàn)的。每個容器組件都有一個 Pipeline 用于組成一個責(zé)任鏈傳遞請求。而 Pipeline 中有一個基礎(chǔ)閥(Basic Valve),而 Engine 容器的基礎(chǔ)閥定義如下:

final?class?StandardEngineValve?extends?ValveBase?{
????@Override
????public?final?void?invoke(Request?request,?Response?response)
????????throws?IOException,?ServletException?
{

????????//?選擇一個合適的?Host?處理請求,通過?Mapper?組件獲取到合適的?Host
????????Host?host?=?request.getHost();
????????if?(host?==?null)?{
????????????response.sendError
????????????????(HttpServletResponse.SC_BAD_REQUEST,
?????????????????sm.getString("standardEngine.noHost",
??????????????????????????????request.getServerName()));
????????????return;
????????}
????????if?(request.isAsyncSupported())?{
????????????request.setAsyncSupported(host.getPipeline().isAsyncSupported());
????????}

????????//?獲取?Host?容器的?Pipeline?first?Valve?,將請求轉(zhuǎn)發(fā)到?Host
????????host.getPipeline().getFirst().invoke(request,?response);
}

這個基礎(chǔ)閥實現(xiàn)非常簡單,就是把請求轉(zhuǎn)發(fā)到 Host 容器。處理請求的 Host 容器對象是從請求中拿到的,請求對象中怎么會有 Host 容器呢?這是因為請求到達 Engine 容器中之前,Mapper 組件已經(jīng)對請求進行了路由處理,Mapper 組件通過請求的 URL 定位了相應(yīng)的容器,并且把容器對象保存到了請求對象中。

組件設(shè)計總結(jié)

大家有沒有發(fā)現(xiàn),Tomcat 的設(shè)計幾乎都是面向接口設(shè)計,也就是通過接口隔離功能設(shè)計其實就是單一職責(zé)的體現(xiàn),每個接口抽象對象不同的組件,通過抽象類定義組件的共同執(zhí)行流程。單一職責(zé)四個字的含義其實就是在這里體現(xiàn)出來了。在分析過程中,我們看到了觀察者模式、模板方法模式、組合模式、責(zé)任鏈模式以及如何抽象組件面向接口設(shè)計的設(shè)計哲學(xué)。

連接器之 I/O 模型與線程池設(shè)計

連接器主要功能就是接受 TCP/IP 連接,限制連接數(shù)然后讀取數(shù)據(jù),最后將請求轉(zhuǎn)發(fā)到?Container?容器。所以這里必然涉及到 I/O 編程,今天帶大家一起分析 Tomcat 如何運用 I/O 模型實現(xiàn)高并發(fā)的,一起進入 I/O 的世界。

I/O 模型主要有 5 種:同步阻塞、同步非阻塞、I/O 多路復(fù)用、信號驅(qū)動、異步 I/O。是不是很熟悉但是又傻傻分不清他們有何區(qū)別?

所謂的I/O 就是計算機內(nèi)存與外部設(shè)備之間拷貝數(shù)據(jù)的過程

CPU 是先把外部設(shè)備的數(shù)據(jù)讀到內(nèi)存里,然后再進行處理。請考慮一下這個場景,當程序通過 CPU 向外部設(shè)備發(fā)出一個讀指令時,數(shù)據(jù)從外部設(shè)備拷貝到內(nèi)存往往需要一段時間,這個時候 CPU 沒事干了,程序是主動把 CPU 讓給別人?還是讓 CPU 不停地查:數(shù)據(jù)到了嗎,數(shù)據(jù)到了嗎……

這就是 I/O 模型要解決的問題。今天我會先說說各種 I/O 模型的區(qū)別,然后重點分析 Tomcat 的 NioEndpoint 組件是如何實現(xiàn)非阻塞 I/O 模型的。

I/O 模型

一個網(wǎng)絡(luò) I/O 通信過程,比如網(wǎng)絡(luò)數(shù)據(jù)讀取,會涉及到兩個對象,分別是調(diào)用這個 I/O 操作的用戶線程和操作系統(tǒng)內(nèi)核。一個進程的地址空間分為用戶空間和內(nèi)核空間,用戶線程不能直接訪問內(nèi)核空間。

網(wǎng)絡(luò)讀取主要有兩個步驟:

  • 用戶線程等待內(nèi)核將數(shù)據(jù)從網(wǎng)卡復(fù)制到內(nèi)核空間。
  • 內(nèi)核將數(shù)據(jù)從內(nèi)核空間復(fù)制到用戶空間。

同理,將數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)也是一樣的流程,將數(shù)據(jù)從用戶線程復(fù)制到內(nèi)核空間,內(nèi)核空間將數(shù)據(jù)復(fù)制到網(wǎng)卡發(fā)送。

不同 I/O 模型的區(qū)別:實現(xiàn)這兩個步驟的方式不一樣。

  • 對于同步,則指的應(yīng)用程序調(diào)用一個方法是否立馬返回,而不需要等待。
  • 對于阻塞與非阻塞:主要就是數(shù)據(jù)從內(nèi)核復(fù)制到用戶空間的讀寫操作是否是阻塞等待的。

同步阻塞 I/O

用戶線程發(fā)起read調(diào)用的時候,線程就阻塞了,只能讓出 CPU,而內(nèi)核則等待網(wǎng)卡數(shù)據(jù)到來,并把數(shù)據(jù)從網(wǎng)卡拷貝到內(nèi)核空間,當內(nèi)核把數(shù)據(jù)拷貝到用戶空間,再把剛剛阻塞的讀取用戶線程喚醒,兩個步驟的線程都是阻塞的。

同步阻塞 I/O

同步非阻塞

用戶線程一直不停的調(diào)用read方法,如果數(shù)據(jù)還沒有復(fù)制到內(nèi)核空間則返回失敗,直到數(shù)據(jù)到達內(nèi)核空間。用戶線程在等待數(shù)據(jù)從內(nèi)核空間復(fù)制到用戶空間的時間里一直是阻塞的,等數(shù)據(jù)到達用戶空間才被喚醒。循環(huán)調(diào)用read方法的時候不阻塞。

同步非阻塞

I/O 多路復(fù)用

用戶線程的讀取操作被劃分為兩步:

  1. 用戶線程先發(fā)起?select?調(diào)用,主要就是詢問內(nèi)核數(shù)據(jù)準備好了沒?當內(nèi)核把數(shù)據(jù)準備好了就執(zhí)行第二步。
  2. 用戶線程再發(fā)起?read?調(diào)用,在等待內(nèi)核把數(shù)據(jù)從內(nèi)核空間復(fù)制到用戶空間的時間里,發(fā)起 read 線程是阻塞的。

為何叫 I/O 多路復(fù)用,核心主要就是:一次?select?調(diào)用可以向內(nèi)核查詢多個**數(shù)據(jù)通道(Channel)**的狀態(tài),因此叫多路復(fù)用。

I/O 多路復(fù)用

異步 I/O

用戶線程執(zhí)行 read 調(diào)用的時候會注冊一個回調(diào)函數(shù), read 調(diào)用立即返回,不會阻塞線程,在等待內(nèi)核將數(shù)據(jù)準備好以后,再調(diào)用剛剛注冊的回調(diào)函數(shù)處理數(shù)據(jù),在整個過程中用戶線程一直沒有阻塞。

異步 I/O

Tomcat NioEndpoint

Tomcat 的 NioEndpoit 組件實際上就是實現(xiàn)了 I/O 多路復(fù)用模型,正是因為這個并發(fā)能力才足夠優(yōu)秀。讓我們一起窺探下 Tomcat NioEndpoint 的設(shè)計原理。

對于 Java 的多路復(fù)用器的使用,無非是兩步:

  1. 創(chuàng)建一個 Seletor,在它身上注冊各種感興趣的事件,然后調(diào)用 select 方法,等待感興趣的事情發(fā)生。

  2. 感興趣的事情發(fā)生了,比如可以讀了,這時便創(chuàng)建一個新的線程從 Channel 中讀數(shù)據(jù)。

Tomcat 的 NioEndpoint 組件雖然實現(xiàn)比較復(fù)雜,但基本原理就是上面兩步。我們先來看看它有哪些組件,它一共包含 LimitLatch、Acceptor、Poller、SocketProcessor 和 Executor 共 5 個組件,它們的工作過程如下圖所示:

NioEndPoint

正是由于使用了 I/O 多路復(fù)用,Poller 內(nèi)部本質(zhì)就是持有 Java Selector 檢測 channel 的 I/O 時間,當數(shù)據(jù)可讀寫的時候創(chuàng)建 SocketProcessor 任務(wù)丟到線程池執(zhí)行,也就是少量線程監(jiān)聽讀寫事件,接著專屬的線程池執(zhí)行讀寫,提高性能。

自定義線程池模型

為了提高處理能力和并發(fā)度, Web 容器通常會把處理請求的工作放在線程池來處理, Tomcat 拓展了 Java 原生的線程池來提升并發(fā)需求,在進入 Tomcat 線程池原理之前,我們先回顧下 Java 線程池原理。

Java 線程池

簡單的說,Java 線程池里內(nèi)部維護一個線程數(shù)組和一個任務(wù)隊列,當任務(wù)處理不過來的時,就把任務(wù)放到隊列里慢慢處理。

ThreadPoolExecutor

來窺探線程池核心類的構(gòu)造函數(shù),我們需要理解每一個參數(shù)的作用,才能理解線程池的工作原理。

????public?ThreadPoolExecutor(int?corePoolSize,
??????????????????????????????int?maximumPoolSize,
??????????????????????????????long?keepAliveTime,
??????????????????????????????TimeUnit?unit,
??????????????????????????????BlockingQueue?workQueue,
??????????????????????????????ThreadFactory?threadFactory,
??????????????????????????????RejectedExecutionHandler?handler)
?
{
????????......
????}
  • corePoolSize:保留在池中的線程數(shù),即使它們空閑,除非設(shè)置了 allowCoreThreadTimeOut,不然不會關(guān)閉。
  • maximumPoolSize:隊列滿后池中允許的最大線程數(shù)。
  • keepAliveTime、TimeUnit:如果線程數(shù)大于核心數(shù),多余的空閑線程的保持的最長時間會被銷毀。unit 是 keepAliveTime 參數(shù)的時間單位。當設(shè)置?allowCoreThreadTimeOut(true)?時,線程池中 corePoolSize 范圍內(nèi)的線程空閑時間達到 keepAliveTime 也將回收。
  • workQueue:當線程數(shù)達到?corePoolSize?后,新增的任務(wù)就放到工作隊列?workQueue?里,而線程池中的線程則努力地從?workQueue?里拉活來干,也就是調(diào)用 poll 方法來獲取任務(wù)。
  • ThreadFactory:創(chuàng)建線程的工廠,比如設(shè)置是否是后臺線程、線程名等。
  • RejectedExecutionHandler:拒絕策略,處理程序因為達到了線程界限和隊列容量執(zhí)行拒絕策略。也可以自定義拒絕策略,只要實現(xiàn)?RejectedExecutionHandler?即可。默認的拒絕策略:AbortPolicy?拒絕任務(wù)并拋出?RejectedExecutionException?異常;CallerRunsPolicy?提交該任務(wù)的線程執(zhí)行;``

來分析下每個參數(shù)之間的關(guān)系:

提交新任務(wù)的時候,如果線程池數(shù) < corePoolSize,則創(chuàng)建新的線程池執(zhí)行任務(wù),當線程數(shù) = corePoolSize 時,新的任務(wù)就會被放到工作隊列 workQueue 中,線程池中的線程盡量從隊列里取任務(wù)來執(zhí)行。

如果任務(wù)很多,workQueue 滿了,且 當前線程數(shù) < maximumPoolSize 時則臨時創(chuàng)建線程執(zhí)行任務(wù),如果總線程數(shù)量超過 maximumPoolSize,則不再創(chuàng)建線程,而是執(zhí)行拒絕策略。DiscardPolicy?什么都不做直接丟棄任務(wù);DiscardOldestPolicy?丟棄最舊的未處理程序;

具體執(zhí)行流程如下圖所示:

線程池執(zhí)行流程

Tomcat 線程池

定制版的 ThreadPoolExecutor,繼承了 java.util.concurrent.ThreadPoolExecutor。對于線程池有兩個很關(guān)鍵的參數(shù):

  • 線程個數(shù)。
  • 隊列長度。

Tomcat 必然需要限定想著兩個參數(shù)不然在高并發(fā)場景下可能導(dǎo)致 CPU 和內(nèi)存有資源耗盡的風(fēng)險。繼承了 與 java.util.concurrent.ThreadPoolExecutor 相同,但實現(xiàn)的效率更高。

其構(gòu)造方法如下,跟 Java 官方的如出一轍

public?ThreadPoolExecutor(int?corePoolSize,?int?maximumPoolSize,?long?keepAliveTime,?TimeUnit?unit,?BlockingQueue?workQueue,?RejectedExecutionHandler?handler)?{
????????super(corePoolSize,?maximumPoolSize,?keepAliveTime,?unit,?workQueue,?handler);
????????prestartAllCoreThreads();
????}

在 Tomcat 中控制線程池的組件是?StandardThreadExecutor?, 也是實現(xiàn)了生命周期接口,下面是啟動線程池的代碼

????@Override
????protected?void?startInternal()?throws?LifecycleException?{
????????//?自定義任務(wù)隊列
????????taskqueue?=?new?TaskQueue(maxQueueSize);
????????//?自定義線程工廠
????????TaskThreadFactory?tf?=?new?TaskThreadFactory(namePrefix,daemon,getThreadPriority());
???????//?創(chuàng)建定制版線程池
????????executor?=?new?ThreadPoolExecutor(getMinSpareThreads(),?getMaxThreads(),?maxIdleTime,?TimeUnit.MILLISECONDS,taskqueue,?tf);
????????executor.setThreadRenewalDelay(threadRenewalDelay);
????????if?(prestartminSpareThreads)?{
????????????executor.prestartAllCoreThreads();
????????}
????????taskqueue.setParent(executor);
????????//?觀察者模式,發(fā)布啟動事件
????????setState(LifecycleState.STARTING);
????}

其中的關(guān)鍵點在于:

  1. Tomcat 有自己的定制版任務(wù)隊列和線程工廠,并且可以限制任務(wù)隊列的長度,它的最大長度是 maxQueueSize。
  2. Tomcat 對線程數(shù)也有限制,設(shè)置了核心線程數(shù)(minSpareThreads)和最大線程池數(shù)(maxThreads)。

除此之外, Tomcat 在官方原有基礎(chǔ)上重新定義了自己的線程池處理流程,原生的處理流程上文已經(jīng)說過。

  • 前 corePoolSize 個任務(wù)時,來一個任務(wù)就創(chuàng)建一個新線程。
  • 還有任務(wù)提交,直接放到隊列,隊列滿了,但是沒有達到最大線程池數(shù)則創(chuàng)建臨時線程救火。
  • 線程總線數(shù)達到 maximumPoolSize ,直接執(zhí)行拒絕策略。

Tomcat 線程池擴展了原生的 ThreadPoolExecutor,通過重寫 execute 方法實現(xiàn)了自己的任務(wù)處理邏輯:

  • 前 corePoolSize 個任務(wù)時,來一個任務(wù)就創(chuàng)建一個新線程。
  • 還有任務(wù)提交,直接放到隊列,隊列滿了,但是沒有達到最大線程池數(shù)則創(chuàng)建臨時線程救火。
  • 線程總線數(shù)達到 maximumPoolSize ,繼續(xù)嘗試把任務(wù)放到隊列中。如果隊列也滿了,插入任務(wù)失敗,才執(zhí)行拒絕策略。

最大的差別在于 Tomcat 在線程總數(shù)達到最大數(shù)時,不是立即執(zhí)行拒絕策略,而是再嘗試向任務(wù)隊列添加任務(wù),添加失敗后再執(zhí)行拒絕策略。

代碼如下所示:

????public?void?execute(Runnable?command,?long?timeout,?TimeUnit?unit)?{
???????//?記錄提交任務(wù)數(shù)?+1
????????submittedCount.incrementAndGet();
????????try?{
????????????//?調(diào)用?java?原生線程池來執(zhí)行任務(wù),當原生拋出拒絕策略
????????????super.execute(command);
????????}?catch?(RejectedExecutionException?rx)?{
??????????//總線程數(shù)達到?maximumPoolSize,Java?原生會執(zhí)行拒絕策略
????????????if?(super.getQueue()?instanceof?TaskQueue)?{
????????????????final?TaskQueue?queue?=?(TaskQueue)super.getQueue();
????????????????try?{
????????????????????//?嘗試把任務(wù)放入隊列中
????????????????????if?(!queue.force(command,?timeout,?unit))?{
????????????????????????submittedCount.decrementAndGet();
??????????????????????//?隊列還是滿的,插入失敗則執(zhí)行拒絕策略
????????????????????????throw?new?RejectedExecutionException("Queue?capacity?is?full.");
????????????????????}
????????????????}?catch?(InterruptedException?x)?{
????????????????????submittedCount.decrementAndGet();
????????????????????throw?new?RejectedExecutionException(x);
????????????????}
????????????}?else?{
??????????????//?提交任務(wù)書?-1
????????????????submittedCount.decrementAndGet();
????????????????throw?rx;
????????????}

????????}
????}

Tomcat 線程池是用 submittedCount 來維護已經(jīng)提交到了線程池,這跟 Tomcat 的定制版的任務(wù)隊列有關(guān)。Tomcat 的任務(wù)隊列 TaskQueue 擴展了 Java 中的 LinkedBlockingQueue,我們知道 LinkedBlockingQueue 默認情況下長度是沒有限制的,除非給它一個 capacity。因此 Tomcat 給了它一個 capacity,TaskQueue 的構(gòu)造函數(shù)中有個整型的參數(shù) capacity,TaskQueue 將 capacity 傳給父類 LinkedBlockingQueue 的構(gòu)造函數(shù),防止無限添加任務(wù)導(dǎo)致內(nèi)存溢出。而且默認是無限制,就會導(dǎo)致當前線程數(shù)達到核心線程數(shù)之后,再來任務(wù)的話線程池會把任務(wù)添加到任務(wù)隊列,并且總是會成功,這樣永遠不會有機會創(chuàng)建新線程了。

為了解決這個問題,TaskQueue 重寫了 LinkedBlockingQueue 的 offer 方法,在合適的時機返回 false,返回 false 表示任務(wù)添加失敗,這時線程池會創(chuàng)建新的線程。

public?class?TaskQueue?extends?LinkedBlockingQueue<Runnable>?{

??...
???@Override
??//?線程池調(diào)用任務(wù)隊列的方法時,當前線程數(shù)肯定已經(jīng)大于核心線程數(shù)了
??public?boolean?offer(Runnable?o)?{

??????//?如果線程數(shù)已經(jīng)到了最大值,不能創(chuàng)建新線程了,只能把任務(wù)添加到任務(wù)隊列。
??????if?(parent.getPoolSize()?==?parent.getMaximumPoolSize())
??????????return?super.offer(o);

??????//?執(zhí)行到這里,表明當前線程數(shù)大于核心線程數(shù),并且小于最大線程數(shù)。
??????//?表明是可以創(chuàng)建新線程的,那到底要不要創(chuàng)建呢?分兩種情況:

??????//1.?如果已提交的任務(wù)數(shù)小于當前線程數(shù),表示還有空閑線程,無需創(chuàng)建新線程
??????if?(parent.getSubmittedCount()<=(parent.getPoolSize()))
??????????return?super.offer(o);

??????//2.?如果已提交的任務(wù)數(shù)大于當前線程數(shù),線程不夠用了,返回?false?去創(chuàng)建新線程
??????if?(parent.getPoolSize()??????????return?false;

??????//?默認情況下總是把任務(wù)添加到任務(wù)隊列
??????return?super.offer(o);
??}

}

只有當前線程數(shù)大于核心線程數(shù)、小于最大線程數(shù),并且已提交的任務(wù)個數(shù)大于當前線程數(shù)時,也就是說線程不夠用了,但是線程數(shù)又沒達到極限,才會去創(chuàng)建新的線程。這就是為什么 Tomcat 需要維護已提交任務(wù)數(shù)這個變量,它的目的就是在任務(wù)隊列的長度無限制的情況下,讓線程池有機會創(chuàng)建新的線程。可以通過設(shè)置 maxQueueSize 參數(shù)來限制任務(wù)隊列的長度。

性能優(yōu)化

線程池調(diào)優(yōu)

跟 I/O 模型緊密相關(guān)的是線程池,線程池的調(diào)優(yōu)就是設(shè)置合理的線程池參數(shù)。我們先來看看 Tomcat 線程池中有哪些關(guān)鍵參數(shù):

參數(shù)詳情
threadPriority線程優(yōu)先級,默認是 5
daemon是否是 后臺線程,默認 true
namePrefix線程名前綴
maxThreads最大線程數(shù),默認 200
minSpareThreads最小線程數(shù)(空閑超過一定時間會被回收),默認 25
maxIdleTime線程最大空閑時間,超過該時間的會被回收,直到只有 minSpareThreads 個。默認是 1 分鐘
maxQueueSize任務(wù)隊列最大長度
prestartAllCoreThreads是否在線程池啟動的時候就創(chuàng)建 minSpareThreads 個線程,默認是 fasle

這里面最核心的就是如何確定 maxThreads 的值,如果這個參數(shù)設(shè)置小了,Tomcat 會發(fā)生線程饑餓,并且請求的處理會在隊列中排隊等待,導(dǎo)致響應(yīng)時間變長;如果 maxThreads 參數(shù)值過大,同樣也會有問題,因為服務(wù)器的 CPU 的核數(shù)有限,線程數(shù)太多會導(dǎo)致線程在 CPU 上來回切換,耗費大量的切換開銷。

線程 I/O 時間與 CPU 時間

至此我們又得到一個線程池個數(shù)的計算公式,假設(shè)服務(wù)器是單核的:

線程池大小 = (線程 I/O 阻塞時間 + 線程 CPU 時間 )/ 線程 CPU 時間

其中:線程 I/O 阻塞時間 + 線程 CPU 時間 = 平均請求處理時間。

Tomcat 內(nèi)存溢出的原因分析及調(diào)優(yōu)

JVM 在拋出 java.lang.OutOfMemoryError 時,除了會打印出一行描述信息,還會打印堆棧跟蹤,因此我們可以通過這些信息來找到導(dǎo)致異常的原因。在尋找原因前,我們先來看看有哪些因素會導(dǎo)致 OutOfMemoryError,其中內(nèi)存泄漏是導(dǎo)致 OutOfMemoryError 的一個比較常見的原因。

其實調(diào)優(yōu)很多時候都是在找系統(tǒng)瓶頸,假如有個狀況:系統(tǒng)響應(yīng)比較慢,但 CPU 的用率不高,內(nèi)存有所增加,通過分析 Heap Dump 發(fā)現(xiàn)大量請求堆積在線程池的隊列中,請問這種情況下應(yīng)該怎么辦呢?可能是請求處理時間太長,去排查是不是訪問數(shù)據(jù)庫或者外部應(yīng)用遇到了延遲。

java.lang.OutOfMemoryError: Java heap space

當 JVM 無法在堆中分配對象的會拋出此異常,一般有以下原因:

  1. 內(nèi)存泄漏:本該回收的對象唄程序一直持有引用導(dǎo)致對象無法被回收,比如在線程池中使用 ThreadLocal、對象池、內(nèi)存池。為了找到內(nèi)存泄漏點,我們通過 jmap 工具生成 Heap Dump,再利用 MAT 分析找到內(nèi)存泄漏點。jmap -dump:live,format=b,file=filename.bin pid

  2. 內(nèi)存不足:我們設(shè)置的堆大小對于應(yīng)用程序來說不夠,修改 JVM 參數(shù)調(diào)整堆大小,比如 -Xms256m -Xmx2048m。

  3. finalize 方法的過度使用。如果我們想在 Java 類實例被 GC 之前執(zhí)行一些邏輯,比如清理對象持有的資源,可以在 Java 類中定義 finalize 方法,這樣 JVM GC 不會立即回收這些對象實例,而是將對象實例添加到一個叫“java.lang.ref.Finalizer.ReferenceQueue”的隊列中,執(zhí)行對象的 finalize 方法,之后才會回收這些對象。Finalizer 線程會和主線程競爭 CPU 資源,但由于優(yōu)先級低,所以處理速度跟不上主線程創(chuàng)建對象的速度,因此 ReferenceQueue 隊列中的對象就越來越多,最終會拋出 OutOfMemoryError。解決辦法是盡量不要給 Java 類定義 finalize 方法。

java.lang.OutOfMemoryError: GC overhead limit exceeded

垃圾收集器持續(xù)運行,但是效率很低幾乎沒有回收內(nèi)存。比如 Java 進程花費超過 96%的 CPU 時間來進行一次 GC,但是回收的內(nèi)存少于 3%的 JVM 堆,并且連續(xù) 5 次 GC 都是這種情況,就會拋出 OutOfMemoryError。

這個問題 IDE 解決方法就是查看 GC 日志或者生成 Heap Dump,先確認是否是內(nèi)存溢出,不是的話可以嘗試增加堆大小??梢酝ㄟ^如下 JVM 啟動參數(shù)打印 GC 日志:

-verbose:gc?//在控制臺輸出GC情況
-XX:+PrintGCDetails??//在控制臺輸出詳細的GC情況
-Xloggc:?filepath??//將GC日志輸出到指定文件中

比如 可以使用?java -verbose:gc -Xloggc:gc.log -XX:+PrintGCDetails -jar xxx.jar?記錄 GC 日志,通過 GCViewer 工具查看 GC 日志,用 GCViewer 打開產(chǎn)生的 gc.log 分析垃圾回收情況。

java.lang.OutOfMemoryError: Requested array size exceeds VM limit

拋出這種異常的原因是“請求的數(shù)組大小超過 JVM 限制”,應(yīng)用程序嘗試分配一個超大的數(shù)組。比如程序嘗試分配 128M 的數(shù)組,但是堆最大 100M,一般這個也是配置問題,有可能 JVM 堆設(shè)置太小,也有可能是程序的 bug,是不是創(chuàng)建了超大數(shù)組。

java.lang.OutOfMemoryError: MetaSpace

JVM 元空間的內(nèi)存在本地內(nèi)存中分配,但是它的大小受參數(shù) MaxMetaSpaceSize 的限制。當元空間大小超過 MaxMetaSpaceSize 時,JVM 將拋出帶有 MetaSpace 字樣的 OutOfMemoryError。解決辦法是加大 MaxMetaSpaceSize 參數(shù)的值。

java.lang.OutOfMemoryError: Request size bytes for reason. Out of swap space

當本地堆內(nèi)存分配失敗或者本地內(nèi)存快要耗盡時,Java HotSpot VM 代碼會拋出這個異常,VM 會觸發(fā)“致命錯誤處理機制”,它會生成“致命錯誤”日志文件,其中包含崩潰時線程、進程和操作系統(tǒng)的有用信息。如果碰到此類型的 OutOfMemoryError,你需要根據(jù) JVM 拋出的錯誤信息來進行診斷;或者使用操作系統(tǒng)提供的 DTrace 工具來跟蹤系統(tǒng)調(diào)用,看看是什么樣的程序代碼在不斷地分配本地內(nèi)存。

java.lang.OutOfMemoryError: Unable to create native threads

  1. Java 程序向 JVM 請求創(chuàng)建一個新的 Java 線程。
  2. JVM 本地代碼(Native Code)代理該請求,通過調(diào)用操作系統(tǒng) API 去創(chuàng)建一個操作系統(tǒng)級別的線程 Native Thread。
  3. 操作系統(tǒng)嘗試創(chuàng)建一個新的 Native Thread,需要同時分配一些內(nèi)存給該線程,每一個 Native Thread 都有一個線程棧,線程棧的大小由 JVM 參數(shù)-Xss決定。
  4. 由于各種原因,操作系統(tǒng)創(chuàng)建新的線程可能會失敗,下面會詳細談到。
  5. JVM 拋出“java.lang.OutOfMemoryError: Unable to create new native thread”錯誤。


總結(jié)

回顧 Tomcat 總結(jié)架構(gòu)設(shè)計,詳細拆解 Tomcat 如何處理高并發(fā)連接設(shè)計。并且分享了如何高效閱讀開源框架源碼思路,設(shè)計模式、并發(fā)編程基礎(chǔ)是重中之重.

點個在看支持我吧,轉(zhuǎn)發(fā)就更好了
瀏覽 82
點贊
評論
收藏
分享

手機掃一掃分享

分享
舉報
評論
圖片
表情
推薦
點贊
評論
收藏
分享

手機掃一掃分享

分享
舉報

感谢您访问我们的网站,您可能还对以下资源感兴趣:

国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频 亚洲精品无码在线观看| 欧美黄片一区二区| 91人妻综合| 日韩黄频| 熟妇无码| 五月天无码av| 欧美性生交18XXXXX无码| 亚洲无码免费网站| 免费做a爰片77777| 久久视频免费观看| 久久中文字幕电影| 亚洲第一色在线| 97伊人超碰| 国产精品久久久999| 国产精品国产精品国产专区不卡 | 无码V | 中文字幕亚洲日韩| 免费三区| 波多野结衣天堂| 淫色五月| 一本大道东京热av无码| 在线亚洲日韩| 九九黄色| 九色在线观看| 日韩av中文在线| 91一级片| 一级a一级a爱片免费免免高潮| 国产精品成人片| 婷婷五月18永久免费视频| 麻豆传媒在线| 亚洲日韩av在线| 欧美日逼视频| 日本黄色电影在线播放| 在线观看国产一区| 久久精品在线视频| 日韩精品一区二区三区在线观看免费| 亚洲激情欧美| 国产无遮挡又黄又爽免费网站| 亚洲人操逼| 青青草乱伦视频| 欧美精品一二三区| 中文字幕66页| 国内自拍偷拍视频| 伊人网在线视频观看| 一区无码免费| 日日操日日摸| 18禁网站在线| 婷婷丁香花| 安微妇搡BBBB搡BBBB| 豆花成人社区,视频| 国产高清精品在线| 国产成人精品久久久| 中文字幕一区二区三区四区| 日本免费在线观看视频| 麻豆性爱视频| 色色网站免费| 91成人在线电影| 69成人网站| 日韩AⅤ视频| 99热这里精品| 午夜艹| 69欧美视频| 国产在线成人| 亚洲欧美日韩免费| 一级欧美一级日韩片| 午夜精品久久久久久不卡8050| 欧美日韩一区二区三区四区| 欧美一卡二卡三卡| 在线观看欧美黄片| 色色看片| 91香蕉在线看| 婷婷色在线视频| 超碰护士| 中文解说AⅤ水果派| 一区二区免费| 俺来俺也去www色在线观看| 黄色小电影在线观看| 久久久久久久久黄色| 中文字幕乱码中文字乱码影响大吗| 人人爱人人插高清| 日韩欧美一级视频| 天堂资源地址在线| 精品人妻一区二区三区在线视频不卡 | 国产亚洲午夜久久久成人电影 | 亚洲人内射片又| 午夜在线观看视频| 国产三级在线观看| 人妻熟女在线| 黄色A片约| H片免费在线观看| 亚洲成人自拍| 日本男人天堂| 福利一区二区视频网| ww免费视频| 亚洲777| 午夜综合在线| 男女啪啪网站| 小h片| 婷婷色色婷婷五月天| 97视频国产| 久久成人电影| 午夜操逼网| 思思热思思操免费视频| 欧美日韩在线视频观看| 国产在线观看黄| 水蜜桃网| 最新亚洲无码在线观看| 91嫩草久久久天美传媒| 免费观看一级毛一片| 中文字幕有码在线| 无码精品成人观看A片| 黄色内射在线播放| 成年女人免费视频| 中文无码网站| 亚洲精品视频在线播放| a三级片| 日韩乱伦网站| 精品无码一区二区三区四区久久久软件 | 中文字幕牛牛婷婷| 99在线精品视频| 色噜噜人妻丝袜无码影院| 操逼视频,黄色大全| 亚洲日韩欧美在线观看| 欧美视频A| 日韩在线精品| 成人大香蕉视频| 丁香六月婷婷综合| 成人欧美在线观看| 摸BBB槡BBBB搡BBB,,,,,| 狠狠躁夜夜躁人爽| 亚洲天堂一区在线观看| 中文字幕欧美日韩| 91精品在线免费观看| 亚洲欧洲在线播放| 久久无码精品| 三级无码片| 亚洲最大三级片| 在线AⅤ| P站免费版-永久免费的福利视频平台| 大香蕉啪啪| 漂亮人妻吃鸡啪啪哥哥真的好| 另类老妇极品BBWBBw| 亚洲精品国产精品乱码视99| 最新中文字幕视频| www.99视频| 又黄又爽无遮挡| 亚洲一区黄色| 久久91| 久久久久99精品成人网站| 国产精品国内自产拍| 欧洲黑种人日P视频| 西西人体44www大胆无码| 一本到在线观看午夜剧场| 人人看人人爱| 粉嫩一区二区三区四区| www男人天堂| 少妇熟女视频| 成年人观看视频| 自拍偷拍亚洲无码| 北条麻妃无码在线| 无码一区二区三区在线观看| 99性爱网| 黄色国产在线观看| 一区二区三区精品无码| 水果派成人播放无码| av资源免费观看| 精品尤物在线| 欧美性爱中文字幕| 99色在线视频| www91久久| 国产一区二| 四川少妇搡bbw搡bbbb| 亚洲精品一区二区二区的游戏情况 | 大香蕉精品视频| 国产精品久久久久久久9999 | 久久精品国产AV| 91亚洲电影| 黄色免费av| 日本伊人在线综合视频| 台湾成人视频| 四季AV之日韩人妻无码| 欧美作爱| 日本www色| 久草精品视频| 亚洲阿v天堂| 小泽玛利亚一区二区免费| 操屄视频在线观看| 大香蕉三级| 激情五月天亚洲| 欧美日韩中文字幕| 亚洲第一页在线观看| 麻豆传媒一区二区| 最新亚洲中文字幕| 老师机性爱视频在线播放| 欧美色色色色色色| 欧美日韩色| 91porn国产| 人妻精品一二三| 操逼视频免费在线观看| 成人在线不卡| 亚洲精品日日夜夜| 国产91在线视频| 亚洲精品久久久久avwww潮水| 日本在线不卡一区| 国产高清无码在线| 欧美三级美国一级| 久久毛片基地| 做爱激情视频网站| 无码中文字幕网站| 在线视频一区二区三区四区| 日韩第三页| 国内毛片毛片毛片毛片毛片毛片毛片毛片毛片毛片毛片毛片 | 欧美老女人操逼群| 黄色小视频免费看| 一级黄色电影免费观看| 草草影院第一页| 熟女视频网| 亚洲国产女人| 日逼黄片| 欧美三级片网址| 91在线无码精品秘入口国战| 精品码产区一区二亚洲国产| 中文资源在线√8| 亚洲中文无码第一页| 日韩在线视频播放| 日韩精品91| 久久久久性| 日韩欧美成人片| 精品国产一级A片黄毛网站| 日欧美美女逼| 精品福利在线| 操逼网站大全| 国产毛片在线视频| 黄色视频网站在线看| 91亚洲国产成人久久精品网站| AV婷婷在线| 亚洲国产成人精品综合99| 亚洲狼人| AV高清无码| 欧美成人猛片AAAAAAA| 黄片免费在线播放| 成人视频一区| 伊人影院久久| 大香蕉网伊人在线| 性生活无码视频| 中文字幕亚洲专区| 影音先锋AV天堂| 人人爱人人操人人爽| 日韩大黄| 国产操比网| 日本一级A片| 婷婷五月丁香六月| 五月丁香六月婷| 久久中文视频| 中文字幕日韩在线视频| 蜜桃av在线| 无码成人av| 日本国产精品| 国产免费自拍视频| 亚洲美女一区| 91视频专区| 国产婷婷色| 各国熟女HD合集| 欧美一级AA| 围内精品久久久久久久久久‘变脸 | 韩国无码视频在线观看| 成人天堂一区二区三区| 一区免费在线观看| 日本中文字幕精品| 亚洲无码高清免费| 欧美日韩美女| 成人免费视频网站| 国产av二区| 欧美一级免费A片| 91爱搞在线| 三级A片视频| 免费的一级片| 成人伊人综合网| 亚洲社区在线观看| 日韩美女免费视频| 精品人妻一区二区三区在线视频不卡 | 成人AV在线资源| 日韩成人免费在线观看| 国产成人大香蕉| 我和岳m愉情XXXⅩ视频| 韩国精品在线观看| 婷婷成人综合| 国产色色色色| 中文字幕日韩人妻在线| 四虎无码| 久久久影院| 欧美一区| 黄色av影院| 蜜臀99| 亚洲va欧美ⅴa在线| 久草大香蕉在线| 成人精品一区二区无码| 久久久激情| 亚洲男人的天堂av| 亚洲欧美影院| 亚洲精品乱码久久久久久| 91在线无码精品秘入口国战| 欧美成人激情视频| 色呦呦中文字幕| 亚洲免费成人| 熟女探花| 欧美一在线一综合| 国产无遮挡又黄又爽在线观看| 日本成人免费电影| 人人操天天干| 亚洲天堂成人在线| 蜜臀av在线播放| 逼特逼视频在线| 人人综合| 特级西西人体WWWww| 先锋AV资源| 先锋影音一区二区三区| 成人a电影| 日韩一区二区三区无码电影 | 91一区在线观看| 国产无码操逼视频| 成人性爱视频网站| 色情片在线观看| 天天插天天射| 1024在线视频| 国产在线欧美在线白浆| 久草视频新| 韩国无码高清视频| 爱爱免费看片| 国产亚洲精品码| 嫩BBB搡BBB槡BBB小号| 日韩无码国产精品| 懂色在线精品分类视频| 天堂av中文字幕| 99精品视频网站| 成年人黄色视频在线观看| 国产成人无码一区二区| 国产传媒在线观看| 国内成人精品网站| 青草久久视频| 久久久大香蕉| 欧美国产日韩综合在线观看170| 精品无码一区二区三区四区久久久软件| 做aAAAAA免费视频| 免费av观看| 上床视频网站| 亚洲AV无码成人精品区大猫| 亚洲无码A片在线观看| av无码在线观看| 日本黄在线播放| 国产av综合网| 国产h在线| 欧美日韩高清无码| 大香蕉国产在线| 久久系列观看完整指南| 久久大香蕉91| 欧美性性生交XXXXX无码| 先锋影音资源av| 男女日日批黄色三级| 一级A片久久久免费直播间| 99久久精品国产成人一区二区 | 色色网站免费| 人人摸人人爱人人操| 亚洲成色A片77777在线小说| 操逼五月天| gay成人在线观看| 久久视频这里有精品| 91人妻人人澡人人爽人妻| 乱子伦】国产精品| 无码专区在线看v| 国产黄片在线视频| 日韩中文字幕高清| 狠狠色婷婷7777| 日韩一区欧美| 一区二区三区日本| 国产免费黄色片| 东方美美高清无码一区| 精品尤物在线| 中文无码一区二区三区| 国产日批| 黄色片网站视频| 国产成人无码精品久在线观看| 亚洲v在线| 微拍福利一区二区| 国产在线观看91| 亚洲AV无码电影| 蜜桃免费AV| 青娱乐在线精品| 亚洲av图片| 中文字幕乱码亚洲无线码在线日噜噜| 亚洲少妇网| a欧美| 性BBwBBwBBwBBw禽| 亚洲视频99| 日韩爱爱网站| 日逼视频免费看| 黄色大片网址| 国产亚洲Av| 欧美一级黄色性爱视频| 黄色在线不卡| 人人人人操| 精品乱子伦一区二区三区毛| 天天成人| 99无码视频| 内射学生妹J亅| 久久久久久综合| 国产日韩欧美在线| 天天操夜夜干| 成人在线视频网| 国产特黄级AAAAA片免| sm在线| 91夫妻交友视频| 国产精品久久毛片| 西西人体WW大胆无码| 亚洲天堂久久久| 日韩毛片中文字幕| 欧美大香蕉伊人网| 九九综合伊人7777777| 免费看一区二区三区A片| 欧美性受XXXX黑人XYX性爽一 | 中日韩在线视频| 大香蕉伊人成人| 一级操逼毛片| 熊猫视频91| 国产精品黄色片| 欧美干| 成人精品免费无码毛片| 美女天天操| 边添小泬边狠狠躁视频| 69成人精品视频| 亚洲精品一区二区三区无码电影| 国产免费小视频| 久久国产一区二区| 男女日比视频| 一级片在线免费看| 乱子伦】国产精品| 影音先锋久久久久AV综合网成人| 草逼小视频| 免费观看成人毛片A片直播千姿| 黄色视频在线观看国产| 亚洲va欧美va天堂v国产综合| 青草青在线视频| 88av在线观看| 成人无码交配视频国产网站| 中国熟女视频| 91人人妻人人澡| 黄色一级在线| AV中文字幕在线播放| 久热只有精品| 亚洲日韩av在线| 天堂无码高清| 国产一级二级三级| 三级黄色免费网站| 亚洲无码二区| 青青草精品在线视频| 中文字幕av高清片,中文在线观看| 国产av三级片| 在线看片AV| 91成人精品一区二区| 久久视频网站| 四虎黄色| 久草免费福利| 丁香五香天堂网| aaa在线观看| 在线观看国产| 午夜福利视频91| 福利黄色片:片| 91久久久久久久| 先锋影音资源一区| 人成视频在线观看| 国产无套进入免费| 插逼视频国产| 国产黄色在线免费观看| 中文字幕一本道| 黄色国产视频| 无码区一区二区三区| 男人的天堂婷婷| 91成人电影在线观看| 日韩WWW| 国产乱伦熟女| 91人人妻人人澡人人爽人人| 色婷婷香蕉| 国产精品揄拍一区二区| 亚洲成人视频免费在线观看| a三级片| 人人操人人插| 特黄色视频| 久久亚洲Aⅴ成人无码国产丝袜 | 成人久久电影| a毛片| 亚洲国产精| 婷婷一区二区| 蜜桃91精品入口| 中文字幕第一页亚洲| 天天做天天爱| 91在线综合| 人人操人人妻人人| 18XXX亚洲HD护士JD| 日韩欧美精品| 女人18片毛片60分钟翻译| 爱操AV| 麻豆视频一区| 操极品少妇逼| 精品欧美片在线观看步骤| 九九热精品视频在线观看| 一级特黄妇女高潮AA片免费播放 | 国产区一区| 噜噜| 美女黄网站| 日韩在线精品| 国产videos| 亚洲无码在线高清| 91乱子伦国产乱子伦| 翔田千里AV| 国产欧美日韩一区| 亚卅毛片| 免费无码国产在线观看快色| 日韩精品在线观看免费| 二区视频在线| 亚洲男女av| 成人爱爱视频| 高清无码做爱视频| 成人毛片18| 西西午夜视频| 亚洲无码免费视频| 熟妇槡BBBB槡BBBB| 欧美操人| 竹菊传媒一区二区三区| 欧美三级在线观看视频| 无码乱伦| 成人做爰黄AAA片免费直播岛国| 欧美熟妇BBB搡BBB| 欧美一区二区三区在线播放| 日本三级片网站在线观看| 特黄毛片| 无码操B| 日韩三级网| 羞羞涩漫无码免费网站入口| 99成人乱码一区二区三区在线 | 综合站欧美精品| 免费毛片基地| 激情五月天影院| 91视频网站在线| 97久久精品| 东北奇淫老老妇| 在线观看免费欧美操逼视频| 人人看人人摸人人| 国产亲子乱XXXXinin| 国产精品揄拍100视频| 美女久久久久| 扒开让我91看片在线看| av无码导航| 人妻性爱| 国产一区二区三区18| 国产一级无码| 国产jk在线观看| 国产免费a| av青青草| 91男女| 久久一二三四| 老熟女伦一区二区三区| 午夜丁香婷婷| 青草视屏| 亚欧美日韩| 色狠狠干| 92午夜福利天堂视频2019| 亚洲日韩精品中文字幕在线| 中文字幕性爱| 西西444WWW无码大胆| 欧美成人精品激情在线视频| 国产一级二级视频| 欧美三级网站在线观看| 尿在小sao货里面好不好| 国产一级AV免费观看| 人成在线视频| 蜜桃免费| 九久久| 特级西西WWW无码| 少妇搡BBBB搡BBB搡18禁| 欧美日韩一级黄片| 日本黄色视频。| 亚洲七区| 波多野结衣福利视频| 在线看的av| 黄色成年人视频在线观看| 国产一毛a一毛a在线观看| 人妻三级| 在线天堂v| 黄色视频免费观看| 吴梦梦| 思思操在线视频| 国模无码在线| 97精品人人A片免费看| 中文字幕亚洲人妻| 亚洲日韩在线视频观看| 熟女视频网| 久草大香蕉视频| 国产成人精品无码片子的价格| 操极品少妇逼| 无码av免费| 黄片中文字幕| 欧美一级AA| 日韩人妻斩| 亚洲福利网站| 亚洲成人综合在线| av资源在线播放| 国产XXXXX| 人妻视频网站| 第一页在线| 免费18蜜桃久久19| 日韩操大屌| 天天操大香蕉| 久久久久无码国产精品一区| 亚洲第一黄色视频| 人妻无码一区二区三区| 日本欧美在线播放中文| 天堂а√在线中文在线新版| 欧美天堂在线观看| 国产成人精品一区二区三区在线 | 亚洲国产精品视频| 亚洲天堂无码| 人妻少妇中文字幕久久牛牛| 亚洲欧洲久久| 欧美AAA黄片| 亚洲精品国产精品国自产| 神马午夜视频| 欧美激情综合网| eeuss一区| www.超碰| 久久久亚洲AV无码精品色午夜| 久草大香蕉在线视频| 中文字幕+乱码+中文乱码91在线观看 | 欧美老女人操逼群| 青青草在线观看免费| 午夜视频网站| 亲子伦视频一区二区三区| 91人妻人人人人爽| 偷拍综合| 午夜视频在线| 骚BBBB槡BBB槡BBB| 国产精品av在线播放| 亚洲区欧美区| 日韩乱伦电影| 水蜜桃一区二区| 国产18女人水真多免费看| 熟女伦乱| 学生妹毛片视频| 日本在线小视频| 国产乱子伦真实精品| 好吊视频一区二区三区红桃视频you | 伊人久久久久久久久久久| 可以免费看av的网站| 日韩高清无码人妻| AV无码高清| 精品国产乱子伦一区二区三区,小小扐 | 伊人逼逼| 国产在线精品观看| 成人做爰A片一区二区app| 久久青留社区金玉| 超碰人妻人人操| 国产精品AV在线观看| 六月激情婷婷| 日韩精品欧美一区二区三区| 亚洲一卡| 国产视频激情| 亚洲无码视频一区| 黄色视频在线观看亚洲一区二区三区免费 | 日韩小电影免费观看高清完整版在线观| 天天激情| 欧美性大香蕉| 强伦轩一区二区三区在线观看| 免费黄色视频网址| 婷婷五月在线视频| 樱桃码一区二区三区| 操B影院| av片在线免费观看| 欧美日逼视频| 午夜传媒一区二区三区| 人人摸人人插| 天天日天天草| 五月丁香激情四射| 成年人在线观看视频网站| 亚洲一级黄| 开心色播五月天| 88AV在线视频| 黄色av天堂| 成人A视频| 日韩免费中文字幕A片| 日韩免费高清无码| 黄色一级在线| 麻豆精品一区二区| 俺去日| 国产欧美精品一区二区三区| 国产香蕉精品视频| 露脸丨91丨九色露脸| 日本色影院| 成人无码久久| 大香蕉尹人网| 黄色视频大全在线观看| 国产毛片网| 日韩午夜欧美精品一二三区| 国产精品扒开腿做爽爽爽视频| 人人插人人干| 国产精品51麻豆cm传媒| 成人高清无码在线观看| 综合一区二区| wwwxx国产| 国产AⅤ爽aV久久久久成人| 97操逼网| 伊人综合视频| 久久6| 欧美日韩婷婷| 无码精品人妻一区二区三刘亦菲| 日韩中文字幕永久| 日本综合色| 日韩中文字幕在线观看| 免费伊人大香蕉| 日韩精品人妻中文字幕| 中文色片| 人人色在线| 在线免费人成视频| 黄色a片视频| 无码午夜| 2018中文字幕第一页| 日韩免费视频一区二区| 亚洲午夜影院在线| 免费看成人A片无码照片88hⅤ| 亚洲av不卡| 国产第8页| 成人一级片| 成人在线综合| 欧美精品久久久| 日韩美女久久| 青青国产| 五月激情丁香婷婷| 国产成人在线免费观看| 亚洲无码av电影| 亚洲成人网站在线| 偷拍视频图片综合网| 操逼视频在线播放| 国产高清AV在线| 亚洲欧美在线观看视频| 一本久道视频一本久道| 大香伊人久久| 思思热免费视频| 91亚洲精品视频| 人人妻人人上| 欧美日韩精品久久久免费观看| 国产一级乱伦| 婷婷无码视频| 免费观看黄片视频| 国产秘久久一区二区| 草草影院CCYYCOM屁屁影院合集限制影院 | 日韩在线网址| 91豆花视频18| 国产精品码一本A片| 色呦呦一欧美| 天天操网址| 日本成人A| 在线免费观看黄色小视频| 亚洲激情欧美激情| 亚洲精品视频无码| 91人妻人人澡人人添人人爽| 中文字幕视频一区| 国产黄色视频在线看| 亚洲无码久久飞鱼网站| 天天天天日| 黄色网页免费观看| 天天干天天拍| 免费视频在线观看一区| 欧洲三级片| 特级西西444www大精品| 69av视频在线观看| 日韩欧美精品在线观看| 黄色国产在线| 在线成人网站| а√在线中文8| 大香蕉伊人丁香五月| 国产成人精| 江苏妇搡BBBB搡BBBB-百度| 日本A视频| 亚洲黄色免费| 五月天深爱激情网| 欧美一卡二卡三卡| 理论片无码| 日屄免费视频| 久久中文视频| 国产女人18毛片水真多成人如厕| 国产在线欧美在线| 影音先锋日韩精品| 天天草天天草| 一线天嫩穴少妇| 日本二区三区| 日韩二三区| 日韩国产欧美精品一区| 18禁一区| 91一级片| 成人免费Av| 亚洲综合免费观看高清完整版 | 国产精品视频网站| 天天拍天天日| 在线免费看黄片| 奇米97| 人人摸人人操人人看| 国产精品免费在线| 亚洲欧美激情小说| 久久五月亭亭| 欧美91熟| 91黄色电影| 亚洲AV成人无码AV小说| 成人三级视频在线| 成人久久久| 中文字幕日韩av| 久久久久久久伊人| 青青草大香蕉在线| 成人激情视频在线观看| A片在线观看免费| 国产免费久久久| 99色播| 99re视频在线| 亚洲国产黄色视频| 日韩在线中文字幕| 91香蕉在线观看视频在线播放| 亚洲精品资源| 欧美日韩黄色片| 黄色大片在线播放| 日本四级片| 五月天婷婷丁香| www.黄色片| 免费内射网站| 91AV天天在线观看| 日本操B久久| 亚洲成人三级片| 在线观看免费国产| 午夜8050| 影音先锋女人aV鲁色资源网站 | 无码视频在线免费播放| 亚洲超级高清无码第一在线视频观看 | 亚洲无码在线高清| 99久热| 日本不卡一区二区三区| 久久成人电影院| 精品码产区一区二亚洲国产| 97精品无码| 超碰激情| 免费国产视频| 操逼五月天| 久久婷婷综合网| 超碰日逼| 三须三级久久三级久久18| 中文字幕乱码亚洲无线码按摩| 国产久久久久| 国产又爽又黄A片| 久久久久久久成人| 日韩啪啪啪网站| 久久国产热| 欧美性一区| 97人妻碰碰中文无码久热丝袜| 国产无码久久| 一级黄色片在线观看| 日本大香蕉伊人| 国产不卡一| 免费看黄色的网站| 91在线无码精品秘软件| AV影院在线| 国产性爱网站| 无码免费观看视频| 国产视频无码| 婷婷久久亚洲| 中文字幕一区在线| 在线视频一区二区三区四区| 天天爽夜夜爽夜夜爽精品| 色婷婷综合视频| 国内精品人妻无码久久久影院蜜桃 | 青娱乐成人在线视频| 亚洲无码A片在线| 午夜AV大片| 色tv在线| 国产综合久久久777777| 蜜桃久久99精品久久久酒店| 久操中文| 操逼观看| 国产精品偷拍| 亚洲激情四射| 久久国产激情| 亚洲精品人人| 欧美在线大香蕉| 亚洲高清成人| 日本69视频| 四房五月婷婷| 91原创国产内射| 特级西西人体www高清大胆| 欧美色精品| 女人18片毛片60分钟翻译| 16一17女人毛片| 日本一区二区精品| 色婷婷Av一区| 亚洲中文字幕无码在线观看 | 性少妇| 国产又粗又大|