## 問題一
@小美
既然一個 JVM 是一個進程,JVM 上跑 Tomcat,Tomcat 上可以部署多個應用。這樣的話,每個跑在 Tomcat 上的應用是一個線程嗎?該怎么理解“如果一個應用 crash 了,其他應用也會 crash”?
理解程序運行時的執行環境,直觀感受程序是如何運行的,對我們開發和維護軟件很有意義。我們以小美同學提的這個場景為例,看下 Java Web 程序的運行時環境是什么樣的,來重新梳理下進程、線程、應用、Web 容器、Java 虛擬機和操作系統之間的關系。
我們用 Java 開發 Web 應用,開發完成,編譯打包以后得到的是一個 war 包,這個 war 包放入 Tomcat 的應用程序路徑下,啟動 Tomcat 就可以通過 HTTP 請求訪問這個 Web 應用了。
在這個場景下,進程是哪個?線程有哪些?Web 程序的 war 包是如何啟動的?HTTP 請求如何被處理?Tomcat 在這里扮演的是什么角色?JVM 又扮演什么角色?
首先,我們是通過執行 Tomcat 的 Shell 腳本啟動 Tomcat 的,而在 Shell 腳本里,其實啟動的是 Java 虛擬機,大概是這樣一個 Shell 命令:
java org.apache.catalina.startup.Bootstrap "$@" start
所以我們在 Linux 操作系統執行 Tomcat 的 Shell 啟動腳本,Tomcat 啟動以后,其實在操作系統里看到的是一個 JVM 虛擬機進程。這個虛擬機進程啟動以后,加載 class 進來執行,首先加載的就這個org.apache.catalina.startup.Bootstrap類,這個類里面有一個main()函數,是整個 Tomcat 的入口函數,JVM 虛擬機會啟動一個主線程從這個入口函數開始執行。
主線程從 Bootstrap 的 main() 函數開始執行,初始化 Tomcat 的運行環境,這時候就需要創建一些線程,比如負責監聽 80 端口的線程,處理客戶端連接請求的線程,以及執行用戶請求的線程。創建這些線程的代碼是 Tomcat 代碼的一部分。
初始化運行環境之后,Tomcat 就會掃描 Web 程序路徑,掃描到開發的 war 包后,再加載 war 包里的類到 JVM。因為 Web 應用是被 Tomcat 加載運行的,所以我們也稱 Tomcat 為 Web 容器。
如果有外部請求發送到 Tomcat,也就是外部程序通過 80 端口和 Tomcat 進行 HTTP 通信的時候,Tomcat 會根據 war 包中的 web.xml 配置,決定這個請求 URL 應該由哪個 Servlet 處理,然后 Tomcat 就會分配一個線程去處理這個請求,實際上,就是這個線程執行相應的 Servlet 代碼。
我們回到小美同學的問題,Tomcat 啟動的時候,啟動的是 JVM 進程,這個進程首先是執行 JVM 的代碼,而 JVM 會加載 Tomcat 的 class 執行,并分配一個主線程,這個主線程會從 main 函數開始執行。在主線程執行過程中,Tomcat 的代碼還會啟動其他一些線程,包括處理 HTTP 請求的線程。
而我們開發的應用是一些 class,被 Tomcat 加載到這個 JVM 里執行,所以,即使這里有多個應用被加載,也只是加載了一些 class,我們的應用被加載進來以后,并沒有增加 JVM 進程中的線程數,也就是 web 應用本身和線程是沒有關系的。
而 Tomcat 會根據 HTTP 請求 URL 執行應用中的代碼,這個時候,可以理解成每個請求分配一個線程,每個線程執行的都是我們開發的 Web 代碼。如果 Web 代碼中包含了創建新線程的代碼,Tomcat 的線程在執行代碼時,就會創建出新的線程,這些線程也會被操作系統調度執行。
如果 Tomcat 的線程在執行代碼時,代碼拋出未處理的異常,那么當前線程就會結束執行,這時控制臺看到的異常信息,其實就是線程堆棧信息,線程會把異常信息以及當前堆棧的方法都打印出來。事實上,這個異常最后還是會被 Tomcat 捕獲,然后 Tomcat 會給客戶端返回一個 500 錯誤。單個線程的異常不影響其他線程執行,也就是不影響其他請求的處理。
但是如果線程在執行代碼的時候,拋出的是 JVM 錯誤,比如OutOfMemoryError,這個時候看起來是應用 crash,事實上是整個進程都無法繼續執行了,也就是進程 crash 了,進程內所有應用都不會被繼續執行了。
從 JVM 的角度看,Tomcat 和我們的 Web 應用是一樣的,都是一些 Java 代碼,但是 Tomcat 卻可以加載執行 Web 代碼,而我們的代碼又不依賴 Tomcat,這也是一個很有意思的話題。Tomcat 是如何設計的,我將會在下個模塊講述。
## 問題二
@黃海峰
有點難以想象,“Hash 表的時間復雜度為什么是 O(1)”這個問題居然有阿里大廠的面試官覺得難。
這不是一個疑問,但其實是一個有意思的話題,我們花一點時間討論下,也許會對你的職業規劃有所啟發。
文中這個故事大概發生在 2009 年,整整十年前,那個時候互聯網還不像今天這樣炙手可熱,提供的薪水也不像今天這樣有競爭力,也沒有 BAT 這樣的專有名詞指代所謂的互聯網巨頭。那個時候,計算機專業優秀的畢業生向往的是微軟、Oracle、IBM 這樣的外資 IT 巨頭,退而求其次,國內好的 IT 公司是聯想、用友這些企業。
事實上,那個時候在技術研發能力上,互聯網公司的技術能力也是落后傳統企業的,阿里巴巴最核心的數據存儲依賴的是 IBM、Oracle、EMC 的解決方案,即所謂的 IOE。
所以在十年前的人才市場上,國內互聯網公司的形象一般是:技術落后、薪水一般、加班嚴重、沒有名氣。可以說在人才市場的競爭中,相比國內外的 IT 巨頭是落于下風的。
我個人感覺,互聯網公司的崛起大概是在七八年前,移動互聯網開始出現,互聯網的滲透率得到加速,BAT 逐漸開始成為家喻戶曉的名字,名氣大漲。其次,經過前面時間的積累,互聯網企業主導的各種分布式技術、大數據技術、移動互聯網技術、云計算技術的風頭超過傳統 IT 巨頭,阿里巴巴開始去 IOE,打造自己的云計算平臺,成為先進技術的代表者;最主要的還是互聯網企業盈利能力大幅增加,能夠提供市場上更有競爭力的薪水和股票。
于是互聯網企業在人才市場上開始變得灼手可熱,BAT 這些企業開始被人稱為“大廠”。我們今天感覺這些互聯網巨頭高高在上,人們紛紛向往。事實上,這個現象出現的時間非常短。今天這些企業有足夠的名氣和資源將自己營造得高高在上,可以在眾多優秀的候選人中間挑來選去,僅僅在十年前,還不是這樣的。
但是事情真正的吊詭之處還不在這里,當今這些互聯網大廠的核心技術和業務模式在十幾年前就已經奠定了,經過幾年的摸索,大概在七八年前開始穩定成熟。也就是說,互聯網企業的技術實力和商業能力是在這些企業還默默無聞的時候就發展起來的,而在這些企業成為明星之后,并沒有什么突破性的進展。想想這些所謂的互聯網大廠,最近幾年,并沒有什么值得稱道的商業模式創新和技術創新。
也就是說,十多年前,可能是一些并不優秀的技術人員加入一個并不出名的公司,然后這些人開創出了一個杰出的事業。用馬云的話說,就是“二流的人做一流的事”。然后公司開始挑選一流的人,但結果似乎只是在維持這個事業,并沒有開創出更加杰出的事業。今天的 BAT 似乎成為當年的 IBM,歷史好像進入了某種循環。
如果這就是事情的真相,我想你或許可以從其中得到某些啟發,重新考慮下未來的職業規劃。也許你會發現,你可能不需要追逐當前所謂的熱門技術,而應該好好想想需要為自己的未來準備些什么。
最后,在第一模塊中,我在每一篇文章的下面都留了幾道思考題,各位同學在評論區都有很好的答案。但只有第五篇文章,我似乎沒有看到比較準確的答案,我在這里回答一下。
RAID5 中,校驗位之所以螺旋式地落在所有硬盤上,主要原因是因為如果將校驗位記錄在同一塊硬盤上,那么對于其他多塊數據盤,任何一塊硬盤修改數據,都需要修改這個校驗盤上的校驗數據,也就是說,對于有 8 塊硬盤的 RAID5 陣列,校驗盤的數據寫入壓力是其他數據盤的 7 倍。而硬盤的頻繁寫入會導致硬盤壽命縮短,校驗盤會頻繁損壞,存儲的整體可用性和維護性都會變差。
所以,作為軟件架構師,當你在進行軟件設計的時候,你不光需要考慮軟件本身,你還需要了解軟件的各種約束,硬盤的特性約束是一種,當然還有其他一些約束,我會在專欄的后面模塊中繼續講解如何在各種約束下,設計出符合期望的軟件系統。
- 技能知識點
- 對死鎖問題的理解
- 文件系統原理:如何用1分鐘遍歷一個100TB的文件?
- 數據庫原理:為什么PrepareStatement性能更好更安全?
- Java Web程序的運行時環境到底是怎樣的?
- 你真的知道自己要解決的問題是什么嗎?
- 如何解決問題
- 經驗分享
- GIT的HTTP方式免密pull、push
- 使用xhprof對php7程序進行性能分析
- 微信掃碼登錄和使用公眾號方式進行掃碼登錄
- 關于curl跳轉抓取
- Linux 下配置 Git 操作免登錄 ssh 公鑰
- Linux Memcached 安裝
- php7安裝3.4版本的phalcon擴展
- centos7下php7.0.x安裝phalcon框架
- 將字符串按照指定長度分割
- 搜索html源碼中標簽包的純文本
- 更換composer鏡像源為阿里云
- mac 隱藏文件顯示/隱藏
- 谷歌(google)世界各國網址大全
- 實戰文檔
- PHP7安裝intl擴展和linux安裝icu
- linux編譯安裝時常見錯誤解決辦法
- linux刪除文件后不釋放磁盤空間解決方法
- PHP開啟異步多線程執行腳本
- file_exists(): open_basedir restriction in effect. File完美解決方案
- PHP 7.1 安裝 ssh2 擴展,用于PHP進行ssh連接
- php命令行加載的php.ini
- linux文件實時同步
- linux下php的psr.so擴展源碼安裝
- php將字符串中的\n變成真正的換行符?
- PHP7 下安裝 memcache 和 memcached 擴展
- PHP 高級面試題 - 如果沒有 mb 系列函數,如何切割多字節字符串
- PHP設置腳本最大執行時間的三種方法
- 升級Php 7.4帶來的兩個大坑
- 不同域名的iframe下,fckeditor在chrome下的SecurityError,解決辦法~~
- Linux find+rm -rf 執行組合刪除
- 從零搭建Prometheus監控報警系統
- Bug之group_concat默認長度限制
- PHP生成的XML顯示無效的Char值27消息(PHP generated XML shows invalid Char value 27 message)
- XML 解析中,如何排除控制字符
- PHP各種時間獲取
- nginx配置移動自適應跳轉
- 已安裝nginx動態添加模塊
- auto_prepend_file與auto_append_file使用方法
- 利用nginx實現web頁面插入統計代碼
- Nginx中的rewrite指令(break,last,redirect,permanent)
- nginx 中 index try_files location 這三個配置項的作用
- linux安裝git服務器
- PHP 中運用 elasticsearch
- PHP解析Mysql Binlog
- 好用的PHP學習網(持續更新中)
- 一篇寫給準備升級PHP7的小伙伴的文章
- linux 安裝php7 -系統centos7
- Linux 下多php 版本共存安裝
- PHP編譯安裝時常見錯誤解決辦法,php編譯常見錯誤
- nginx upstream模塊--負載均衡
- 如何解決Tomcat服務器打開不了HOST Manager的問題
- PHP的內存泄露問題與垃圾回收
- Redis數據結構 - string字符串
- PHP開發api接口安全驗證
- 服務接口API限流 Rate Limit
- php內核分析---內存管理(一)
- PHP內存泄漏問題解析
- 【代碼片-1】 MongoDB與PHP -- 高級查詢
- 【代碼片-1】 php7 mongoDB 簡單封裝
- php與mysql系統中出現大量數據庫sleep的空連接問題分析
- 解決crond引發大量sendmail、postdrop進程問題
- PHP操作MongoDB GridFS 存儲文件,如圖片文件
- 淺談php安全
- linux上keepalived+nginx實現高可用web負載均衡
- 整理php防注入和XSS攻擊通用過濾