### Tomcat類加載器層次
Tomcat 作為`Servlet`容器,它負責加載我們的`Servlet`類,此外它還負責加載`Servlet`所依賴的 JAR 包。并且`Tomcat`本身也是也是一個 Java 程序,因此它需要加載自己的類和依賴的 JAR 包。首先讓我們思考這一下這幾個問題:
1. 假如我們在 Tomcat 中運行了兩個 Web 應用程序,兩個 Web 應用中有同名的`Servlet`,但是功能不同,Tomcat 需要同時加載和管理這兩個同名的`Servlet`類,保證它們不會沖突,因此 Web 應用之間的類需要隔離。
2. 假如兩個 Web 應用都依賴同一個第三方的 JAR 包,比如`Spring`,那`Spring`的 JAR 包被加載到內存后,`Tomcat`要保證這兩個 Web 應用能夠共享,也就是說`Spring`的 JAR 包只被加載一次,否則隨著依賴的第三方 JAR 包增多,`JVM`的內存會膨脹。
3. 跟 JVM 一樣,我們需要隔離 Tomcat 本身的類和 Web 應用的類

具體介紹下:
* **CommonClassLoader**:以應用類加載器為父類,是tomcat頂層的公用類加載器,其路徑由conf/catalina.properties中的common.loader指定,默認指向${catalina.home}/lib下的包。
* **CatalinaClassloader**:以Common類加載器為父類,是用于加載Tomcat應用服務器的類加載器,其路徑由server.loader指定,默認為空,此時tomcat使用Common類加載器加載應用服務器。
* **SharedClassLoader**:以Common類加載器為父類,是所有Web應用的父類加載器,其路徑由shared.loader指定,默認為空,此時tomcat使用Common類加載器作為Web應用的父加載器。本質需求是兩個 Web 應用之間怎么共享庫類,并且不能重復加載相同的類。在雙親委托機制里,各個子加載器都可以通過父加載器去加載類,那么把需要共享的類放到父加載器的加載路徑下不就行了嗎。
因此 Tomcat 的設計者又加了一個類加載器`SharedClassLoader`,作為`WebAppClassLoader`的父加載器,專門來加載 Web 應用之間共享的類。如果`WebAppClassLoader`自己沒有加載到某個類,就會委托父加載器`SharedClassLoader`去加載這個類,`SharedClassLoader`會在指定目錄下加載共享類,之后返回給`WebAppClassLoader`,例如多個web應用都依賴的spring就可以使用SharedClassLoader進行加載;
* **WebAppClassLoader**:以Shared類加載器為父類,加載/WEB-INF/classes目錄下的未壓縮的Class和資源文件以及/WEB-INF/lib目錄下的jar包,該類加載器只對當前Web應用可見,對其他Web應用均不可見;Tomcat 的解決方案是自定義一個類加載器`WebAppClassLoader`, 并且給每個 Web 應用創建一個類加載器實例。我們知道,Context 容器組件對應一個 Web 應用,因此,每個`Context`容器負責創建和維護一個`WebAppClassLoader`加載器實例。這背后的原理是,**不同的加載器實例加載的類被認為是不同的類**,即使它們的類名相同。這就相當于在 Java 虛擬機內部創建了一個個相互隔離的 Java 類空間,每一個 Web 應用都有自己的類空間,Web 應用之間通過各自的類加載器互相隔離。
#### 如何隔離 Tomcat 本身的類和 Web 應用的類?
要共享可以通過父子關系,要隔離那就需要兄弟關系了。兄弟關系就是指兩個類加載器是平行的,它們可能擁有同一個父加載器,基于此 Tomcat 又設計一個類加載器`CatalinaClassloader`,專門來加載 Tomcat 自身的類。
這樣設計有個問題,那 Tomcat 和各 Web 應用之間需要共享一些類時該怎么辦呢?
老辦法,還是再增加一個`CommonClassLoader`,作為`CatalinaClassloader`和`SharedClassLoader`的父加載器。`CommonClassLoader`能加載的類都可以被`CatalinaClassLoader`和`SharedClassLoader`使用
*****
默認情況下,Common、Catalina、Shared類加載器是同一個,但可以配置3個不同的類加載器,使他們各司其職
> Common類加載器復雜加載Tomcat應用服務器內部和Web應用均可見的類,如Servlet規范相關包和一些通用工具包。
> Catalina類加載器負責只有Tomcat應用服務器內部可見的類,這些類對Web應用不可見。比如,想實現自己的會話存儲方案,而且該方案依賴了一些第三方包,當然是不希望這些包對Web應用可見,這時可以配置server.load,創建獨立的Catalina類加載器。
> Shared類復雜加載Web應用共享類,這些類tomcat服務器不會依賴
*****
Q:如果有10個Web應用程序都是用Spring來進行組織和管理的話,可以把Spring放到Common或Shared目錄下讓這些程序共享。Spring要對用戶程序的類進行管理,自然要能訪問到用戶程序的類,而用戶的程序顯然是放在/WebApp/WEB-INF目錄中的,那么被CommonClassLoader或SharedClassLoader加載的Spring如何訪問并不在其加載范圍內的用戶程序呢?
如果按主流的雙親委派機制,顯然無法做到讓父類加載器加載的類去訪問子類加載器加載的類,但使用線程上下文加載器,可以讓父類加載器請求子類加載器去完成類加載的動作。spring加載類所用的Classloader是通過Thread.currentThread().getContextClassLoader()來獲取的,而當線程創建時會默認setContextClassLoader(AppClassLoader),即線程上下文類加載器被設置為AppClassLoader,spring中始終可以獲取到這個AppClassLoader(在Tomcat里就是WebAppClassLoader)子類加載器來加載bean,以后任何一個線程都可以通過getContextClassLoader()獲取到WebAppClassLoader來getbean了
- 概述
- CAP理論
- BASE理論
- ACID
- 分布式系統相關技術
- 主流數據庫連接池
- 基礎
- 系統單點
- 負載均衡
- HTTP重定向負載均衡
- DNS域名解析負載均衡
- 反向代理負載均衡
- IP負載均衡
- 數據鏈路層負載均衡
- 負載均衡算法
- 輪詢法(Round Robin)
- 加權輪詢(Weight Round Robin)
- 隨機算法(Random)
- 源地址Hash算法
- 加權隨機法(Weight Random)
- 最小連接數法(Least Connections)
- 接入層負載均衡
- 軟件架構
- 性能
- 性能測試指標
- 響應時間
- 并發數
- 吞吐量
- 性能計數器
- 性能測試方法
- 性能測試報告
- 性能優化
- Web前端性能優化
- 應用服務器性能優化
- 可用性
- 服務降級
- 伸縮性
- 擴展性
- 事件驅動架構
- 安全性
- 信息加密技術
- 分布式系統概述
- 自動化
- 分布式唯一ID
- 冪等設計
- 分布式鎖
- 腦裂
- 一致性原理
- Paxos
- Zab
- Raft
- 分布式遠程服務調用
- RMI
- Spring RMI
- WebService
- SOA服務架構
- 微服務架構
- 微服務的九大特性
- 服務注冊和發現
- 解決方案及組件
- 分布式網關
- 注冊中心
- Zookeeper
- ZNode
- Watch接口
- 持久節點-配置中心實現原理
- 臨時節點-注冊中心
- Zookeeper選舉
- Zookeeper角色
- ZooKeeper工作原理
- 選主流程
- 同步流程
- Leader工作流程
- Follower工作流程
- 常見限流算法
- 計數器算法
- 漏桶算法
- 令牌桶算法
- 滑動窗口
- 計數器&滑動窗口
- 斷路器
- 大流量高并發高可用
- 高可用
- 高并發/大流量
- 分布式緩存系統
- 基本概念
- 緩存命中率
- 緩存最大元素
- 緩存回收策略
- 回收算法
- 緩存穿透與緩存雪崩
- CDN緩存
- 緩存分類
- memcached
- 客戶端路由原理
- 內存管理機制
- Redis
- Redis數據模型
- redisObject/Redis type/Redis encoding
- 命令的類型檢查和多態
- skiplist跳躍表
- 為什么使用跳躍表
- redis-內存管理機制
- Redis淘汰策略
- Redis持久化策略
- Redis并發競爭
- redis主從復制
- Redis集群實現方案
- Redis Cluster
- redis事務
- Redis-Sentinel
- Redis適用場景
- Redis客戶端
- redis rehash原理
- dict數據結構
- 觸發rehash的條件
- 漸進式rehash
- 漸進式rehash過程
- Redis多線程版本
- 緩存實際應用
- 堆緩存-Guava Cache
- 主要參數
- Caffeine
- Spring注解緩存
- 分布式存儲
- Database
- AUTOCOMMIT
- 臟讀&幻讀&不可重復讀
- 子查詢
- 連接
- 內連接
- 自連接
- 自然連接
- 外連接
- 組合查詢
- 隔離級別
- 數據庫范式
- 索引實現機制
- 數據庫拆分
- 表分區
- 分庫
- 分表
- MySQL
- MySQL基礎架構
- 鎖分類
- 排它鎖&獨占鎖
- 共享鎖
- 間隙鎖
- 表級鎖
- 存儲引擎
- 磁盤IO
- 磁盤結構圖
- 磁盤數據讀寫原理
- MySQL索引原理
- B+樹索引
- 局部性原理
- 索引數據結構
- 聯合索引
- 最左前綴匹配原則
- 建索引的幾大原則
- 數據文件和索引文件
- 執行計劃explain
- 常見問題
- 數據頁
- MYSQL單表存儲量計算
- 回表
- 索引覆蓋
- 索引下推
- 頁分裂和頁合并
- InnoDB
- innodb索引
- Innodb引擎的底層實現
- MyISAM
- MyISAM引擎的底層實現
- MVCC
- Next-Key Locks
- MySQL索引類型
- MYSQL復制
- 主從復制
- 讀寫分離
- MySQL Dual-Master
- 分庫分表實現方案
- MySQL事務實現原理
- MYSQL調優
- 性能優化
- HBase
- 不停機分庫分表遷移
- RDBMS&NoSQL
- 分布式事務
- 協議或事務模型
- X/Open XA協議
- 分布式事務編程接口規范JTA
- TCC模型
- 解決方案
- 兩階段提交2PC
- 三階段提交3PC
- Seata
- 分布式事務Seata產品模塊
- AT模式
- TCC模式
- Saga模式
- XA模式
- 基于消息中間件的最終一致性事務方案
- 消息隊列
- AMQP
- JMS
- ActiveMQ
- RabbitMQ
- RocketMQ
- RocketMQ基本概念
- 主要特性
- 分區順序消息
- 全局順序消息
- 消息可靠性
- 定時消息
- 消息重試
- 死信隊列
- 分布式事務消息
- RocketMQ架構
- Producer
- Consumer
- NameServer
- Broker
- RocketMQ設計
- 消息存儲
- 頁緩存與內存映射
- 消息刷盤
- 通信機制
- console控制臺
- RocketMQ部署架構
- Kafka
- Pulsar
- MQ消息重復消費與丟失
- 主流消息隊列比較
- 分布式調度系統
- 分布式搜索
- 分布式計算
- 架構案例
- 秒殺業務
- 秒殺整體架構
- 常見的監控系統
- 小米手機搶購秒殺方案
- 架構師領導藝術
- 架構師箴言
- 技術leader核心職責
- WEB服務器
- Servlet
- Servlet實現
- Servlet生命周期
- Servlet容器工作模式
- Servlet工作原理
- servlet線程安全
- CGI&FastCGI
- CGI
- FastCGI
- FastCGI與CGI特點
- CGI與Servlet比較
- HTTP Server
- Nginx
- Apache
- Nginx與Apache比較
- Application Server
- Tomcat
- Tomcat總體架構
- Connector
- 連接器核心功能
- ProtocolHandler
- EndPoint
- Processor
- Adapter
- Container
- 請求定位Servlet的過程
- Lifecycle生命周期
- Tomcat模塊設計
- Tomcat實例
- Tomcat運行原理
- spring & servlet
- Tomcat啟動流程
- Tomcat支持的I/O模型
- Tomcat應用層協議
- Tomcat類加載機制
- Tomcat類加載器
- Tomcat類加載器層次
- Apache+Tomcat
- 序列化
- XML&JSON
- JSON
- JAVA原生序列化
- hessian
- 常見中間件
- Canal
- Databus
- ELK日志套件
- 數據庫連接池
- spring狀態機
- 常見解決方案
- 二維碼掃碼登錄原理
- 前沿技術
- Saas服務
- 服務網格(Service Mesh)
- 云原生
- 常見面試問題
- Redis持久化的幾種方式
- Redis的緩存失效策略
- 附錄
- 二將軍問題
- 常見問題定位步驟
- 如何快速熟悉新系統
- 制定技術方案套路
- NUMA陷阱