Java后臺面試 常見問題

從三月份找實習(xí)到現(xiàn)在,面了一些公司崔涂,掛了不少阳掐,但最終還是拿到小米、百度、阿里缭保、京東汛闸、新浪、CVTE艺骂、樂視家的研發(fā)崗offer诸老。我找的是java后臺開發(fā),把常見的問題分享給大家钳恕,有一些是自己的總結(jié)别伏,有一些是網(wǎng)上借鑒的內(nèi)容。希望能幫助到各位忧额。預(yù)祝各位同學(xué)拿到自己心儀的offer厘肮!


Nginx負載均衡

  • 輪詢、輪詢是默認的睦番,每一個請求按順序逐一分配到不同的后端服務(wù)器轴脐,如果后端服務(wù)器down掉了,則能自動剔除

  • ip_hash抡砂、個請求按訪問IP的hash結(jié)果分配大咱,這樣來自同一個IP的訪客固定訪問一個后端服務(wù)器,有效解決了動態(tài)網(wǎng)頁存在的session共享問題注益。

  • weight碴巾、weight是設(shè)置權(quán)重,用于后端服務(wù)器性能不均的情況丑搔,訪問比率約等于權(quán)重之比

  • fair(第三方)厦瓢、這是比上面兩個更加智能的負載均衡算法。此種算法可以依據(jù)頁面大小和加載時間長短智能地進行負載均衡啤月,也就是根據(jù)后端服務(wù)器的響應(yīng)時間來分配請求煮仇,響應(yīng)時間短的優(yōu)先分配。Nginx本身是不支持fair的谎仲,如果需要使用這種調(diào)度算法浙垫,必須下載Nginx的upstream_fair模塊。

  • url_hash(第三方)此方法按訪問url的hash結(jié)果來分配請求郑诺,使每個url定向到同一個后端服務(wù)器夹姥,可以進一步提高后端緩存服務(wù)器的效率。Nginx本身是不支持url_hash的辙诞,如果需要使用這種調(diào)度算法辙售,必須安裝Nginx 的hash軟件包。

代理的概念

正向代理飞涂,也就是傳說中的代理, 簡單的說旦部,我是一個用戶祈搜,我訪問不了某網(wǎng)站,但是我能訪問一個代理服務(wù)器士八,這個代理服務(wù)器呢际起,他能訪問那個我不能訪問的網(wǎng)站尤莺,于是我先連上代理服務(wù)器缝彬,告訴他我需要那個無法訪問網(wǎng)站的內(nèi)容财忽,代理服務(wù)器去取回來捧杉,然后返回給我陕见。從網(wǎng)站的角度,只在代理服務(wù)器來取內(nèi)容的時候有一次記錄味抖,有時候并不知道是用戶的請求评甜,也隱藏了用戶的資料,這取決于代理告不告訴網(wǎng)站仔涩。

反向代理: 結(jié)論就是忍坷,反向代理正好相反,對于客戶端而言它就像是原始服務(wù)器熔脂,并且客戶端不需要進行任何特別的設(shè)置佩研。客戶端向反向代理的命名空間(name-space)中的內(nèi)容發(fā)送普通請求霞揉,接著反向代理將判斷向何處(原始服務(wù)器)轉(zhuǎn)交請求旬薯,并將獲得的內(nèi)容返回給客戶端,就像這些內(nèi)容原本就是它自己的一樣适秩。

Volatile的特征:

A绊序、原子性 :對任意單個volatile變量的讀/寫具有原子性,但類似于volatile++這種復(fù)合操作不具有原子性秽荞。
B骤公、可見性:對一個volatile變量的讀,總是能看到(任意線程)對這個volatile變量最后的寫入扬跋。

Volatile的內(nèi)存語義:

當寫一個volatile變量時阶捆,JMM會把線程對應(yīng)的本地內(nèi)存中的共享變量值刷新到主內(nèi)存。

這里寫圖片描述

當讀一個volatile變量時钦听,JMM會把線程對應(yīng)的本地內(nèi)存置為無效趁猴,線程接下來將從主內(nèi)存中讀取共享變量。

這里寫圖片描述

Volatile的重排序

1彪见、當?shù)诙€操作為volatile寫操做時,不管第一個操作是什么(普通讀寫或者volatile讀寫),都不能進行重排序儡司。這個規(guī)則確保volatile寫之前的所有操作都不會被重排序到volatile之后;

2、當?shù)谝粋€操作為volatile讀操作時,不管第二個操作是什么,都不能進行重排序余指。這個規(guī)則確保volatile讀之后的所有操作都不會被重排序到volatile之前;

3捕犬、當?shù)谝粋€操作是volatile寫操作時,第二個操作是volatile讀操作,不能進行重排序跷坝。

這個規(guī)則和前面兩個規(guī)則一起構(gòu)成了:兩個volatile變量操作不能夠進行重排序;

除以上三種情況以外可以進行重排序碉碉。

比如:

1柴钻、第一個操作是普通變量讀/寫,第二個是volatile變量的讀;
2垢粮、第一個操作是volatile變量的寫,第二個是普通變量的讀/寫贴届;


內(nèi)存屏障/內(nèi)存柵欄

內(nèi)存屏障(Memory Barrier,或有時叫做內(nèi)存柵欄蜡吧,Memory Fence)是一種CPU指令毫蚓,用于控制特定條件下的重排序和內(nèi)存可見性問題。Java編譯器也會根據(jù)內(nèi)存屏障的規(guī)則禁止重排序昔善。(也就是讓一個CPU處理單元中的內(nèi)存狀態(tài)對其它處理單元可見的一項技術(shù)元潘。)

內(nèi)存屏障可以被分為以下幾種類型:

LoadLoad屏障:對于這樣的語句Load1; LoadLoad; Load2,在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪問前君仆,保證Load1要讀取的數(shù)據(jù)被讀取完畢翩概。

StoreStore屏障:對于這樣的語句Store1; StoreStore; Store2,在Store2及后續(xù)寫入操作執(zhí)行前返咱,保證Store1的寫入操作對其它處理器可見钥庇。

LoadStore屏障:對于這樣的語句Load1; LoadStore; Store2,在Store2及后續(xù)寫入操作被刷出前咖摹,保證Load1要讀取的數(shù)據(jù)被讀取完畢上沐。

StoreLoad屏障:對于這樣的語句Store1; StoreLoad; Load2,在Load2及后續(xù)所有讀取操作執(zhí)行前楞艾,保證Store1的寫入對所有處理器可見参咙。它的開銷是四種屏障中最大的。

在大多數(shù)處理器的實現(xiàn)中硫眯,這個屏障是個萬能屏障蕴侧,兼具其它三種內(nèi)存屏障的功能。

內(nèi)存屏障阻礙了CPU采用優(yōu)化技術(shù)來降低內(nèi)存操作延遲两入,必須考慮因此帶來的性能損失净宵。為了達到最佳性能,最好是把要解決的問題模塊化裹纳,這樣處理器可以按單元執(zhí)行任務(wù)择葡,然后在任務(wù)單元的邊界放上所有需要的內(nèi)存屏障。采用這個方法可以讓處理器不受限的執(zhí)行一個任務(wù)單元剃氧。合理的內(nèi)存屏障組合還有一個好處是:緩沖區(qū)在第一次被刷后開銷會減少敏储,因為再填充改緩沖區(qū)不需要額外工作了。


happens-before原則

如果一個操作執(zhí)行的結(jié)果需要對另一個操作可見朋鞍,那么這兩個操作之間必須要存在happens-before關(guān)系已添。

這里寫圖片描述

Java是如何實現(xiàn)跨平臺的妥箕?

跨平臺是怎樣實現(xiàn)的呢?這就要談及Java虛擬機(Java Virtual Machine更舞,簡稱 JVM)畦幢。

JVM也是一個軟件,不同的平臺有不同的版本缆蝉。我們編寫的Java源碼宇葱,編譯后會生成一種 .class 文件,稱為字節(jié)碼文件刊头。Java虛擬機就是負責(zé)將字節(jié)碼文件翻譯成特定平臺下的機器碼然后運行黍瞧。也就是說,只要在不同平臺上安裝對應(yīng)的JVM芽偏,就可以運行字節(jié)碼文件,運行我們編寫的Java程序弦讽。

而這個過程中污尉,我們編寫的Java程序沒有做任何改變,僅僅是通過JVM這一”中間層“往产,就能在不同平臺上運行被碗,真正實現(xiàn)了”一次編譯,到處運行“的目的仿村。

JVM是一個”橋梁“锐朴,是一個”中間件“,是實現(xiàn)跨平臺的關(guān)鍵蔼囊,Java代碼首先被編譯成字節(jié)碼文件焚志,再由JVM將字節(jié)碼文件翻譯成機器語言,從而達到運行Java程序的目的畏鼓。

注意:編譯的結(jié)果不是生成機器碼酱酬,而是生成字節(jié)碼,字節(jié)碼不能直接運行云矫,必須通過JVM翻譯成機器碼才能運行膳沽。不同平臺下編譯生成的字節(jié)碼是一樣的,但是由JVM翻譯成的機器碼卻不一樣让禀。

所以挑社,運行Java程序必須有JVM的支持,因為編譯的結(jié)果不是機器碼巡揍,必須要經(jīng)過JVM的再次翻譯才能執(zhí)行痛阻。即使你將Java程序打包成可執(zhí)行文件(例如 .exe),仍然需要JVM的支持腮敌。

注意:跨平臺的是Java程序录平,不是JVM麻车。JVM是用C/C++開發(fā)的,是編譯后的機器碼斗这,不能跨平臺动猬,不同平臺下需要安裝不同版本的JVM。

垃圾搜集器

  1. 按照線程數(shù)量來分:
    1. 串行 串行垃圾回收器一次只使用一個線程進行垃圾回收
    2. 并行 并行垃圾回收器一次將開啟多個線程同時進行垃圾回收表箭。
  2. 按照工作模式來分:
    1. 并發(fā) 并發(fā)式垃圾回收器與應(yīng)用程序線程交替工作赁咙,以盡可能減少應(yīng)用程序的停頓時間
    2. 獨占 一旦運行,就停止應(yīng)用程序中的其他所有線程免钻,直到垃圾回收過程完全結(jié)束
  3. 按照碎片處理方式:
    1. 壓縮式 壓縮式垃圾回收器會在回收完成后彼水,對存活對象進行壓縮整消除回收后的碎片;
    2. 非壓縮式 非壓縮式的垃圾回收器不進行這步操作极舔。
  4. 按工作的內(nèi)存區(qū)間 可分為新生代垃圾回收器和老年代垃圾回收器
  • 新生代串行收集器 serial 它僅僅使用單線程進行垃圾回收凤覆;第二,它獨占式的垃圾回收拆魏。使用復(fù)制算法盯桦。

  • 老年代串行收集器 serial old 年代串行收集器使用的是標記-壓縮算法。和新生代串行收集器一樣渤刃,它也是一個串行的拥峦、獨占式的垃圾回收器

  • 并行收集器 parnew 并行收集器是工作在新生代的垃圾收集器,它只簡單地將串行回收器多線程化卖子。它的回收策略略号、算法以及參數(shù)和串行回收器一樣 并行回收器也是獨占式的回收器,在收集過程中洋闽,應(yīng)用程序會全部暫停玄柠。但由于并行回收器使用多線程進行垃圾回收,因此诫舅,在并發(fā)能力比較強的 CPU 上随闪,它產(chǎn)生的停頓時間要短于串行回收器,而在單 CPU 或者并發(fā)能力較弱的系統(tǒng)中骚勘,并行回收器的效果不會比串行回收器好铐伴,由于多線程的壓力,它的實際表現(xiàn)很可能比串行回收器差俏讹。

  • 新生代并行回收 (Parallel Scavenge) 收集器 新生代并行回收收集器也是使用復(fù)制算法的收集器当宴。從表面上看,它和并行收集器一樣都是多線程泽疆、獨占式的收集器户矢。但是,并行回收收集器有一個重要的特點:它非常關(guān)注系統(tǒng)的吞吐量殉疼。

  • 老年代并行回收收集器 parallel old 老年代的并行回收收集器也是一種多線程并發(fā)的收集器梯浪。和新生代并行回收收集器一樣捌年,它也是一種關(guān)注吞吐量的收集器。老年代并行回收收集器使用標記-壓縮算法挂洛,JDK1.6 之后開始啟用礼预。

  • CMS 收集器 CMS 收集器主要關(guān)注于系統(tǒng)停頓時間。CMS 是 Concurrent Mark Sweep 的縮寫虏劲,意為并發(fā)標記清除托酸,從名稱上可以得知嫂侍,它使用的是標記-清除算法苇羡,同時它又是一個使用多線程并發(fā)回收的垃圾收集器凉倚。

    • CMS 工作時晦毙,主要步驟有:初始標記、并發(fā)標記腾窝、重新標記堆生、并發(fā)清除和并發(fā)重置虐译。其中初始標記和重新標記是獨占系統(tǒng)資源的泉唁,而并發(fā)標記鹅龄、并發(fā)清除和并發(fā)重置是可以和用戶線程一起執(zhí)行的。因此游两,從整體上來說砾层,CMS 收集不是獨占式的漩绵,它可以在應(yīng)用程序運行過程中進行垃圾回收贱案。

      根據(jù)標記-清除算法,初始標記止吐、并發(fā)標記和重新標記都是為了標記出需要回收的對象宝踪。并發(fā)清理則是在標記完成后,正式回收垃圾對象碍扔;并發(fā)重置是指在垃圾回收完成后瘩燥,重新初始化 CMS 數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù),為下一次垃圾回收做好準備不同。并發(fā)標記厉膀、并發(fā)清理和并發(fā)重置都是可以和應(yīng)用程序線程一起執(zhí)行的。

  • G1 收集器 G1 收集器是基于標記-壓縮算法的二拐。因此服鹅,它不會產(chǎn)生空間碎片,也沒有必要在收集完成后百新,進行一次獨占式的碎片整理工作企软。G1 收集器還可以進行非常精確的停頓控制。

網(wǎng)絡(luò)基本概念

OSI模型

OSI 模型(Open System Interconnection model)是一個由國際標準化組織??提出的概念模型,試圖??供一個使各種不同的計算機和網(wǎng)絡(luò)在世界范圍內(nèi)實現(xiàn)互聯(lián)的標準框架饭望。
它將計算機網(wǎng)絡(luò)體系結(jié)構(gòu)劃分為七層,每層都可以??供抽象良好的接口仗哨。了解 OSI 模型有助于理解實際上互聯(lián)網(wǎng)絡(luò)的工業(yè)標準——TCP/IP 協(xié)議形庭。
OSI 模型各層間關(guān)系和通訊時的數(shù)據(jù)流向如圖所示:

OSI 模型.png

顯然、如果一個東西想包羅萬象厌漂、一般時不可能的萨醒;在實際的開發(fā)應(yīng)用中一般時在此模型的基礎(chǔ)上進行裁剪、整合桩卵!

七層模型介紹

  • 物理層:
    物理層負責(zé)最后將信息編碼成電流脈沖或其它信號用于網(wǎng)上傳輸验靡;
    eg:RJ45等將數(shù)據(jù)轉(zhuǎn)化成0和1;
  • 數(shù)據(jù)鏈路層:
    數(shù)據(jù)鏈路層通過物理網(wǎng)絡(luò)鏈路??供數(shù)據(jù)傳輸雏节。不同的數(shù)據(jù)鏈路層定義了不同的網(wǎng)絡(luò)和協(xié) 議特征,其中包括物理編址胜嗓、網(wǎng)絡(luò)拓撲結(jié)構(gòu)、錯誤校驗钩乍、數(shù)據(jù)幀序列以及流控;
    可以簡單的理解為:規(guī)定了0和1的分包形式辞州,確定了網(wǎng)絡(luò)數(shù)據(jù)包的形式;
  • 網(wǎng)絡(luò)層
    網(wǎng)絡(luò)層負責(zé)在源和終點之間建立連接;
    可以理解為寥粹,此處需要確定計算機的位置变过,怎么確定?IPv4涝涤,IPv6媚狰!
  • 傳輸層
    傳輸層向高層??提供可靠的端到端的網(wǎng)絡(luò)數(shù)據(jù)流服務(wù)。
    可以理解為:每一個應(yīng)用程序都會在網(wǎng)卡注冊一個端口號阔拳,該層就是端口與端口的通信崭孤!常用的(TCP/IP)協(xié)議;
  • 會話層
    會話層建立糊肠、管理和終止表示層與實體之間的通信會話辨宠;
    建立一個連接(自動的手機信息、自動的網(wǎng)絡(luò)尋址);
  • 表示層:
    表示層??供多種功能用于應(yīng)用層數(shù)據(jù)編碼和轉(zhuǎn)化,以確保以一個系統(tǒng)應(yīng)用層發(fā)送的信息 可以被另一個系統(tǒng)應(yīng)用層識別;
    可以理解為:解決不同系統(tǒng)之間的通信货裹,eg:Linux下的QQ和Windows下的QQ可以通信嗤形;
  • 應(yīng)用層:
    OSI 的應(yīng)用層協(xié)議包括文件的傳輸、訪問及管理協(xié)議(FTAM) ,以及文件虛擬終端協(xié)議(VIP)和公用管理系統(tǒng)信息(CMIP)等;
    規(guī)定數(shù)據(jù)的傳輸協(xié)議弧圆;

常見的應(yīng)用層協(xié)議:

常見的應(yīng)用層協(xié)議.png

互聯(lián)網(wǎng)分層結(jié)構(gòu)的好處: 上層的變動完全不影響下層的結(jié)構(gòu)赋兵。

TCP/IP 協(xié)議基本概念

OSI 模型所分的七層,在實際應(yīng)用中,往往有一些層被整合,或者功能分散到其他層去。TCP/IP 沒有照搬 OSI 模型,也沒有 一個公認的 TCP/IP 層級模型,一般劃分為三層到五層模型來??述 TCP/IP 協(xié)議搔预。

  • 在此描述用一個通用的四層模型來描述,每一層都和 OSI 模型有較強的相關(guān)性但是又可能會有交叉霹期。
  • TCP/IP 的設(shè)計,是吸取了分層模型的精華思想——封裝。每層對上一層??供服務(wù)的時 候,上一層的數(shù)據(jù)結(jié)構(gòu)是黑盒,直接作為本層的數(shù)據(jù),而不需要關(guān)心上一層協(xié)議的任何細節(jié)斯撮。

TCP/IP 分層模型的分層以以太網(wǎng)上傳輸 UDP 數(shù)據(jù)包如圖所示;

UDP 數(shù)據(jù)包.png

數(shù)據(jù)包

寬泛意義的數(shù)據(jù)包:每一個數(shù)據(jù)包都包含"標頭"和"數(shù)據(jù)"兩個部分."標頭"包含本數(shù)據(jù)包的一些說明."數(shù)據(jù)"則是本數(shù)據(jù)包的內(nèi)容.

細分數(shù)據(jù)包:

  • 應(yīng)用程序數(shù)據(jù)包: 標頭部分規(guī)定應(yīng)用程序的數(shù)據(jù)格式.數(shù)據(jù)部分傳輸具體的數(shù)據(jù)內(nèi)容.** ——對應(yīng)上圖中的數(shù)據(jù)经伙!**
  • TCP/UDP數(shù)據(jù)包:標頭部分包含雙方的發(fā)出端口和接收端口. UDP數(shù)據(jù)包:'標頭'長度:8個字節(jié),"數(shù)據(jù)包"總長度最大為65535字節(jié),正好放進一個IP數(shù)據(jù)包. TCP數(shù)據(jù)包:理論上沒有長度限制,但是,為了保證網(wǎng)絡(luò)傳輸效率,通常不會超過IP數(shù)據(jù)長度,確保單個包不會被分割. ——對應(yīng)上圖中的UDP數(shù)據(jù)!
  • IP數(shù)據(jù)包: 標頭部分包含通信雙方的IP地址,協(xié)議版本,長度等信息. '標頭'長度:20~60字節(jié),"數(shù)據(jù)包"總長度最大為65535字節(jié). ——對應(yīng)上圖中的IP數(shù)據(jù)
  • 以太網(wǎng)數(shù)據(jù)包: 最基礎(chǔ)的數(shù)據(jù)包.標頭部分包含了通信雙方的MAC地址,數(shù)據(jù)類型等. '標頭'長度:18字節(jié),'數(shù)據(jù)'部分長度:46~1500字節(jié). ——對應(yīng)上圖中的以太網(wǎng)數(shù)據(jù)

四層模型

  1. 網(wǎng)絡(luò)接口層
    網(wǎng)絡(luò)接口層包括用于協(xié)作IP數(shù)據(jù)在已有網(wǎng)絡(luò)介質(zhì)上傳輸?shù)膮f(xié)議。
    它定義像地址解析協(xié)議(Address Resolution Protocol,ARP)這樣的協(xié)議,??供 TCP/IP 協(xié)議的數(shù)據(jù)結(jié)構(gòu)和實際物理硬件之間的接口帕膜。
    可以理解為:確定了網(wǎng)絡(luò)數(shù)據(jù)包的形式枣氧。
  2. 網(wǎng)間層
    網(wǎng)間層對應(yīng)于 OSI 七層參考模型的網(wǎng)絡(luò)層,本層包含 IP 協(xié)議垮刹、RIP 協(xié)議(Routing Information Protocol,路由信息協(xié)議),負責(zé)數(shù)據(jù)的包裝达吞、尋址和路由。同時還包含網(wǎng)間控制報文協(xié)議(Internet Control Message Protocol,ICMP)用來??供網(wǎng)絡(luò)診斷信息荒典;
    可以理解為:該層時確定計算機的位置酪劫。
  3. 傳輸層
    傳輸層對應(yīng)于 OSI 七層參考模型的傳輸層,它??供兩種端到端的通信服務(wù)。其中 TCP 協(xié)議(Transmission Control Protocol)??供可靠的數(shù)據(jù)流運輸服務(wù),UDP 協(xié)議(Use Datagram Protocol)??供不可靠的用戶數(shù)據(jù)報服務(wù)寺董。
    TCP:三次握手覆糟、四次揮手;UDP:只發(fā)不管別人收不收得到--任性哈
  4. 應(yīng)用層
    應(yīng)用層對應(yīng)于 OSI 七層參考模型的應(yīng)用層和表達層;
    不明白的再看看7層參考模型的描述遮咖。

TCP/IP 協(xié)議族常用協(xié)議

  • 應(yīng)用層:TFTP滩字,HTTP,SNMP御吞,F(xiàn)TP麦箍,SMTP,DNS陶珠,Telnet 等等
  • 傳輸層:TCP挟裂,UDP
  • 網(wǎng)絡(luò)層:IP,ICMP揍诽,OSPF诀蓉,EIGRP,IGMP
  • 數(shù)據(jù)鏈路層:SLIP寝姿,CSLIP交排,PPP划滋,MTU

重要的 TCP/IP 協(xié)議族協(xié)議進行簡單介紹:

  • IP(Internet Protocol,網(wǎng)際協(xié)議)是網(wǎng)間層的主要協(xié)議,任務(wù)是在源地址和和目的地址之間傳輸數(shù)據(jù)饵筑。IP 協(xié)議只是盡最大努力來傳輸數(shù)據(jù)包,并不保證所有的包都可以傳輸 到目的地,也不保證數(shù)據(jù)包的順序和唯一。
    • IP 定義了 TCP/IP 的地址,尋址方法,以及路由規(guī)則〈ζ海現(xiàn)在廣泛使用的 IP 協(xié)議有 IPv4 和 IPv6 兩種:IPv4 使用 32 位二進制整數(shù)做地址,一般使用點分十進制方式表示,比如 192.168.0.1根资。
    • IP 地址由兩部分組成,即網(wǎng)絡(luò)號和主機號。故一個完整的 IPv4 地址往往表示 為 192.168.0.1/24 或192.168.0.1/255.255.255.0 這種形式同窘。
    • IPv6 是為了解決 IPv4 地址耗盡和其它一些問題而研發(fā)的最新版本的 IP玄帕。使用 128 位 整數(shù)表示地址,通常使用冒號分隔的十六進制來表示,并且可以省略其中一串連續(xù)的 0,如:fe80::200:1ff:fe00:1。
      目前使用并不多想邦!
  • ICMP(Internet Control Message Protocol,網(wǎng)絡(luò)控制消息協(xié)議)是 TCP/IP 的 核心協(xié)議之一,用于在 IP 網(wǎng)絡(luò)中發(fā)送控制消息,??供通信過程中的各種問題反饋裤纹。 ICMP 直接使用 IP 數(shù)據(jù)包傳輸,但 ICMP 并不被視為 IP 協(xié)議的子協(xié)議。常見的聯(lián)網(wǎng)狀態(tài)診斷工具比如依賴于 ICMP 協(xié)議;
  • TCP(TransmissionControlProtocol,傳輸控制協(xié)議)是一種面向連接的,可靠的, 基于字節(jié)流傳輸?shù)耐ㄐ艆f(xié)議。TCP 具有端口號的概念,用來標識同一個地址上的不 同應(yīng)用鹰椒。??述 TCP 的標準文檔是 RFC793锡移。
  • UDP(UserDatagramProtocol,用戶數(shù)據(jù)報協(xié)議)是一個面向數(shù)據(jù)報的傳輸層協(xié) 議。UDP 的傳輸是不可靠的,簡單的說就是發(fā)了不管,發(fā)送者不會知道目標地址 的數(shù)據(jù)通路是否發(fā)生擁塞,也不知道數(shù)據(jù)是否到達,是否完整以及是否還是原來的 次序漆际。它同 TCP 一樣有用來標識本地應(yīng)用的端口號淆珊。所以應(yīng)用 UDP 的應(yīng)用,都能 夠容忍一定數(shù)量的錯誤和丟包,但是對傳輸性能敏感的,比如流媒體、DNS 等奸汇。
  • ECHO(EchoProtocol,回聲協(xié)議)是一個簡單的調(diào)試和檢測工具施符。服務(wù)器器會 原樣回發(fā)它收到的任何數(shù)據(jù),既可以使用 TCP 傳輸,也可以使用 UDP 傳輸。使用 端口號 7 擂找。
  • DHCP(DynamicHostConfigrationProtocol,動態(tài)主機配置協(xié)議)是用于局域 網(wǎng)自動分配 IP 地址和主機配置的協(xié)議戳吝。可以使局域網(wǎng)的部署更加簡單贯涎。
  • DNS(DomainNameSystem,域名系統(tǒng))是互聯(lián)網(wǎng)的一項服務(wù),可以簡單的將用“.” 分隔的一般會有意義的域名轉(zhuǎn)換成不易記憶的 IP 地址骨坑。一般使用 UDP 協(xié)議傳輸, 也可以使用 TCP,默認服務(wù)端口號 53。??
  • FTP(FileTransferProtocol,文件傳輸協(xié)議)是用來進行文件傳輸?shù)臉藴蕝f(xié)議柬采。 FTP 基于 TCP 使用端口號 20 來傳輸數(shù)據(jù),21 來傳輸控制信息欢唾。
  • TFTP(Trivial File Transfer Protocol,簡單文件傳輸協(xié)議)是一個簡化的文 件傳輸協(xié)議,其設(shè)計非常簡單,通過少量存儲器就能輕松實現(xiàn),所以一般被用來通 過網(wǎng)絡(luò)引導(dǎo)計算機過程中傳輸引導(dǎo)文件等小文件;
  • SSH(SecureShell,安全Shell),因為傳統(tǒng)的網(wǎng)絡(luò)服務(wù)程序比如TELNET本質(zhì)上都極不安全,明文傳說數(shù)據(jù)和用戶信息包括密碼,SSH 被開發(fā)出來避免這些問題, 它其實是一個協(xié)議框架,有大量的擴展冗余能力,并且??供了加密壓縮的通道可以 為其他協(xié)議使用。
  • POP(PostOfficeProtocol,郵局協(xié)議)是支持通過客戶端訪問電子郵件的服務(wù), 現(xiàn)在版本是 POP3,也有加密的版本 POP3S粉捻。協(xié)議使用 TCP,端口 110礁遣。
  • SMTP(Simple Mail Transfer Protocol,簡單郵件傳輸協(xié)議)是現(xiàn)在在互聯(lián)網(wǎng) 上發(fā)送電子郵件的事實標準。使用 TCP 協(xié)議傳輸,端口號 25肩刃。
  • HTTP(HyperTextTransferProtocol,超文本傳輸協(xié)議)是現(xiàn)在廣為流行的WEB 網(wǎng)絡(luò)的基礎(chǔ),HTTPS 是 HTTP 的加密安全版本祟霍。協(xié)議通過 TCP 傳輸,HTTP 默認 使用端口 80,HTTPS 使用 443。

以上就是今天回顧的內(nèi)容盈包。
下篇回顧一下socket沸呐、TCP、UDP呢燥!

線程池

Executor 框架便是 Java 5 中引入的崭添,其內(nèi)部使用了線程池機制

好處

第一:降低資源消耗 通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。

第二:提高響應(yīng)速度叛氨。當任務(wù)到達時呼渣,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。

第三:提高線程的可管理性寞埠。線程是稀缺資源屁置,如果無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源仁连,還會降低系統(tǒng)的穩(wěn)定性蓝角,使用線程池可以進行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。但是要做到合理的利用線程池使鹅,必須對其原理了如指掌颇象。

Java線程間的通信方式

wait()方法

wait()方法使得當前線程必須要等待,等到另外一個線程調(diào)用notify()或者notifyAll()方法并徘。

當前的線程必須擁有當前對象的monitor遣钳,也即lock,就是鎖麦乞。

線程調(diào)用wait()方法蕴茴,釋放它對鎖的擁有權(quán),然后等待另外的線程來通知它(通知的方式是notify()或者notifyAll()方法)姐直,這樣它才能重新獲得鎖的擁有權(quán)和恢復(fù)執(zhí)行倦淀。

要確保調(diào)用wait()方法的時候擁有鎖,即声畏,wait()方法的調(diào)用必須放在synchronized方法或synchronized塊中撞叽。

一個小比較:

當線程調(diào)用了wait()方法時,它會釋放掉對象的鎖插龄。

另一個會導(dǎo)致線程暫停的方法:Thread.sleep()愿棋,它會導(dǎo)致線程睡眠指定的毫秒數(shù)鹉胖,但線程在睡眠的過程中是不會釋放掉對象的鎖的西剥。

notify()方法

notify()方法會喚醒一個等待當前對象的鎖的線程。

如果多個線程在等待奖亚,它們中的一個將會選擇被喚醒徘跪。這種選擇是隨意的甘邀,和具體實現(xiàn)有關(guān)。(線程等待一個對象的鎖是由于調(diào)用了wait方法中的一個)垮庐。

被喚醒的線程是不能被執(zhí)行的松邪,需要等到當前線程放棄這個對象的鎖。

被喚醒的線程將和其他線程以通常的方式進行競爭哨查,來獲得對象的鎖逗抑。也就是說,被喚醒的線程并沒有什么優(yōu)先權(quán)解恰,也沒有什么劣勢锋八,對象的下一個線程還是需要通過一般性的競爭浙于。

notify()方法應(yīng)該是被擁有對象的鎖的線程所調(diào)用护盈。

(This method should only be called by a thread that is the owner of this object's monitor.)

換句話說,和wait()方法一樣羞酗,notify方法調(diào)用必須放在synchronized方法或synchronized塊中腐宋。

wait()和notify()方法要求在調(diào)用時線程已經(jīng)獲得了對象的鎖,因此對這兩個方法的調(diào)用需要放在synchronized方法或synchronized塊中。

  一個線程變?yōu)橐粋€對象的鎖的擁有者是通過下列三種方法:

1.執(zhí)行這個對象的synchronized實例方法胸竞。

2.執(zhí)行這個對象的synchronized語句塊欺嗤。這個語句塊鎖的是這個對象。

3.對于Class類的對象卫枝,執(zhí)行那個類的synchronized煎饼、static方法。


Java 線程有哪些狀態(tài)校赤,這些狀態(tài)之間是如何轉(zhuǎn)化的吆玖?

這里寫圖片描述
  1. 新建(new):新創(chuàng)建了一個線程對象。
  2. 可運行(runnable):線程對象創(chuàng)建后马篮,其他線程(比如main線程)調(diào)用了該對象的start()方法沾乘。該狀態(tài)的線程位于可運行線程池中,等待被線程調(diào)度選中浑测,獲取cpu 的使用權(quán) 翅阵。
  3. 運行(running):可運行狀態(tài)(runnable)的線程獲得了cpu 時間片(timeslice) ,執(zhí)行程序代碼迁央。
  4. 阻塞(block):阻塞狀態(tài)是指線程因為某種原因放棄了cpu 使用權(quán)掷匠,也即讓出了cpu timeslice,暫時停止運行岖圈。直到線程進入可運行(runnable)狀態(tài)槐雾,才有機會再次獲得cpu timeslice 轉(zhuǎn)到運行(running)狀態(tài)。阻塞的情況分三種:

(一). 等待阻塞:運行(running)的線程執(zhí)行o.wait()方法幅狮,JVM會把該線程放入等待隊列(waitting queue)中募强。同時釋放對象鎖

(二). 同步阻塞:運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用崇摄,則JVM會把該線程放入鎖池(lock pool)中擎值。

(三). 其他阻塞:運行(running)的線程執(zhí)行Thread.sleep(long ms)或t.join()方法,或者發(fā)出了I/O請求時逐抑,JVM會把該線程置為阻塞狀態(tài)鸠儿。當sleep()狀態(tài)超時、join()等待線程終止或者超時厕氨、或者I/O處理完畢時进每,線程重新轉(zhuǎn)入可運行(runnable)狀態(tài)。

  1. 死亡(dead):線程run()命斧、main() 方法執(zhí)行結(jié)束田晚,或者因異常退出了run()方法,則該線程結(jié)束生命周期国葬。死亡的線程不可再次復(fù)生贤徒。

List接口芹壕、Set接口和Map接口的區(qū)別

1、List和Set接口自Collection接口接奈,而Map不是繼承的Collection接口

Collection表示一組對象,這些對象也稱為collection的元素;一些 collection允許有重復(fù)的元素,而另一些則不允許;一些collection是有序的,而另一些則是無序的;JDK中不提供此接口的任何直接實 現(xiàn),它提供更具體的子接口(如 Set 和 List)實現(xiàn);Map沒有繼承Collection接口,Map提供key到value的映射;一個Map中不能包含相同key,每個key只能映射一個value;Map接口提供3種集合的視圖,Map的內(nèi)容可以被當做一組key集合,一組value集合,或者一組key-value映射;

2踢涌、List接口

元素有放入順序,元素可重復(fù) 
List接口有三個實現(xiàn)類:LinkedList序宦,ArrayList睁壁,Vector       
LinkedList:底層基于鏈表實現(xiàn),鏈表內(nèi)存是散亂的互捌,每一個元素存儲本身內(nèi)存地址的同時還存儲下一個元素的地址堡僻。鏈表增刪快,查找慢 
ArrayList和Vector的區(qū)別:ArrayList是非線程安全的疫剃,效率高钉疫;Vector是基于線程安全的,效率低 
List是一種有序的Collection巢价,可以通過索引訪問集合中的數(shù)據(jù),List比Collection多了10個方法牲阁,主要是有關(guān)索引的方法。
      1).所有的索引返回的方法都有可能拋出一個IndexOutOfBoundsException異常
      2).subList(int fromIndex, int toIndex)返回的是包括fromIndex壤躲,不包括toIndex的視圖城菊,該列表的size()=toIndex-fromIndex。
      所有的List中只能容納單個不同類型的對象組成的表碉克,而不是Key-Value鍵值對凌唬。例如:[ tom,1,c ];
      所有的List中可以有相同的元素,例如Vector中可以有 [ tom,koo,too,koo ];
      所有的List中可以有null元素漏麦,例如[ tom,null,1 ];
      基于Array的List(Vector客税,ArrayList)適合查詢,而LinkedList(鏈表)適合添加撕贞,刪除操作;

3更耻、Set接口

元素?zé)o放入順序,元素不可重復(fù)(注意:元素雖然無放入順序捏膨,但是元素在set中的位置是有該元素的HashCode決定的秧均,其位置其實是固定的)
Set接口有兩個實現(xiàn)類:HashSet(底層由HashMap實現(xiàn)),LinkedHashSet 
  SortedSet接口有一個實現(xiàn)類:TreeSet(底層由平衡二叉樹實現(xiàn))
  Query接口有一個實現(xiàn)類:LinkList 
  Set具有與Collection完全一樣的接口号涯,因此沒有任何額外的功能目胡,不像前面有兩個不同的List。實際上Set就是Collection,只是行為不同链快。(這是繼承與多態(tài)思想的典型應(yīng)用:表現(xiàn)不同的行為誉己。)Set不保存重復(fù)的元素(至于如何判斷元素相同則較為負責(zé))
  Set : 存入Set的每個元素都必須是唯一的,因為Set不保存重復(fù)元素久又。加入Set的元素必須定義equals()方法以確保對象的唯一性巫延。Set與Collection有完全一樣的接口效五。Set接口不保證維護元素的次序地消。
  HashSet : 為快速查找設(shè)計的Set炉峰。存入HashSet的對象必須定義hashCode()。
  TreeSet : 保存次序的Set, 底層為樹結(jié)構(gòu)脉执。使用它可以從Set中提取有序的序列疼阔。
  LinkedHashSet : 具有HashSet的查詢速度,且內(nèi)部使用鏈表維護元素的順序(插入的次序)半夷。于是在使用迭代器遍歷Set時婆廊,結(jié)果會按元素插入的次序顯示。

4巫橄、map接口

以鍵值對的方式出現(xiàn)的 
Map接口有三個實現(xiàn)類:HashMap淘邻,HashTable,LinkeHashMap 

      HashMap非線程安全湘换,高效宾舅,支持null;

      HashTable線程安全彩倚,低效筹我,不支持null 

      SortedMap有一個實現(xiàn)類:TreeMap 
  

Session機制

一、術(shù)語session

session帆离,中文經(jīng)常翻譯為會話蔬蕊,其本來的含義是指有始有終的一系列動作/消息,比如打電話時從拿起電話撥號到掛斷電話這中間的一系列過程可以稱之為一個session哥谷。有時候我們可以看到這樣的話“在一個瀏覽器會話期間岸夯,...”,這里的會話一詞用的就是其本義们妥,是指從一個瀏覽器窗口打開到關(guān)閉這個期間①囱修。最混亂的是“用戶(客戶端)在一次會話期間”這樣一句話,它可能指用戶的一系列動作(一般情況下是同某個具體目的相關(guān)的一系列動作王悍,比如從登錄到選購商品到結(jié)賬登出這樣一個網(wǎng)上購物的過程破镰,有時候也被稱為一個transaction),然而有時候也可能僅僅是指一次連接压储,也有可能是指含義①鲜漩,其中的差別只能靠上下文來推斷②。
然而當session一詞與網(wǎng)絡(luò)協(xié)議相關(guān)聯(lián)時集惋,它又往往隱含了“面向連接”和/或“保持狀態(tài)”這樣兩個含義孕似,“面向連接”指的是在通信雙方在通信之前要先建立一個通信的渠道,比如打電話刮刑,直到對方接了電話通信才能開始喉祭,與此相對的是寫信养渴,在你把信發(fā)出去的時候你并不能確認對方的地址是否正確,通信渠道不一定能建立泛烙,但對發(fā)信人來說理卑,通信已經(jīng)開始了”伟保“保持狀態(tài)”則是指通信的一方能夠把一系列的消息關(guān)聯(lián)起來藐唠,使得消息之間可以互相依賴,比如一個服務(wù)員能夠認出再次光臨的老顧客并且記得上次這個顧客還欠店里一塊錢鹉究。這一類的例子有“一個TCP session”或者“一個POP3 session”③宇立。
而到了web服務(wù)器蓬勃發(fā)展的時代,session在web開發(fā)語境下的語義又有了新的擴展自赔,它的含義是指一類用來在客戶端與服務(wù)器之間保持狀態(tài)的解決方案④妈嘹。有時候session也用來指這種解決方案的存儲結(jié)構(gòu),如“把xxx保存在session里”⑤绍妨。由于各種用于web開發(fā)的語言在一定程度上都提供了對這種解決方案的支持润脸,所以在某種特定語言的語境下,session也被用來指代該語言的解決方案痘绎,比如經(jīng)常把Java里提供的javax.servlet.http.HttpSession簡稱為session⑥津函。
鑒于這種混亂已不可改變,本文中session一詞的運用也會根據(jù)上下文有不同的含義孤页,請大家注意分辨尔苦。
在本文中,使用中文“瀏覽器會話期間”來表達含義①行施,使用“session機制”來表達含義④允坚,使用“session”表達含義⑤,使用具體的“HttpSession”來表達含義⑥
** 二蛾号、HTTP協(xié)議與狀態(tài)保持**
HTTP協(xié)議本身是無狀態(tài)的稠项,這與HTTP協(xié)議本來的目的是相符的,客戶端只需要簡單的向服務(wù)器請求下載某些文件鲜结,無論是客戶端還是服務(wù)器都沒有必要紀錄彼此過去的行為展运,每一次請求之間都是獨立的,好比一個顧客和一個自動售貨機或者一個普通的(非會員制)大賣場之間的關(guān)系一樣精刷。
然而聰明(或者貪心拗胜?)的人們很快發(fā)現(xiàn)如果能夠提供一些按需生成的動態(tài)信息會使web變得更加有用琳疏,就像給有線電視加上點播功能一樣肌蜻。這種需求一方面迫使HTML逐步添加了表單、腳本峻凫、DOM等客戶端行為纫事,另一方面在服務(wù)器端則出現(xiàn)了CGI規(guī)范以響應(yīng)客戶端的動態(tài)請求勘畔,作為傳輸載體的HTTP協(xié)議也添加了文件上載所灸、cookie這些特性。其中cookie的作用就是為了解決HTTP協(xié)議無狀態(tài)的缺陷所作出的努力炫七。至于后來出現(xiàn)的session機制則是又一種在客戶端與服務(wù)器之間保持狀態(tài)的解決方案爬立。
讓我們用幾個例子來描述一下cookie和session機制之間的區(qū)別與聯(lián)系。筆者曾經(jīng)常去的一家咖啡店有喝5杯咖啡免費贈一杯咖啡的優(yōu)惠诉字,然而一次性消費5杯咖啡的機會微乎其微懦尝,這時就需要某種方式來紀錄某位顧客的消費數(shù)量知纷。想象一下其實也無外乎下面的幾種方案:
1壤圃、該店的店員很厲害,能記住每位顧客的消費數(shù)量琅轧,只要顧客一走進咖啡店伍绳,店員就知道該怎么對待了。這種做法就是協(xié)議本身支持狀態(tài)乍桂。
2冲杀、發(fā)給顧客一張卡片,上面記錄著消費的數(shù)量睹酌,一般還有個有效期限权谁。每次消費時,如果顧客出示這張卡片憋沿,則此次消費就會與以前或以后的消費相聯(lián)系起來旺芽。這種做法就是在客戶端保持狀態(tài)。
3辐啄、發(fā)給顧客一張會員卡采章,除了卡號之外什么信息也不紀錄,每次消費時壶辜,如果顧客出示該卡片悯舟,則店員在店里的紀錄本上找到這個卡號對應(yīng)的紀錄添加一些消費信息。這種做法就是在服務(wù)器端保持狀態(tài)砸民。
由于HTTP協(xié)議是無狀態(tài)的抵怎,而出于種種考慮也不希望使之成為有狀態(tài)的,因此岭参,后面兩種方案就成為現(xiàn)實的選擇反惕。具體來說cookie機制采用的是在客戶端保持狀態(tài)的方案,而session機制采用的是在服務(wù)器端保持狀態(tài)的方案冗荸。同時我們也看到承璃,由于采用服務(wù)器端保持狀態(tài)的方案在客戶端也需要保存一個標識,所以session機制可能需要借助于cookie機制來達到保存標識的目的蚌本,但實際上它還有其他選擇盔粹。
**三隘梨、理解cookie機制 **
cookie機制的基本原理就如上面的例子一樣簡單,但是還有幾個問題需要解決:“會員卡”如何分發(fā)舷嗡;“會員卡”的內(nèi)容轴猎;以及客戶如何使用“會員卡”。
正統(tǒng)的cookie分發(fā)是通過擴展HTTP協(xié)議來實現(xiàn)的进萄,服務(wù)器通過在HTTP的響應(yīng)頭中加上一行特殊的指示以提示瀏覽器按照指示生成相應(yīng)的cookie捻脖。然而純粹的客戶端腳本如JavaScript或者VBScript也可以生成cookie。
而cookie的使用是由瀏覽器按照一定的原則在后臺自動發(fā)送給服務(wù)器的中鼠。瀏覽器檢查所有存儲的cookie可婶,如果某個cookie所聲明的作用范圍大于等于將要請求的資源所在的位置,則把該cookie附在請求資源的HTTP請求頭上發(fā)送給服務(wù)器援雇。意思是麥當勞的會員卡只能在麥當勞的店里出示矛渴,如果某家分店還發(fā)行了自己的會員卡,那么進這家店的時候除了要出示麥當勞的會員卡惫搏,還要出示這家店的會員卡具温。
cookie的內(nèi)容主要包括:名字,值筐赔,過期時間铣猩,路徑和域。
其中域可以指定某一個域比如.google.com茴丰,相當于總店招牌达皿,比如寶潔公司,也可以指定一個域下的具體某臺機器比如www.google.com或者froogle.google.com较沪,可以用飄柔來做比鳞绕。
路徑就是跟在域名后面的URL路徑,比如/或者/foo等等尸曼,可以用某飄柔專柜做比们何。路徑與域合在一起就構(gòu)成了cookie的作用范圍。如果不設(shè)置過期時間控轿,則表示這個cookie的生命期為瀏覽器會話期間冤竹,只要關(guān)閉瀏覽器窗口,cookie就消失了茬射。這種生命期為瀏覽器會話期的cookie被稱為會話cookie鹦蠕。會話cookie一般不存儲在硬盤上而是保存在內(nèi)存里,當然這種行為并不是規(guī)范規(guī)定的在抛。如果設(shè)置了過期時間钟病,瀏覽器就會把cookie保存到硬盤上,關(guān)閉后再次打開瀏覽器,這些cookie仍然有效直到超過設(shè)定的過期時間肠阱。
存儲在硬盤上的cookie可以在不同的瀏覽器進程間共享票唆,比如兩個IE窗口。而對于保存在內(nèi)存里的cookie屹徘,不同的瀏覽器有不同的處理方式走趋。對于IE,在一個打開的窗口上按Ctrl-N(或者從文件菜單)打開的窗口可以與原窗口共享噪伊,而使用其他方式新開的IE進程則不能共享已經(jīng)打開的窗口的內(nèi)存cookie簿煌;對于Mozilla Firefox0.8,所有的進程和標簽頁都可以共享同樣的cookie鉴吹。一般來說是用javascript的window.open打開的窗口會與原窗口共享內(nèi)存cookie姨伟。瀏覽器對于會話cookie的這種只認cookie不認人的處理方式經(jīng)常給采用session機制的web應(yīng)用程序開發(fā)者造成很大的困擾。

Cookie和Session的區(qū)別

HTTP請求是無狀態(tài)的拙寡。

共同之處

cookie和session都是用來跟蹤瀏覽器用戶身份的會話方式授滓。

區(qū)別

  • cookie數(shù)據(jù)保存在客戶端琳水,session數(shù)據(jù)保存在服務(wù)器端肆糕。簡單的說,當你登錄一個網(wǎng)站的時候, 如果web服務(wù)器端使用的是session在孝,那么所有的數(shù)據(jù)都保存在服務(wù)器上诚啃,客戶端每次請求服務(wù)器的時候會發(fā)送當前會話的sessionid,服務(wù)器根據(jù)當前sessionid判斷相應(yīng)的用戶數(shù)據(jù)標志私沮,以確定用戶是否登錄或具有某種權(quán)限始赎。由于數(shù)據(jù)是存儲在服務(wù)器上面,所以你不能偽造仔燕。
  • sessionid是服務(wù)器和客戶端鏈接時候隨機分配的. 如果瀏覽器使用的是cookie造垛,那么所有的數(shù)據(jù)都保存在瀏覽器端,比如你登錄以后晰搀,服務(wù)器設(shè)置了cookie用戶名五辽,那么當你再次請求服務(wù)器的時候,瀏覽器會將用戶名一塊發(fā)送給服務(wù)器外恕,這些變量有一定的特殊標記杆逗。服務(wù)器會解釋為cookie變量,所以只要不關(guān)閉瀏覽器鳞疲,那么cookie變量一直是有效的罪郊,所以能夠保證長時間不掉線。如果你能夠截獲某個用戶的 cookie變量尚洽,然后偽造一個數(shù)據(jù)包發(fā)送過去悔橄,那么服務(wù)器還是認為你是合法的。所以,使用 cookie被攻擊的可能性比較大癣疟。

如果設(shè)置了的有效時間尺铣,那么它會將 cookie保存在客戶端的硬盤上,下次再訪問該網(wǎng)站的時候争舞,瀏覽器先檢查有沒有 cookie凛忿,如果有的話,就讀取該 cookie竞川,然后發(fā)送給服務(wù)器店溢。如果你在機器上面保存了某個論壇 cookie,有效期是一年委乌,如果有人入侵你的機器床牧,將你的 cookie拷走,然后放在他的瀏覽器的目錄下面遭贸,那么他登錄該網(wǎng)站的時候就是用你的的身份登錄的戈咳。所以 cookie是可以偽造的。當然壕吹,偽造的時候需要主意著蛙,直接copy cookie文件到 cookie目錄,瀏覽器是不認的耳贬,他有一個index.dat文件踏堡,存儲了 cookie文件的建立時間,以及是否有修改咒劲,所以你必須先要有該網(wǎng)站的 cookie文件顷蟆,并且要從保證時間上騙過瀏覽器

兩個都可以用來存私密的東西,同樣也都有有效期的說法,區(qū)別在于session是放在服務(wù)器上的腐魂,過期與否取決于服務(wù)期的設(shè)定帐偎,cookie是存在客戶端的,過去與否可以在cookie生成的時候設(shè)置進去蛔屹。

(1)cookie數(shù)據(jù)存放在客戶的瀏覽器上削樊,session數(shù)據(jù)放在服務(wù)器上
(2)cookie不是很安全,別人可以分析存放在本地的COOKIE并進行COOKIE欺騙,如果主要考慮到安全應(yīng)當使用session
(3)session會在一定時間內(nèi)保存在服務(wù)器上判导。當訪問增多嫉父,會比較占用你服務(wù)器的性能,如果主要考慮到減輕服務(wù)器性能方面眼刃,應(yīng)當使用COOKIE
(4)單個cookie在客戶端的限制是3K绕辖,就是說一個站點在客戶端存放的COOKIE不能3K。
(5)所以:將登陸信息等重要信息存放為SESSION;其他信息如果需要保留擂红,可以放在COOKIE中

Java中的equals和hashCode方法詳解

equals()方法是用來判斷其他的對象是否和該對象相等.

equals()方法在object類中定義如下:

public boolean equals(Object obj) {  
   return (this == obj);  
}  

很明顯是對兩個對象的地址值進行的比較(即比較引用是否相同)仪际。但是我們知道围小,String 、Math树碱、Integer肯适、Double等這些封裝類在使用equals()方法時,已經(jīng)覆蓋了object類的equals()方法成榜。

比如在String類中如下:

[
復(fù)制代碼

](javascript:void(0);)

public boolean equals(Object anObject) {  
   if (this == anObject) {  
       return true;  
   }  
   if (anObject instanceof String) {  
       String anotherString = (String)anObject;  
       int n = count;  
       if (n == anotherString.count) {  
           char v1[] = value;  
           char v2[] = anotherString.value;  
           int i = offset;  
           int j = anotherString.offset;  
           while (n– != 0) {  
               if (v1[i++] != v2[j++])  
                   return false;  
           }  
           return true;  
       }  
   }  
   return false;  
}  

很明顯框舔,這是進行的內(nèi)容比較,而已經(jīng)不再是地址的比較赎婚。依次類推Math刘绣、Integer、Double等這些類都是重寫了equals()方法的挣输,從而進行的是內(nèi)容的比較纬凤。當然,基本類型是進行值的比較撩嚼。

它的性質(zhì)有:

  • 自反性(reflexive)停士。對于任意不為null的引用值x,x.equals(x)一定是true
  • 對稱性(symmetric)。對于任意不為null的引用值xy,當且僅當x.equals(y)true時,y.equals(x)也是true温技。
  • 傳遞性(transitive)。對于任意不為null的引用值x瞬沦、yz醒颖,如果x.equals(y)true,同時y.equals(z)true刁赖,那么x.equals(z)一定是true搁痛。
  • 一致性(consistent)。對于任意不為null的引用值xy宇弛,如果用于equals比較的對象信息沒有被修改的話鸡典,多次調(diào)用時x.equals(y)要么一致地返回true要么一致地返回false
  • 對于任意不為null的引用值x枪芒,x.equals(null)返回false彻况。

對于Object類來說,equals()方法在對象上實現(xiàn)的是差別可能性最大的等價關(guān)系舅踪,即纽甘,對于任意非null的引用值xy,當且僅當xy引用的是同一個對象抽碌,該方法才會返回true悍赢。

需要注意的是當equals()方法被override時,hashCode()也要被override。按照一般hashCode()方法的實現(xiàn)來說左权,相等的對象皮胡,它們的hash code一定相等。

hashcode() 方法詳解

hashCode()方法給對象返回一個hash code值赏迟。這個方法被用于hash tables屡贺,例如HashMap。

它的性質(zhì)是:

  • 在一個Java應(yīng)用的執(zhí)行期間锌杀,如果一個對象提供給equals做比較的信息沒有被修改的話烹笔,該對象多次調(diào)用hashCode()方法,該方法必須始終如一返回同一個integer抛丽。
  • 如果兩個對象根據(jù)equals(Object)方法是相等的谤职,那么調(diào)用二者各自的hashCode()方法必須產(chǎn)生同一個integer結(jié)果。
  • 并不要求根據(jù)equals(java.lang.Object)方法不相等的兩個對象亿鲜,調(diào)用二者各自的hashCode()方法必須產(chǎn)生不同的integer結(jié)果允蜈。然而,程序員應(yīng)該意識到對于不同的對象產(chǎn)生不同的integer結(jié)果蒿柳,有可能會提高hash table的性能饶套。

Java中CAS算法--樂觀鎖的一種實現(xiàn)方式

悲觀者與樂觀者的做事方式完全不一樣,悲觀者的人生觀是一件事情我必須要百分之百完全控制才會去做垒探,否則就認為這件事情一定會出問題妓蛮;而樂觀者的人生觀則相反,凡事不管最終結(jié)果如何圾叼,他都會先嘗試去做蛤克,大不了最后不成功。這就是悲觀鎖與樂觀鎖的區(qū)別夷蚊,悲觀鎖會把整個對象加鎖占為自有后才去做操作构挤,樂觀鎖不獲取鎖直接做操作,然后通過一定檢測手段決定是否更新數(shù)據(jù)惕鼓。這一節(jié)將對樂觀鎖進行深入探討筋现。

上節(jié)討論的Synchronized互斥鎖屬于悲觀鎖,它有一個明顯的缺點箱歧,它不管數(shù)據(jù)存不存在競爭都加鎖矾飞,隨著并發(fā)量增加,且如果鎖的時間比較長呀邢,其性能開銷將會變得很大洒沦。有沒有辦法解決這個問題?答案是基于沖突檢測的樂觀鎖驼鹅。這種模式下微谓,已經(jīng)沒有所謂的鎖概念了森篷,每條線程都直接先去執(zhí)行操作,計算完成后檢測是否與其他線程存在共享數(shù)據(jù)競爭豺型,如果沒有則讓此操作成功仲智,如果存在共享數(shù)據(jù)競爭則可能不斷地重新執(zhí)行操作和檢測,直到成功為止姻氨,可叫CAS自旋钓辆。

樂觀鎖的核心算法是CAS(Compareand Swap,比較并交換)肴焊,它涉及到三個操作數(shù):內(nèi)存值前联、預(yù)期值、新值娶眷。當且僅當預(yù)期值和內(nèi)存值相等時才將內(nèi)存值修改為新值似嗤。這樣處理的邏輯是,首先檢查某塊內(nèi)存的值是否跟之前我讀取時的一樣届宠,如不一樣則表示期間此內(nèi)存值已經(jīng)被別的線程更改過烁落,舍棄本次操作,否則說明期間沒有其他線程對此內(nèi)存值操作豌注,可以把新值設(shè)置給此塊內(nèi)存伤塌。如圖2-5-4-1,有兩個線程可能會差不多同時對某內(nèi)存操作轧铁,線程二先讀取某內(nèi)存值作為預(yù)期值每聪,執(zhí)行到某處時線程二決定將新值設(shè)置到內(nèi)存塊中,如果線程一在此期間修改了內(nèi)存塊齿风,則通過CAS即可以檢測出來药薯,假如檢測沒問題則線程二將新值賦予內(nèi)存塊。

img

圖2-5-4-1

假如你足夠細心你可能會發(fā)現(xiàn)一個疑問聂宾,比較和交換果善,從字面上就有兩個操作了,更別說實際CAS可能會有更多的執(zhí)行指令系谐,他們是原子性的嗎?如果非原子性又怎么保證CAS操作期間出現(xiàn)并發(fā)帶來的問題讨跟?我是不是需要用上節(jié)提到的互斥鎖來保證他的原子性操作纪他?CAS肯定是具有原子性的,不然就談不上在并發(fā)中使用了晾匠,但這個原子性是由CPU硬件指令實現(xiàn)保證的茶袒,即使用JNI調(diào)用native方法調(diào)用由C++編寫的硬件級別指令,jdk中提供了Unsafe類執(zhí)行這些操作凉馆。另外薪寓,你可能想著CAS是通過互斥鎖來實現(xiàn)原子性的亡资,這樣確實能實現(xiàn),但用這種方式來保證原子性顯示毫無意義向叉。下面一個偽代碼加深對CAS的理解:

public class AtomicInt {
 private volatile int value;
 public final int get() {
     return value;
  }
public final int getAndIncrement() {
     for (;;) {
         int current = get();
         int next = current + 1;
         if (compareAndSet(current, next))
              return current;
     }
  }
 public final boolean compareAndSet(int expect, int update) {
   Unsafe類提供的硬件級別的compareAndSwapInt方法;
  }
}

其中最重要的方法是getAndIncrement方法锥腻,它里面實現(xiàn)了基于CAS的自旋。

現(xiàn)在已經(jīng)了解樂觀鎖及CAS相關(guān)機制母谎,樂觀鎖避免了悲觀鎖獨占對象的現(xiàn)象瘦黑,同時也提高了并發(fā)性能,但它也有缺點:

① 觀鎖只能保證一個共享變量的原子操作奇唤。如上例子幸斥,自旋過程中只能保證value變量的原子性,這時如果多一個或幾個變量咬扇,樂觀鎖將變得力不從心甲葬,但互斥鎖能輕易解決,不管對象數(shù)量多少及對象顆粒度大小懈贺。

② 長時間自旋可能導(dǎo)致開銷大经窖。假如CAS長時間不成功而一直自旋,會給CPU帶來很大的開銷隅居。

③ ABA問題钠至。CAS的核心思想是通過比對內(nèi)存值與預(yù)期值是否一樣而判斷內(nèi)存值是否被改過,但這個判斷邏輯不嚴謹胎源,假如內(nèi)存值原來是A棉钧,后來被一條線程改為B,最后又被改成了A涕蚤,則CAS認為此內(nèi)存值并沒有發(fā)生改變宪卿,但實際上是有被其他線程改過的,這種情況對依賴過程值的情景的運算結(jié)果影響很大万栅。解決的思路是引入版本號佑钾,每次變量更新都把版本號加一。

樂觀鎖是對悲觀鎖的改進烦粒,雖然它也有缺點休溶,但它確實已經(jīng)成為提高并發(fā)性能的主要手段,而且jdk中的并發(fā)包也大量使用基于CAS的樂觀鎖扰她。

TimSort原理

comparable與comparator的區(qū)別

Comparable和Comparator的區(qū)別

初次碰到這個問題是之前有一次電話面試兽掰,問了一個小時的問題,其中有一個問題就問到Comparable和Comparator的區(qū)別徒役,當時沒答出 來孽尽。之后是公司入職時候做的一套Java編程題,里面用JUnit跑用例的時候也用到了Comparator接口忧勿,再加上JDK的大量的類包括常見的 String杉女、Byte瞻讽、Char、Date等都實現(xiàn)了Comparable接口熏挎,因此要學(xué)習(xí)一下這兩個類的區(qū)別以及用法速勇。

Comparable

Comparable可以認為是一個內(nèi)比較器,實現(xiàn)了Comparable接口的類有一個特點婆瓜,就是這些類是可以和自己比較的快集,至于具體和另一個實現(xiàn)了Comparable接口的類如何比較,則依賴compareTo方法的實現(xiàn)廉白,compareTo方法也被稱為自然比較方法个初。如果開發(fā)者add進入一個Collection的對象想要Collections的sort方法幫你自動進行排序的話,那么這個對象必須實現(xiàn)Comparable接口猴蹂。compareTo方法的返回值是int院溺,有三種情況:

1、比較者大于被比較者(也就是compareTo方法里面的對象)磅轻,那么返回正整數(shù)

2珍逸、比較者等于被比較者,那么返回0

3聋溜、比較者小于被比較者谆膳,那么返回負整數(shù)

寫個很簡單的例子:

public class Domain implements Comparable<Domain>
{
   private String str;

   public Domain(String str)
   {
       this.str = str;
   }

   public int compareTo(Domain domain)
   {
       if (this.str.compareTo(domain.str) > 0)
           return 1;
       else if (this.str.compareTo(domain.str) == 0)
           return 0;
       else 
           return -1;
   }
   
   public String getStr()
   {
       return str;
   }
}
public static void main(String[] args)
   {
       Domain d1 = new Domain("c");
       Domain d2 = new Domain("c");
       Domain d3 = new Domain("b");
       Domain d4 = new Domain("d");
       System.out.println(d1.compareTo(d2));
       System.out.println(d1.compareTo(d3));
       System.out.println(d1.compareTo(d4));
   }

運行結(jié)果為:

0
1
-1

注意一下,前面說實現(xiàn)Comparable接口的類是可以支持和自己比較的撮躁,但是其實代碼里面Comparable的泛型未必就一定要是Domain漱病,將泛型指定為String或者指定為其他任何任何類型都可以----只要開發(fā)者指定了具體的比較算法就行。

Comparator

Comparator可以認為是是一個外比較器把曼,個人認為有兩種情況可以使用實現(xiàn)Comparator接口的方式:

1杨帽、一個對象不支持自己和自己比較(沒有實現(xiàn)Comparable接口),但是又想對兩個對象進行比較

2嗤军、一個對象實現(xiàn)了Comparable接口注盈,但是開發(fā)者認為compareTo方法中的比較方式并不是自己想要的那種比較方式

Comparator接口里面有一個compare方法,方法有兩個參數(shù)T o1和T o2叙赚,是泛型的表示方式老客,分別表示待比較的兩個對象,方法返回值和Comparable接口一樣是int震叮,有三種情況:

1沿量、o1大于o2,返回正整數(shù)

2冤荆、o1等于o2,返回0

3权纤、o1小于o3钓简,返回負整數(shù)

寫個很簡單的例子乌妒,上面代碼的Domain不變(假設(shè)這就是第2種場景,我對這個compareTo算法實現(xiàn)不滿意外邓,要自己寫實現(xiàn)):

public class DomainComparator implements Comparator<Domain>
{
   public int compare(Domain domain1, Domain domain2)
   {
       if (domain1.getStr().compareTo(domain2.getStr()) > 0)
           return 1;
       else if (domain1.getStr().compareTo(domain2.getStr()) == 0)
           return 0;
       else 
           return -1;
   }
}
public static void main(String[] args)
{
   Domain d1 = new Domain("c");
   Domain d2 = new Domain("c");
   Domain d3 = new Domain("b");
   Domain d4 = new Domain("d");
   DomainComparator dc = new DomainComparator();
   System.out.println(dc.compare(d1, d2));
   System.out.println(dc.compare(d1, d3));
   System.out.println(dc.compare(d1, d4));
}

看一下運行結(jié)果:

0
1
-1

當然因為泛型指定死了撤蚊,所以實現(xiàn)Comparator接口的實現(xiàn)類只能是兩個相同的對象(不能一個Domain、一個String)進行比較了己单,因此實現(xiàn)Comparator接口的實現(xiàn)類一般都會以"待比較的實體類+Comparator"來命名

總結(jié)

總結(jié)一下荆针,兩種比較器Comparable和Comparator突颊,后者相比前者有如下優(yōu)點:

1、如果實現(xiàn)類沒有實現(xiàn)Comparable接口光涂,又想對兩個類進行比較(或者實現(xiàn)類實現(xiàn)了Comparable接口,但是對compareTo方法內(nèi)的比較算法不滿意)拧烦,那么可以實現(xiàn)Comparator接口忘闻,自定義一個比較器,寫比較算法

2恋博、實現(xiàn)Comparable接口的方式比實現(xiàn)Comparator接口的耦合性 要強一些齐佳,如果要修改比較算法,要修改Comparable接口的實現(xiàn)類债沮,而實現(xiàn)Comparator的類是在外部進行比較的炼吴,不需要對實現(xiàn)類有任何修 改。從這個角度說疫衩,其實有些不太好硅蹦,尤其在我們將實現(xiàn)類的.class文件打成一個.jar文件提供給開發(fā)者使用的時候。實際上實現(xiàn)Comparator 接口的方式后面會寫到就是一種典型的策略模式隧土。

手寫單例模式(線程安全)

解法一:只適合單線程環(huán)境(不好)

package test;
/**
* @author xiaoping
*
*/
public class Singleton {
   private static Singleton instance=null;
   private Singleton(){
       
   }
   public static Singleton getInstance(){
       if(instance==null){
           instance=new Singleton();
       }
       return instance;
   }
}

注解:Singleton的靜態(tài)屬性instance中提针,只有instance為null的時候才創(chuàng)建一個實例,構(gòu)造函數(shù)私有曹傀,確保每次都只創(chuàng)建一個辐脖,避免重復(fù)創(chuàng)建。
缺點:只在單線程的情況下正常運行皆愉,在多線程的情況下嗜价,就會出問題。例如:當兩個線程同時運行到判斷instance是否為空的if語句幕庐,并且instance確實沒有創(chuàng)建好時久锥,那么兩個線程都會創(chuàng)建一個實例。

解法二:多線程的情況可以用异剥。(懶漢式瑟由,不好)

public class Singleton {
   private static Singleton instance=null;
   private Singleton(){
       
   }
   public static synchronized Singleton getInstance(){
       if(instance==null){
           instance=new Singleton();
       }
       return instance;
   }
}

注解:在解法一的基礎(chǔ)上加上了同步鎖,使得在多線程的情況下可以用冤寿。例如:當兩個線程同時想創(chuàng)建實例歹苦,由于在一個時刻只有一個線程能得到同步鎖青伤,當?shù)谝粋€線程加上鎖以后,第二個線程只能等待殴瘦。第一個線程發(fā)現(xiàn)實例沒有創(chuàng)建狠角,創(chuàng)建之。第一個線程釋放同步鎖蚪腋,第二個線程才可以加上同步鎖丰歌,執(zhí)行下面的代碼。由于第一個線程已經(jīng)創(chuàng)建了實例屉凯,所以第二個線程不需要創(chuàng)建實例立帖。保證在多線程的環(huán)境下也只有一個實例。
缺點:每次通過getInstance方法得到singleton實例的時候都有一個試圖去獲取同步鎖的過程神得。而眾所周知厘惦,加鎖是很耗時的。能避免則避免哩簿。

解法三:加同步鎖時宵蕉,前后兩次判斷實例是否存在(可行)

public class Singleton {
   private static Singleton instance=null;
   private Singleton(){ }
   public static Singleton getInstance(){
       if(instance==null){
           synchronized(Singleton.class){
               if(instance==null){
                   instance=new Singleton();
               }
           }
       }
       return instance;
   }
}

注解:只有當instance為null時,需要獲取同步鎖节榜,創(chuàng)建一次實例羡玛。當實例被創(chuàng)建,則無需試圖加鎖宗苍。
缺點:用雙重if判斷稼稿,復(fù)雜,容易出錯讳窟。

解法四:餓漢式(建議使用)

public class Singleton {
   private static Singleton instance=new Singleton();
   private Singleton(){
   }
   public static Singleton getInstance(){
       return instance;
   }
}

注解:初試化靜態(tài)的instance創(chuàng)建一次让歼。如果我們在Singleton類里面寫一個靜態(tài)的方法不需要創(chuàng)建實例,它仍然會早早的創(chuàng)建一次實例丽啡。而降低內(nèi)存的使用率谋右。

缺點:沒有l(wèi)azy loading的效果,從而降低內(nèi)存的使用率补箍。

解法五:靜態(tài)內(nèi)部內(nèi)改执。(建議使用)

public class Singleton {
   private Singleton(){
       
   }
   private static class SingletonHolder{
       private final static Singleton instance=new Singleton();
   }
   public static Singleton getInstance(){
       return SingletonHolder.instance;
   }
}

注解:定義一個私有的內(nèi)部類,在第一次用這個嵌套類時坑雅,會創(chuàng)建一個實例辈挂。而類型為SingletonHolder的類,只有在Singleton.getInstance()中調(diào)用裹粤,由于私有的屬性终蒂,他人無法使用SingleHolder,不調(diào)用Singleton.getInstance()就不會創(chuàng)建實例。
優(yōu)點:達到了lazy loading的效果后豫,即按需創(chuàng)建實例悉尾。

JVM參數(shù)初始值

初始堆大小:1/64內(nèi)存-Xms 最大堆大写炷稹:1/4內(nèi)存-Xmx

初始永久代大小:1/64內(nèi)存-XX:PermSize 最大堆大秀的选:1/4內(nèi)存-XX:MaxPermSize

Java8的內(nèi)存分代改進

JAVA 8持久代已經(jīng)被徹底刪除了

取代它的是另一個內(nèi)存區(qū)域也被稱為元空間早龟。

元空間 —— 快速入門

  • 它是本地內(nèi)存中的一部分
  • 最直接的表現(xiàn)就是OOM(內(nèi)存溢出)問題將不復(fù)存在,因為直接利用的是本地內(nèi)存猫缭。
  • 它可以通過-XX:MetaspaceSize和-XX:MaxMetaspaceSize來進行調(diào)整
  • 當?shù)竭_XX:MetaspaceSize所指定的閾值后會開始進行清理該區(qū)域
  • 如果本地空間的內(nèi)存用盡了會收到j(luò)ava.lang.OutOfMemoryError: Metadata space的錯誤信息葱弟。
  • 和持久代相關(guān)的JVM參數(shù)-XX:PermSize及-XX:MaxPermSize將會被忽略掉,并且在啟動的時候給出警告信息猜丹。
  • 充分利用了Java語言規(guī)范中的好處:類及相關(guān)的元數(shù)據(jù)的生命周期與類加載器的一致

元空間 —— 內(nèi)存分配模型絕大多數(shù)的類元數(shù)據(jù)的空間都從本地內(nèi)存中分配芝加。用來描述類元數(shù)據(jù)的類也被刪除了,分元數(shù)據(jù)分配了多個虛擬內(nèi)存空間給每個類加載器分配一個內(nèi)存塊的列表射窒,只進行線性分配藏杖。塊的大小取決于類加載器的類型, sun/反射/代理對應(yīng)的類加載器的塊會小一些脉顿。不會單獨回收某個類蝌麸,如果GC發(fā)現(xiàn)某個類加載器不再存活了,會把相關(guān)的空間整個回收掉艾疟。這樣減少了碎片来吩,并節(jié)省GC掃描和壓縮的時間。

元空間 —— 調(diào)優(yōu)使用-XX:MaxMetaspaceSize參數(shù)可以設(shè)置元空間的最大值蔽莱,默認是沒有上限的弟疆,也就是說你的系統(tǒng)內(nèi)存上限是多少它就是多少。使用-XX:MetaspaceSize選項指定的是元空間的初始大小盗冷,如果沒有指定的話怠苔,元空間會根據(jù)應(yīng)用程序運行時的需要動態(tài)地調(diào)整大小。 一旦類元數(shù)據(jù)的使用量達到了“MaxMetaspaceSize”指定的值正塌,對于無用的類和類加載器嘀略,垃圾收集此時會觸發(fā)。為了控制這種垃圾收集的頻率和延遲乓诽,合適的監(jiān)控和調(diào)整Metaspace非常有必要帜羊。過于頻繁的Metaspace垃圾收集是類和類加載器發(fā)生內(nèi)存泄露的征兆,同時也說明你的應(yīng)用程序內(nèi)存大小不合適鸠天,需要調(diào)整讼育。

** 快速過一遍JVM的內(nèi)存結(jié)構(gòu),JVM中的內(nèi)存分為5個虛擬的區(qū)域:(程序計數(shù)器、

虛擬機棧奶段、本地方法棧饥瓷、堆區(qū)、方法區(qū))

Java8的JVM持久代 - 何去何從痹籍?

  • 你的Java程序中所分配的每一個對象都需要存儲在內(nèi)存里呢铆。堆是這些實例化的對象所存儲的地方。是的——都怪new操作符蹲缠,是它把你的Java堆都占滿了的棺克!
  • 它由所有線程共享
  • 當堆耗盡的時候,JVM會拋出java.lang.OutOfMemoryError 異常
  • 堆的大小可以通過JVM選項-Xms和-Xmx來進行調(diào)整

堆被分為:

  • Eden區(qū) —— 新對象或者生命周期很短的對象會存儲在這個區(qū)域中线定,這個區(qū)的大小可以通過-XX:NewSize和-XX:MaxNewSize參數(shù)來調(diào)整娜谊。新生代GC(垃圾回收器)會清理這一區(qū)域。
  • Survivor區(qū) —— 那些歷經(jīng)了Eden區(qū)的垃圾回收仍能存活下來的依舊存在引用的對象會待在這個區(qū)域斤讥。這個區(qū)的大小可以由JVM參數(shù)-XX:SurvivorRatio來進行調(diào)節(jié)纱皆。
  • 老年代 —— 那些在歷經(jīng)了Eden區(qū)和Survivor區(qū)的多次GC后仍然存活下來的對象(當然了,是拜那些揮之不去的引用所賜)會存儲在這個區(qū)里芭商。這個區(qū)會由一個特殊的垃圾回收器來負責(zé)派草。年老代中的對象的回收是由老年代的GC(major GC)來進行的。

方法區(qū)

  • 也被稱為非堆區(qū)域(在HotSpot JVM的實現(xiàn)當中)
  • 它被分為兩個主要的子區(qū)域

持久代 —— 這個區(qū)域會 存儲包括類定義蓉坎,結(jié)構(gòu)澳眷,字段,方法(數(shù)據(jù)及代碼)以及常量在內(nèi)的類相關(guān)數(shù)據(jù)蛉艾。它可以通過-XX:PermSize及 -XX:MaxPermSize來進行調(diào)節(jié)钳踊。如果它的空間用完了,會導(dǎo)致java.lang.OutOfMemoryError: PermGen space的異常勿侯。

代碼緩存——這個緩存區(qū)域是用來存儲編譯后的代碼拓瞪。編譯后的代碼就是本地代碼(硬件相關(guān)的),它是由JIT(Just In Time)編譯器生成的助琐,這個編譯器是Oracle HotSpot JVM所特有的祭埂。

JVM棧

  • 和Java類中的方法密切相關(guān)
  • 它會存儲局部變量以及方法調(diào)用的中間結(jié)果及返回值
  • Java中的每個線程都有自己專屬的棧,這個棧是別的線程無法訪問的兵钮。
  • 可以通過JVM選項-Xss來進行調(diào)整

本地棧

  • 用于本地方法(非Java代碼)
  • 按線程分配

PC寄存器

  • 特定線程的程序計數(shù)器
  • 包含JVM正在執(zhí)行的指令的地址(如果是本地方法的話它的值則未定義)

好吧蛆橡,這就是JVM內(nèi)存分區(qū)的基礎(chǔ)知識了。現(xiàn)在再說說持久代這個話題吧掘譬。

對Java內(nèi)存模型的理解以及其在并發(fā)當中的作用

概述

Java平臺自動集成了線程以及多處理器技術(shù)泰演,這種集成程度比Java以前誕生的計算機語言要厲害很多,該語言針對多種異構(gòu)平臺的平臺獨立性而使用的多線程技術(shù)支持也是具有開拓性的一面葱轩,有時候在開發(fā)Java同步和線程安全要求很嚴格的程序時睦焕,往往容易混淆的一個概念就是內(nèi)存模型藐握。究竟什么是內(nèi)存模型?內(nèi)存模型描述了程序中各個變量(實例域垃喊、靜態(tài)域和數(shù)組元素)之間的關(guān)系猾普,以及在實際計算機系統(tǒng)中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細節(jié),對象最終是存儲在內(nèi)存里面的本谜,這點沒有錯初家,但是編譯器、運行庫耕突、處理器或者系統(tǒng)緩存可以有特權(quán)在變量指定內(nèi)存位置存儲或者取出變量的值笤成。【JMM】(Java Memory Model的縮寫)允許編譯器和緩存以數(shù)據(jù)在處理器特定的緩存(或寄存器)和主存之間移動的次序擁有重要的特權(quán)眷茁,除非程序員使用了final或synchronized明確請求了某些可見性的保證。在Java中應(yīng)為不同的目的可以將java劃分為兩種內(nèi)存模型:gc內(nèi)存模型纵诞。并發(fā)內(nèi)存模型上祈。

gc內(nèi)存模型

java與c++之間有一堵由內(nèi)存動態(tài)分配與垃圾收集技術(shù)所圍成的“高墻”。墻外面的人想進去浙芙,墻里面的人想出來登刺。java在執(zhí)行java程序的過程中會把它管理的內(nèi)存劃分若干個不同功能的數(shù)據(jù)管理區(qū)域。如圖:

img
img
img

hotspot中的gc內(nèi)存模型

整體上嗡呼。分為三部分:棧纸俭,堆,程序計數(shù)器南窗,他們每一部分有其各自的用途揍很;虛擬機棧保存著每一條線程的執(zhí)行程序調(diào)用堆棧;堆保存著類對象万伤、數(shù)組的具體信息窒悔;程序計數(shù)器保存著每一條線程下一次執(zhí)行指令位置。這三塊區(qū)域中棧和程序計數(shù)器是線程私有的敌买。也就是說每一個線程擁有其獨立的棧和程序計數(shù)器简珠。我們可以看看具體結(jié)構(gòu):

虛擬機/本地方法棧

在棧中,會為每一個線程創(chuàng)建一個棧虹钮。線程越多聋庵,棧的內(nèi)存使用越大。對于每一個線程棧芙粱。當一個方法在線程中執(zhí)行的時候祭玉,會在線程棧中創(chuàng)建一個棧幀(stack frame),用于存放該方法的上下文(局部變量表宅倒、操作數(shù)棧攘宙、方法返回地址等等)屯耸。每一個方法從調(diào)用到執(zhí)行完畢的過程,就是對應(yīng)著一個棧幀入棧出棧的過程蹭劈。

本地方法棧與虛擬機棧發(fā)揮的作用是類似的鄙陡,他們之間的區(qū)別不過是虛擬機棧為虛擬機執(zhí)行java(字節(jié)碼)服務(wù)的秃症,而本地方法棧是為虛擬機執(zhí)行native方法服務(wù)的。

方法區(qū)/堆

在hotspot的實現(xiàn)中,方法區(qū)就是在堆中稱為永久代的堆區(qū)域躯砰。幾乎所有的對象/數(shù)組的內(nèi)存空間都在堆上(有少部分在棧上)。在gc管理中莺褒,將虛擬機堆分為永久代挖炬、老年代、新生代料仗。通過名字我們可以知道一個對象新建一般在新生代湾盗。經(jīng)過幾輪的gc。還存活的對象會被移到老年代立轧。永久代用來保存類信息格粪、代碼段等幾乎不會變的數(shù)據(jù)。堆中的所有數(shù)據(jù)是線程共享的氛改。

  • 新生代:應(yīng)為gc具體實現(xiàn)的優(yōu)化的原因帐萎。hotspot又將新生代劃分為一個eden區(qū)和兩個survivor區(qū)。每一次新生代gc時候胜卤。只用到一個eden區(qū)疆导,一個survivor區(qū)。新生代一般的gc策略為mark-copy葛躏。
  • 老年代:當新生代中的對象經(jīng)過若干輪gc后還存活/或survisor在gc內(nèi)存不夠的時候澈段。會把當前對象移動到老年代。老年代一般gc策略為mark-compact紫新。
  • 永久代:永久代一般可以不參與gc均蜜。應(yīng)為其中保存的是一些代碼/常量數(shù)據(jù)/類信息。在永久代gc芒率。清楚的是類信息以及常量池囤耳。

JVM內(nèi)存模型中分兩大塊,一塊是 NEW Generation, 另一塊是Old Generation. 在New Generation中偶芍,有一個叫Eden的空間充择,主要是用來存放新生的對象,還有兩個Survivor Spaces(from,to), 它們用來存放每次垃圾回收后存活下來的對象匪蟀。在Old Generation中椎麦,主要存放應(yīng)用程序中生命周期長的內(nèi)存對象,還有個Permanent Generation材彪,主要用來放JVM自己的反射對象观挎,比如類對象和方法對象等琴儿。

程序計數(shù)器

如同其名稱一樣。程序計數(shù)器用于記錄某個線程下次執(zhí)行指令位置嘁捷。程序計數(shù)器也是線程私有的造成。

并發(fā)內(nèi)存模型

java試圖定義一個Java內(nèi)存模型(Java memory model jmm)來屏蔽掉各種硬件/操作系統(tǒng)的內(nèi)存訪問差異,以實現(xiàn)讓java程序在各個平臺下都能達到一致的內(nèi)存訪問效果雄嚣。java內(nèi)存模型主要目標是定義程序中各個變量的訪問規(guī)則晒屎,即在虛擬機中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細節(jié)。模型圖如下:

img

java并發(fā)內(nèi)存模型以及內(nèi)存操作規(guī)則

java內(nèi)存模型中規(guī)定了所有變量都存貯到主內(nèi)存(如虛擬機物理內(nèi)存中的一部分)中缓升。每一個線程都有一個自己的工作內(nèi)存(如cpu中的高速緩存)鼓鲁。線程中的工作內(nèi)存保存了該線程使用到的變量的主內(nèi)存的副本拷貝。線程對變量的所有操作(讀取港谊、賦值等)必須在該線程的工作內(nèi)存中進行骇吭。不同線程之間無法直接訪問對方工作內(nèi)存中變量。線程間變量的值傳遞均需要通過主內(nèi)存來完成歧寺。

關(guān)于主內(nèi)存與工作內(nèi)存之間的交互協(xié)議绵跷,即一個變量如何從主內(nèi)存拷貝到工作內(nèi)存。如何從工作內(nèi)存同步到主內(nèi)存中的實現(xiàn)細節(jié)成福。java內(nèi)存模型定義了8種操作來完成。這8種操作每一種都是原子操作荆残。8種操作如下:

  • lock(鎖定):作用于主內(nèi)存奴艾,它把一個變量標記為一條線程獨占狀態(tài);
  • unlock(解鎖):作用于主內(nèi)存内斯,它將一個處于鎖定狀態(tài)的變量釋放出來蕴潦,釋放后的變量才能夠被其他線程鎖定;
  • read(讀取):作用于主內(nèi)存俘闯,它把變量值從主內(nèi)存?zhèn)魉偷骄€程的工作內(nèi)存中潭苞,以便隨后的load動作使用;
  • load(載入):作用于工作內(nèi)存真朗,它把read操作的值放入工作內(nèi)存中的變量副本中此疹;
  • use(使用):作用于工作內(nèi)存,它把工作內(nèi)存中的值傳遞給執(zhí)行引擎遮婶,每當虛擬機遇到一個需要使用這個變量的指令時候蝗碎,將會執(zhí)行這個動作;
  • assign(賦值):作用于工作內(nèi)存旗扑,它把從執(zhí)行引擎獲取的值賦值給工作內(nèi)存中的變量蹦骑,每當虛擬機遇到一個給變量賦值的指令時候,執(zhí)行該操作臀防;
  • store(存儲):作用于工作內(nèi)存眠菇,它把工作內(nèi)存中的一個變量傳送給主內(nèi)存中边败,以備隨后的write操作使用;
  • write(寫入):作用于主內(nèi)存捎废,它把store傳送值放到主內(nèi)存中的變量中笑窜。

Java內(nèi)存模型還規(guī)定了執(zhí)行上述8種基本操作時必須滿足如下規(guī)則:

  • 不允許read和load、store和write操作之一單獨出現(xiàn)缕坎,以上兩個操作必須按順序執(zhí)行怖侦,但沒有保證必須連續(xù)執(zhí)行,也就是說谜叹,read與load之間匾寝、store與write之間是可插入其他指令的。
  • 不允許一個線程丟棄它的最近的assign操作荷腊,即變量在工作內(nèi)存中改變了之后必須把該變化同步回主內(nèi)存艳悔。
  • 不允許一個線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從線程的工作內(nèi)存同步回主內(nèi)存中。
  • 一個新的變量只能從主內(nèi)存中“誕生”女仰,不允許在工作內(nèi)存中直接使用一個未被初始化(load或assign)的變量猜年,換句話說就是對一個變量實施use和store操作之前,必須先執(zhí)行過了assign和load操作疾忍。
  • 一個變量在同一個時刻只允許一條線程對其執(zhí)行l(wèi)ock操作乔外,但lock操作可以被同一個條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后一罩,只有執(zhí)行相同次數(shù)的unlock操作杨幼,變量才會被解鎖。
  • 如果對一個變量執(zhí)行l(wèi)ock操作聂渊,將會清空工作內(nèi)存中此變量的值差购,在執(zhí)行引擎使用這個變量前,需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值汉嗽。
  • 如果一個變量實現(xiàn)沒有被lock操作鎖定欲逃,則不允許對它執(zhí)行unlock操作,也不允許去unlock一個被其他線程鎖定的變量饼暑。
  • 對一個變量執(zhí)行unlock操作之前稳析,必須先把此變量同步回主內(nèi)存(執(zhí)行store和write操作)。

volatile型變量的特殊規(guī)則

關(guān)鍵字volatile可以說是Java虛擬機提供的最輕量級的同步機制撵孤,但是它并不容易完全被正確迈着、完整的理解,以至于許多程序員都不習(xí)慣去使用它邪码,遇到需要處理多線程的問題的時候一律使用synchronized來進行同步裕菠。了解volatile變量的語義對后面了解多線程操作的其他特性很有意義。Java內(nèi)存模型對volatile專門定義了一些特殊的訪問規(guī)則闭专,當一個變量被定義成volatile之后奴潘,他將具備兩種特性:

  • 保證此變量對所有線程的可見性旧烧。第一保證此變量對所有線程的可見性,這里的“可見性”是指當一條線程修改了這個變量的值画髓,新值對于其他線程來說是可以立即得知的掘剪。而普通變量是做不到這點,普通變量的值在線程在線程間傳遞均需要通過住內(nèi)存來完成奈虾,例如夺谁,線程A修改一個普通變量的值,然后向主內(nèi)存進行會寫肉微,另外一個線程B在線程A回寫完成了之后再從主內(nèi)存進行讀取操作匾鸥,新變量值才會對線程B可見。另外碉纳,java里面的運算并非原子操作勿负,會導(dǎo)致volatile變量的運算在并發(fā)下一樣是不安全的。
  • 禁止指令重排序優(yōu)化劳曹。普通的變量僅僅會保證在該方法的執(zhí)行過程中所有依賴賦值結(jié)果的地方都能獲得正確的結(jié)果奴愉,而不能保證變量賦值操作的順序與程序中的執(zhí)行順序一致,在單線程中铁孵,我們是無法感知這一點的锭硼。

由于volatile變量只能保證可見性,在不符合以下兩條規(guī)則的運算場景中蜕劝,我們?nèi)匀灰ㄟ^加鎖來保證原子性账忘。

  • 1.運算結(jié)果并不依賴變量的當前值,或者能夠確保只有單一的線程修改變量的值熙宇。
  • 2.變量不需要與其他的狀態(tài)比阿尼浪共同參與不變約束。

原子性溉浙、可見性與有序性

Java內(nèi)存模型是圍繞著在并發(fā)過程中如何處理原子性烫止、可見性和有序性這三個特征來建立的,我們逐個看下哪些操作實現(xiàn)了這三個特性戳稽。

  • 原子性(Atomicity):由Java內(nèi)存模型來直接保證的原子性變量包括read馆蠕、load、assign惊奇、use互躬、store和write,我們大致可以認為基本數(shù)據(jù)類型的訪問讀寫是具備原子性的颂郎。如果應(yīng)用場景需要一個更大方位的原子性保證吼渡,Java內(nèi)存模型還提供了lock和unlock操作來滿足這種需求,盡管虛擬機未把lock和unlock操作直接開放給用戶使用乓序,但是卻提供了更高層次的字節(jié)碼指令monitorenter和monitorexit來隱式的使用這兩個操作寺酪,這兩個字節(jié)碼指令反應(yīng)到Java代碼中就是同步塊--synchronized關(guān)鍵字坎背,因此在synchronized塊之間的操作也具備原子性。
  • 可見性(Visibility):可見性是指當一個線程修改了共享變量的值寄雀,其他線程能夠立即得知這個修改得滤。上文在講解volatile變量的時候我們已詳細討論過這一點。Java內(nèi)存模型是通過在變量修改后將新值同步回主內(nèi)存盒犹,在變量讀取前從主內(nèi)存刷新變量值這種依賴主內(nèi)存作為傳遞媒介的方式來實現(xiàn)可見性的懂更,無論是普通變量還是volatile變量都是如此,普通變量與volatile變量的區(qū)別是急膀,volatile的特殊規(guī)則保證了新值能立即同步到主內(nèi)存沮协,以及每次使用前立即從主內(nèi)存刷新。因此脖阵,可以說volatile保證了多線程操作時變量的可見性皂股,而普通變量則不能保證這一點。除了volatile之外命黔,Java還有兩個關(guān)鍵字能實現(xiàn)可見性呜呐,即synchronized和final.同步快的可見性是由“對一個變量執(zhí)行unlock操作前,必須先把此變量同步回主內(nèi)存”這條規(guī)則獲得的悍募,而final關(guān)鍵字的可見性是指:被final修飾的字段在構(gòu)造器中一旦初始化完成蘑辑,并且構(gòu)造器沒有把"this"的引用傳遞出去,那么在其他線程中就能看見final字段的值坠宴。
  • 有序性(Ordering):Java內(nèi)存模型的有序性在前面講解volatile時也詳細的討論過了洋魂,Java程序中天然的有序性可以總結(jié)為一句話:如果在本線程內(nèi)觀察,所有的操作都是有序的:如果在一個線程中觀察另外一個線程喜鼓,所有的線程操作都是無序的副砍。前半句是指“線程內(nèi)表現(xiàn)為串行的語義”,后半句是指“指令重排序”現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象庄岖。Java語言提供了volatile和synchronized兩個關(guān)鍵字來保證線程之間操作的有序性豁翎,volatile關(guān)鍵字本身就包含了禁止指令重排序的語義,而synchronized則是由“一個變量在同一個時刻只允許一條線程對其進行l(wèi)ock操作”這條規(guī)則獲得的隅忿,這條規(guī)則決定了持有同一個鎖的兩個同步塊只能串行的進入心剥。

Arrays和Collections 對于sort的不同實現(xiàn)原理

1、Arrays.sort()
該算法是一個經(jīng)過調(diào)優(yōu)的快速排序背桐,此算法在很多數(shù)據(jù)集上提供N*log(N)的性能优烧,這導(dǎo)致其他快速排序會降低二次型性能。

2链峭、Collections.sort()
該算法是一個經(jīng)過修改的合并排序算法(其中畦娄,如果低子列表中的最高元素效益高子列表中的最低元素,則忽略合并)。此算法可提供保證的N*log(N)的性能纷责,此實現(xiàn)將指定列表轉(zhuǎn)儲到一個數(shù)組中捍掺,然后再對數(shù)組進行排序,在重置數(shù)組中相應(yīng)位置處每個元素的列表上進行迭代再膳。這避免了由于試圖原地對鏈接列表進行排序而產(chǎn)生的n2log(n)性能挺勿。

Java中object常用方法

1瓦糕、clone()
2烟阐、equals()
3、finalize()
4枚粘、getclass()
5灾杰、hashcode()
6蚊丐、notify()
7、notifyAll()
8艳吠、toString()

對于Java中多態(tài)的理解

所謂多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時并不確定麦备,而是在程序運行期間才確定,即一個引用變量到底會指向哪個類的實例對象昭娩,該引用變量發(fā)出的方法調(diào)用到底是哪個類中實現(xiàn)的方法凛篙,必須在由程序運行期間才能決定。因為在程序運行時才確定具體的類栏渺,這樣呛梆,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實現(xiàn)上磕诊,從而導(dǎo)致該引用調(diào)用的具體方法隨之改變填物,即不修改程序代碼就可以改變程序運行時所綁定的具體代碼,讓程序可以選擇多個運行狀態(tài)霎终,這就是多態(tài)性滞磺。

多態(tài)的定義:指允許不同類的對象對同一消息做出響應(yīng)。即同一消息可以根據(jù)發(fā)送對象的不同而采用多種不同的行為方式莱褒。(發(fā)送消息就是函數(shù)調(diào)用)

Java實現(xiàn)多態(tài)有三個必要條件:繼承雁刷、重寫、父類引用指向子類對象保礼。

繼承:在多態(tài)中必須存在有繼承關(guān)系的子類和父類。

重寫:子類對父類中某些方法進行重新定義责语,在調(diào)用這些方法時就會調(diào)用子類的方法炮障。

父類引用指向子類對象:在多態(tài)中需要將子類的引用賦給父類對象,只有這樣該引用才能夠具備技能調(diào)用父類的方法和子類的方法坤候。

實現(xiàn)多態(tài)的技術(shù)稱為:動態(tài)綁定(dynamic binding)胁赢,是指在執(zhí)行期間判斷所引用對象的實際類型,根據(jù)其實際的類型調(diào)用其相應(yīng)的方法白筹。

多態(tài)的作用:消除類型之間的耦合關(guān)系智末。

Java序列化與反序列化是什么谅摄?為什么需要序列化與反序列化?如何實現(xiàn)Java序列化與反序列化

spring AOP 實現(xiàn)原理

什么是AOP

AOP(Aspect-OrientedProgramming系馆,面向方面編程)送漠,可以說是OOP(Object-Oriented Programing,面向?qū)ο缶幊蹋┑难a充和完善由蘑。OOP引入封裝闽寡、繼承和多態(tài)性等概念來建立一種對象層次結(jié)構(gòu),用以模擬公共行為的一個集合尼酿。當我們需要為分散的對象引入公共行為的時候爷狈,OOP則顯得無能為力。也就是說裳擎,OOP允許你定義從上到下的關(guān)系涎永,但并不適合定義從左到右的關(guān)系。例如日志功能鹿响。日志代碼往往水平地散布在所有對象層次中羡微,而與它所散布到的對象的核心功能毫無關(guān)系。對于其他類型的代碼抢野,如安全性拷淘、異常處理和透明的持續(xù)性也是如此。這種散布在各處的無關(guān)的代碼被稱為橫切(cross-cutting)代碼指孤,在OOP設(shè)計中启涯,它導(dǎo)致了大量代碼的重復(fù),而不利于各個模塊的重用恃轩。

而AOP技術(shù)則恰恰相反结洼,它利用一種稱為“橫切”的技術(shù),剖解開封裝的對象內(nèi)部叉跛,并將那些影響了多個類的公共行為封裝到一個可重用模塊松忍,并將其名為“Aspect”,即方面筷厘。所謂“方面”鸣峭,簡單地說,就是將那些與業(yè)務(wù)無關(guān)酥艳,卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任封裝起來摊溶,便于減少系統(tǒng)的重復(fù)代碼,降低模塊間的耦合度充石,并有利于未來的可操作性和可維護性莫换。AOP代表的是一個橫向的關(guān)系,如果說“對象”是一個空心的圓柱體,其中封裝的是對象的屬性和行為拉岁;那么面向方面編程的方法坷剧,就仿佛一把利刃,將這些空心圓柱體剖開喊暖,以獲得其內(nèi)部的消息惫企。而剖開的切面,也就是所謂的“方面”了哄啄。然后它又以巧奪天功的妙手將這些剖開的切面復(fù)原雅任,不留痕跡。

使用“橫切”技術(shù)咨跌,AOP把軟件系統(tǒng)分為兩個部分:核心關(guān)注點和橫切關(guān)注點沪么。業(yè)務(wù)處理的主要流程是核心關(guān)注點,與之關(guān)系不大的部分是橫切關(guān)注點锌半。橫切關(guān)注點的一個特點是禽车,他們經(jīng)常發(fā)生在核心關(guān)注點的多處,而各處都基本相似刊殉。比如權(quán)限認證殉摔、日志、事務(wù)處理记焊。Aop 的作用在于分離系統(tǒng)中的各種關(guān)注點逸月,將核心關(guān)注點和橫切關(guān)注點分離開來。正如Avanade公司的高級方案構(gòu)架師Adam Magee所說遍膜,AOP的核心思想就是“將應(yīng)用程序中的商業(yè)邏輯同對其提供支持的通用服務(wù)進行分離碗硬。”

實現(xiàn)AOP的技術(shù)瓢颅,主要分為兩大類:一是采用動態(tài)代理技術(shù)恩尾,利用截取消息的方式,對該消息進行裝飾挽懦,以取代原有對象行為的執(zhí)行翰意;二是采用靜態(tài)織入的方式,引入特定的語法創(chuàng)建“方面”信柿,從而使得編譯器可以在編譯期間織入有關(guān)“方面”的代碼冀偶。

AOP使用場景

AOP用來封裝橫切關(guān)注點,具體可以在下面的場景中使用:

Authentication 權(quán)限

Caching 緩存

Context passing 內(nèi)容傳遞

Error handling 錯誤處理

Lazy loading 懶加載

Debugging  調(diào)試

logging, tracing, profiling and monitoring 記錄跟蹤 優(yōu)化 校準

Performance optimization 性能優(yōu)化

Persistence  持久化

Resource pooling 資源池

Synchronization 同步

Transactions 事務(wù)

AOP相關(guān)概念

方面(Aspect):一個關(guān)注點的模塊化渔嚷,這個關(guān)注點實現(xiàn)可能另外橫切多個對象进鸠。事務(wù)管理是J2EE應(yīng)用中一個很好的橫切關(guān)注點例子。方面用spring的 Advisor或攔截器實現(xiàn)圃伶。

連接點(Joinpoint): 程序執(zhí)行過程中明確的點,如方法的調(diào)用或特定的異常被拋出。

通知(Advice): 在特定的連接點窒朋,AOP框架執(zhí)行的動作搀罢。各種類型的通知包括“around”、“before”和“throws”通知侥猩。通知類型將在下面討論榔至。許多AOP框架包括Spring都是以攔截器做通知模型,維護一個“圍繞”連接點的攔截器鏈欺劳。Spring中定義了四個advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice

切入點(Pointcut): 指定一個通知將被引發(fā)的一系列連接點的集合唧取。AOP框架必須允許開發(fā)者指定切入點:例如,使用正則表達式划提。 Spring定義了Pointcut接口枫弟,用來組合MethodMatcher和ClassFilter,可以通過名字很清楚的理解鹏往, MethodMatcher是用來檢查目標類的方法是否可以被應(yīng)用此通知淡诗,而ClassFilter是用來檢查Pointcut是否應(yīng)該應(yīng)用到目標類上

引入(Introduction): 添加方法或字段到被通知的類。 Spring允許引入新的接口到任何被通知的對象伊履。例如韩容,你可以使用一個引入使任何對象實現(xiàn) IsModified接口,來簡化緩存唐瀑。Spring中要使用Introduction, 可有通過DelegatingIntroductionInterceptor來實現(xiàn)通知群凶,通過DefaultIntroductionAdvisor來配置Advice和代理類要實現(xiàn)的接口

目標對象(Target Object): 包含連接點的對象。也被稱作被通知或被代理對象哄辣。POJO

AOP代理(AOP Proxy): AOP框架創(chuàng)建的對象请梢,包含通知。 在Spring中柔滔,AOP代理可以是JDK動態(tài)代理或者CGLIB代理溢陪。

織入(Weaving): 組裝方面來創(chuàng)建一個被通知對象。這可以在編譯時完成(例如使用AspectJ編譯器)睛廊,也可以在運行時完成形真。Spring和其他純Java AOP框架一樣,在運行時完成織入超全。

Spring AOP組件

下面這種類圖列出了Spring中主要的AOP組件

img

如何使用Spring AOP

可以通過配置文件或者編程的方式來使用Spring AOP咆霜。

配置可以通過xml文件來進行,大概有四種方式:

\1. 配置ProxyFactoryBean嘶朱,顯式地設(shè)置advisors, advice, target等

  1.    配置AutoProxyCreator蛾坯,這種方式下,還是如以前一樣使用定義的bean疏遏,但是從容器中獲得的其實已經(jīng)是代理對象
    
  2.    通過<aop:config>來配置
    
  3.    通過<aop: aspectj-autoproxy>來配置脉课,使用AspectJ的注解來標識通知及切入點
    

也可以直接使用ProxyFactory來以編程的方式使用Spring AOP救军,通過ProxyFactory提供的方法可以設(shè)置target對象, advisor等相關(guān)配置,最終通過 getProxy()方法來獲取代理對象

具體使用的示例可以google. 這里略去

Spring AOP代理對象的生成

Spring提供了兩種方式來生成代理對象: JDKProxy和Cglib倘零,具體使用哪種方式生成由AopProxyFactory根據(jù)AdvisedSupport對象的配置來決定唱遭。默認的策略是如果目標類是接口,則使用JDK動態(tài)代理技術(shù)呈驶,否則使用Cglib來生成代理拷泽。下面我們來研究一下Spring如何使用JDK來生成代理對象,具體的生成代碼放在JdkDynamicAopProxy這個類中袖瞻,直接上相關(guān)代碼:

友情鏈接 :Spring AOP 實現(xiàn)原理

/**
   * <ol>
   * <li>獲取代理類要實現(xiàn)的接口,除了Advised對象中配置的,還會加上SpringProxy, Advised(opaque=false)
   * <li>檢查上面得到的接口中有沒有定義 equals或者hashcode的接口
   * <li>調(diào)用Proxy.newProxyInstance創(chuàng)建代理對象
   * </ol>
   */
  public Object getProxy(ClassLoader classLoader) {
      if (logger.isDebugEnabled()) {
          logger.debug("Creating JDK dynamic proxy: target source is " +this.advised.getTargetSource());
      }
      Class[] proxiedInterfaces =AopProxyUtils.completeProxiedInterfaces(this.advised);
      findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
      return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

那這個其實很明了司致,注釋上我也已經(jīng)寫清楚了,不再贅述聋迎。

下面的問題是脂矫,代理對象生成了,那切面是如何織入的砌庄?

我們知道InvocationHandler是JDK動態(tài)代理的核心羹唠,生成的代理對象的方法調(diào)用都會委托到InvocationHandler.invoke()方法。而通過JdkDynamicAopProxy的簽名我們可以看到這個類其實也實現(xiàn)了InvocationHandler娄昆,下面我們就通過分析這個類中實現(xiàn)的invoke()方法來具體看下Spring AOP是如何織入切面的佩微。


Servlet 工作原理

Servlet 工作原理解析

從 Servlet 容器說起

前面說了 Servlet 容器作為一個獨立發(fā)展的標準化產(chǎn)品,目前它的種類很多萌焰,但是它們都有自己的市場定位哺眯,很難說誰優(yōu)誰劣,各有特點扒俯。例如現(xiàn)在比較流行的 Jetty奶卓,在定制化和移動領(lǐng)域有不錯的發(fā)展,我們這里還是以大家最為熟悉 Tomcat 為例來介紹 Servlet 容器如何管理 Servlet撼玄。Tomcat 本身也很復(fù)雜夺姑,我們只從 Servlet 與 Servlet 容器的接口部分開始介紹,關(guān)于 Tomcat 的詳細介紹可以參考我的另外一篇文章《 Tomcat 系統(tǒng)架構(gòu)與模式設(shè)計分析》掌猛。

Tomcat 的容器等級中盏浙,Context 容器是直接管理 Servlet 在容器中的包裝類 Wrapper,所以 Context 容器如何運行將直接影響 Servlet 的工作方式荔茬。

圖 1 . Tomcat 容器模型

從上圖可以看出 Tomcat 的容器分為四個等級废膘,真正管理 Servlet 的容器是 Context 容器,一個 Context 對應(yīng)一個 Web 工程慕蔚,在 Tomcat 的配置文件中可以很容易發(fā)現(xiàn)這一點丐黄,如下:

清單 1 Context 配置參數(shù)
<Context path="/projectOne " docBase="D:\projects\projectOne" 
reloadable="true" />

下面詳細介紹一下 Tomcat 解析 Context 容器的過程,包括如何構(gòu)建 Servlet 的過程孔飒。

Servlet 容器的啟動過程

Tomcat7 也開始支持嵌入式功能灌闺,增加了一個啟動類 org.apache.catalina.startup.Tomcat艰争。創(chuàng)建一個實例對象并調(diào)用 start 方法就可以很容易啟動 Tomcat,我們還可以通過這個對象來增加和修改 Tomcat 的配置參數(shù)桂对,如可以動態(tài)增加 Context园细、Servlet 等。下面我們就利用這個 Tomcat 類來管理新增的一個 Context 容器接校,我們就選擇 Tomcat7 自帶的 examples Web 工程,并看看它是如何加到這個 Context 容器中的狮崩。

清單 2 . 給 Tomcat 增加一個 Web 工程
Tomcat tomcat = getTomcatInstance(); 
File appDir = new File(getBuildDirectory(), "webapps/examples"); 
tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath()); 
tomcat.start(); 
ByteChunk res = getUrl("http://localhost:" + getPort() + 
              "/examples/servlets/servlet/HelloWorldExample"); 
assertTrue(res.toString().indexOf("<h1>Hello World!</h1>") > 0);

清單 1 的代碼是創(chuàng)建一個 Tomcat 實例并新增一個 Web 應(yīng)用蛛勉,然后啟動 Tomcat 并調(diào)用其中的一個 HelloWorldExample Servlet,看有沒有正確返回預(yù)期的數(shù)據(jù)睦柴。

Tomcat 的 addWebapp 方法的代碼如下:

清單 3 .Tomcat.addWebapp
public Context addWebapp(Host host, String url, String path) { 
       silence(url); 
       Context ctx = new StandardContext(); 
       ctx.setPath( url ); 
       ctx.setDocBase(path); 
       if (defaultRealm == null) { 
           initSimpleAuth(); 
       } 
       ctx.setRealm(defaultRealm); 
       ctx.addLifecycleListener(new DefaultWebXmlListener()); 
       ContextConfig ctxCfg = new ContextConfig(); 
       ctx.addLifecycleListener(ctxCfg); 
       ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML"); 
       if (host == null) { 
           getHost().addChild(ctx); 
       } else { 
           host.addChild(ctx); 
       } 
       return ctx; 
}

前面已經(jīng)介紹了一個 Web 應(yīng)用對應(yīng)一個 Context 容器诽凌,也就是 Servlet 運行時的 Servlet 容器,添加一個 Web 應(yīng)用時將會創(chuàng)建一個 StandardContext 容器坦敌,并且給這個 Context 容器設(shè)置必要的參數(shù)侣诵,url 和 path 分別代表這個應(yīng)用在 Tomcat 中的訪問路徑和這個應(yīng)用實際的物理路徑,這個兩個參數(shù)與清單 1 中的兩個參數(shù)是一致的狱窘。其中最重要的一個配置是 ContextConfig,這個類將會負責(zé)整個 Web 應(yīng)用配置的解析工作,后面將會詳細介紹劲够。最后將這個 Context 容器加到父容器 Host 中聊疲。

接下去將會調(diào)用 Tomcat 的 start 方法啟動 Tomcat,如果你清楚 Tomcat 的系統(tǒng)架構(gòu)搭儒,你會容易理解 Tomcat 的啟動邏輯穷当,Tomcat 的啟動邏輯是基于觀察者模式設(shè)計的,所有的容器都會繼承 Lifecycle 接口淹禾,它管理者容器的整個生命周期馁菜,所有容器的的修改和狀態(tài)的改變都會由它去通知已經(jīng)注冊的觀察者(Listener),關(guān)于這個設(shè)計模式可以參考《 Tomcat 的系統(tǒng)架構(gòu)與設(shè)計模式铃岔,第二部分:設(shè)計模式》汪疮。Tomcat 啟動的時序圖可以用圖 2 表示。

圖 2. Tomcat 主要類的啟動時序圖(查看大圖

上圖描述了 Tomcat 啟動過程中德撬,主要類之間的時序關(guān)系铲咨,下面我們將會重點關(guān)注添加 examples 應(yīng)用所對應(yīng)的 StandardContext 容器的啟動過程。

當 Context 容器初始化狀態(tài)設(shè)為 init 時蜓洪,添加在 Contex 容器的 Listener 將會被調(diào)用纤勒。ContextConfig 繼承了 LifecycleListener 接口,它是在調(diào)用清單 3 時被加入到 StandardContext 容器中隆檀。ContextConfig 類會負責(zé)整個 Web 應(yīng)用的配置文件的解析工作摇天。

ContextConfig 的 init 方法將會主要完成以下工作:

  1. 創(chuàng)建用于解析 xml 配置文件的 contextDigester 對象
  2. 讀取默認 context.xml 配置文件粹湃,如果存在解析它
  3. 讀取默認 Host 配置文件,如果存在解析它
  4. 讀取默認 Context 自身的配置文件泉坐,如果存在解析它
  5. 設(shè)置 Context 的 DocBase

ContextConfig 的 init 方法完成后为鳄,Context 容器的會執(zhí)行 startInternal 方法,這個方法啟動邏輯比較復(fù)雜腕让,主要包括如下幾個部分:

  1. 創(chuàng)建讀取資源文件的對象
  2. 創(chuàng)建 ClassLoader 對象
  3. 設(shè)置應(yīng)用的工作目錄
  4. 啟動相關(guān)的輔助類如:logger孤钦、realm、resources 等
  5. 修改啟動狀態(tài)纯丸,通知感興趣的觀察者(Web 應(yīng)用的配置)
  6. 子容器的初始化
  7. 獲取 ServletContext 并設(shè)置必要的參數(shù)
  8. 初始化“l(fā)oad on startup”的 Servlet

Web 應(yīng)用的初始化工作

Web 應(yīng)用的初始化工作是在 ContextConfig 的 configureStart 方法中實現(xiàn)的偏形,應(yīng)用的初始化主要是要解析 web.xml 文件,這個文件描述了一個 Web 應(yīng)用的關(guān)鍵信息觉鼻,也是一個 Web 應(yīng)用的入口俊扭。

Tomcat 首先會找 globalWebXml 這個文件的搜索路徑是在 engine 的工作目錄下尋找以下兩個文件中的任一個 org/apache/catalin/startup/NO_DEFAULT_XML 或 conf/web.xml。接著會找 hostWebXml 這個文件可能會在 System.getProperty("catalina.base")/conf/${EngineName}/${HostName}/web.xml.default坠陈,接著尋找應(yīng)用的配置文件 examples/WEB-INF/web.xml萨惑。web.xml 文件中的各個配置項將會被解析成相應(yīng)的屬性保存在 WebXml 對象中。如果當前應(yīng)用支持 Servlet3.0仇矾,解析還將完成額外 9 項工作庸蔼,這個額外的 9 項工作主要是為 Servlet3.0 新增的特性,包括 jar 包中的 META-INF/web-fragment.xml 的解析以及對 annotations 的支持贮匕。

接下去將會將 WebXml 對象中的屬性設(shè)置到 Context 容器中朱嘴,這里包括創(chuàng)建 Servlet 對象、filter粗合、listener 等等萍嬉。這段代碼在 WebXml 的 configureContext 方法中。下面是解析 Servlet 的代碼片段:

清單 4. 創(chuàng)建 Wrapper 實例
for (ServletDef servlet : servlets.values()) { 
           Wrapper wrapper = context.createWrapper(); 
           String jspFile = servlet.getJspFile(); 
           if (jspFile != null) { 
               wrapper.setJspFile(jspFile); 
           } 
           if (servlet.getLoadOnStartup() != null) { 
               wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue()); 
           } 
           if (servlet.getEnabled() != null) { 
               wrapper.setEnabled(servlet.getEnabled().booleanValue()); 
           } 
           wrapper.setName(servlet.getServletName()); 
           Map<String,String> params = servlet.getParameterMap(); 
           for (Entry<String, String> entry : params.entrySet()) { 
               wrapper.addInitParameter(entry.getKey(), entry.getValue()); 
           } 
           wrapper.setRunAs(servlet.getRunAs()); 
           Set<SecurityRoleRef> roleRefs = servlet.getSecurityRoleRefs(); 
           for (SecurityRoleRef roleRef : roleRefs) { 
               wrapper.addSecurityReference( 
                       roleRef.getName(), roleRef.getLink()); 
           } 
           wrapper.setServletClass(servlet.getServletClass()); 
           MultipartDef multipartdef = servlet.getMultipartDef(); 
           if (multipartdef != null) { 
               if (multipartdef.getMaxFileSize() != null && 
                       multipartdef.getMaxRequestSize()!= null && 
                       multipartdef.getFileSizeThreshold() != null) { 
                   wrapper.setMultipartConfigElement(new 
MultipartConfigElement( 
                           multipartdef.getLocation(), 
                           Long.parseLong(multipartdef.getMaxFileSize()), 
                           Long.parseLong(multipartdef.getMaxRequestSize()), 
                           Integer.parseInt( 
                                   multipartdef.getFileSizeThreshold()))); 
               } else { 
                   wrapper.setMultipartConfigElement(new 
MultipartConfigElement( 
                           multipartdef.getLocation())); 
               } 
           } 
           if (servlet.getAsyncSupported() != null) { 
               wrapper.setAsyncSupported( 
                       servlet.getAsyncSupported().booleanValue()); 
           } 
           context.addChild(wrapper); 
}

這段代碼清楚的描述了如何將 Servlet 包裝成 Context 容器中的 StandardWrapper隙疚,這里有個疑問壤追,為什么要將 Servlet 包裝成 StandardWrapper 而不直接是 Servlet 對象。這里 StandardWrapper 是 Tomcat 容器中的一部分供屉,它具有容器的特征行冰,而 Servlet 為了一個獨立的 web 開發(fā)標準,不應(yīng)該強耦合在 Tomcat 中伶丐。

除了將 Servlet 包裝成 StandardWrapper 并作為子容器添加到 Context 中悼做,其它的所有 web.xml 屬性都被解析到 Context 中,所以說 Context 容器才是真正運行 Servlet 的 Servlet 容器哗魂。一個 Web 應(yīng)用對應(yīng)一個 Context 容器肛走,容器的配置屬性由應(yīng)用的 web.xml 指定,這樣我們就能理解 web.xml 到底起到什么作用了录别。

回頁首

創(chuàng)建 Servlet 實例

前面已經(jīng)完成了 Servlet 的解析工作朽色,并且被包裝成 StandardWrapper 添加在 Context 容器中邻吞,但是它仍然不能為我們工作,它還沒有被實例化葫男。下面我們將介紹 Servlet 對象是如何創(chuàng)建的抱冷,以及如何被初始化的。

創(chuàng)建 Servlet 對象

如果 Servlet 的 load-on-startup 配置項大于 0梢褐,那么在 Context 容器啟動的時候就會被實例化旺遮,前面提到在解析配置文件時會讀取默認的 globalWebXml,在 conf 下的 web.xml 文件中定義了一些默認的配置項盈咳,其定義了兩個 Servlet趣效,分別是:org.apache.catalina.servlets.DefaultServlet 和 org.apache.jasper.servlet.JspServlet 它們的 load-on-startup 分別是 1 和 3,也就是當 Tomcat 啟動時這兩個 Servlet 就會被啟動猪贪。

創(chuàng)建 Servlet 實例的方法是從 Wrapper. loadServlet 開始的。loadServlet 方法要完成的就是獲取 servletClass 然后把它交給 InstanceManager 去創(chuàng)建一個基于 servletClass.class 的對象讯私。如果這個 Servlet 配置了 jsp-file热押,那么這個 servletClass 就是 conf/web.xml 中定義的 org.apache.jasper.servlet.JspServlet 了。

創(chuàng)建 Servlet 對象的相關(guān)類結(jié)構(gòu)圖如下:

圖 3. 創(chuàng)建 Servlet 對象的相關(guān)類結(jié)構(gòu)

初始化 Servlet

初始化 Servlet 在 StandardWrapper 的 initServlet 方法中斤寇,這個方法很簡單就是調(diào)用 Servlet 的 init 的方法桶癣,同時把包裝了 StandardWrapper 對象的 StandardWrapperFacade 作為 ServletConfig 傳給 Servlet。Tomcat 容器為何要傳 StandardWrapperFacade 給 Servlet 對象將在后面做詳細解析娘锁。

如果該 Servlet 關(guān)聯(lián)的是一個 jsp 文件牙寞,那么前面初始化的就是 JspServlet,接下去會模擬一次簡單請求莫秆,請求調(diào)用這個 jsp 文件间雀,以便編譯這個 jsp 文件為 class,并初始化這個 class镊屎。

這樣 Servlet 對象就初始化完成了惹挟,事實上 Servlet 從被 web.xml 中解析到完成初始化,這個過程非常復(fù)雜缝驳,中間有很多過程连锯,包括各種容器狀態(tài)的轉(zhuǎn)化引起的監(jiān)聽事件的觸發(fā)、各種訪問權(quán)限的控制和一些不可預(yù)料的錯誤發(fā)生的判斷行為等等用狱。我們這里只抓了一些關(guān)鍵環(huán)節(jié)進行闡述运怖,試圖讓大家有個總體脈絡(luò)。

下面是這個過程的一個完整的時序圖夏伊,其中也省略了一些細節(jié)摇展。

圖 4. 初始化 Servlet 的時序圖(查看大圖

回頁首

Servlet 體系結(jié)構(gòu)

我們知道 Java Web 應(yīng)用是基于 Servlet 規(guī)范運轉(zhuǎn)的,那么 Servlet 本身又是如何運轉(zhuǎn)的呢溺忧?為何要設(shè)計這樣的體系結(jié)構(gòu)吗购。

圖 5.Servlet 頂層類關(guān)聯(lián)圖

從上圖可以看出 Servlet 規(guī)范就是基于這幾個類運轉(zhuǎn)的医男,與 Servlet 主動關(guān)聯(lián)的是三個類,分別是 ServletConfig捻勉、ServletRequest 和 ServletResponse镀梭。這三個類都是通過容器傳遞給 Servlet 的,其中 ServletConfig 是在 Servlet 初始化時就傳給 Servlet 了踱启,而后兩個是在請求達到時調(diào)用 Servlet 時傳遞過來的报账。我們很清楚 ServletRequest 和 ServletResponse 在 Servlet 運行的意義,但是 ServletConfig 和 ServletContext 對 Servlet 有何價值埠偿?仔細查看 ServletConfig 接口中聲明的方法發(fā)現(xiàn)透罢,這些方法都是為了獲取這個 Servlet 的一些配置屬性,而這些配置屬性可能在 Servlet 運行時被用到冠蒋。而 ServletContext 又是干什么的呢羽圃? Servlet 的運行模式是一個典型的“握手型的交互式”運行模式。所謂“握手型的交互式”就是兩個模塊為了交換數(shù)據(jù)通常都會準備一個交易場景抖剿,這個場景一直跟隨個這個交易過程直到這個交易完成為止朽寞。這個交易場景的初始化是根據(jù)這次交易對象指定的參數(shù)來定制的,這些指定參數(shù)通常就會是一個配置類斩郎。所以對號入座脑融,交易場景就由 ServletContext 來描述,而定制的參數(shù)集合就由 ServletConfig 來描述缩宜。而 ServletRequest 和 ServletResponse 就是要交互的具體對象了肘迎,它們通常都是作為運輸工具來傳遞交互結(jié)果。

ServletConfig 是在 Servlet init 時由容器傳過來的锻煌,那么 ServletConfig 到底是個什么對象呢妓布?

下圖是 ServletConfig 和 ServletContext 在 Tomcat 容器中的類關(guān)系圖。

圖 6. ServletConfig 在容器中的類關(guān)聯(lián)圖

上圖可以看出 StandardWrapper 和 StandardWrapperFacade 都實現(xiàn)了 ServletConfig 接口宋梧,而 StandardWrapperFacade 是 StandardWrapper 門面類秋茫。所以傳給 Servlet 的是 StandardWrapperFacade 對象,這個類能夠保證從 StandardWrapper 中拿到 ServletConfig 所規(guī)定的數(shù)據(jù)乃秀,而又不把 ServletConfig 不關(guān)心的數(shù)據(jù)暴露給 Servlet肛著。

同樣 ServletContext 也與 ServletConfig 有類似的結(jié)構(gòu),Servlet 中能拿到的 ServletContext 的實際對象也是 ApplicationContextFacade 對象跺讯。ApplicationContextFacade 同樣保證 ServletContex 只能從容器中拿到它該拿的數(shù)據(jù)枢贿,它們都起到對數(shù)據(jù)的封裝作用,它們使用的都是門面設(shè)計模式刀脏。

通過 ServletContext 可以拿到 Context 容器中一些必要信息局荚,比如應(yīng)用的工作路徑,容器支持的 Servlet 最小版本等。

Servlet 中定義的兩個 ServletRequest 和 ServletResponse 它們實際的對象又是什么呢耀态?轮傍,我們在創(chuàng)建自己的 Servlet 類時通常使用的都是 HttpServletRequest 和 HttpServletResponse,它們繼承了 ServletRequest 和 ServletResponse首装。為何 Context 容器傳過來的 ServletRequest创夜、ServletResponse 可以被轉(zhuǎn)化為 HttpServletRequest 和 HttpServletResponse 呢?

圖 7.Request 相關(guān)類結(jié)構(gòu)圖

上圖是 Tomcat 創(chuàng)建的 Request 和 Response 的類結(jié)構(gòu)圖仙逻。Tomcat 一接受到請求首先將會創(chuàng)建 org.apache.coyote.Request 和 org.apache.coyote.Response驰吓,這兩個類是 Tomcat 內(nèi)部使用的描述一次請求和相應(yīng)的信息類它們是一個輕量級的類,它們作用就是在服務(wù)器接收到請求后系奉,經(jīng)過簡單解析將這個請求快速的分配給后續(xù)線程去處理檬贰,所以它們的對象很小,很容易被 JVM 回收缺亮。接下去當交給一個用戶線程去處理這個請求時又創(chuàng)建 org.apache.catalina.connector. Request 和 org.apache.catalina.connector. Response 對象翁涤。這兩個對象一直穿越整個 Servlet 容器直到要傳給 Servlet,傳給 Servlet 的是 Request 和 Response 的門面類 RequestFacade 和 RequestFacade萌踱,這里使用門面模式與前面一樣都是基于同樣的目的——封裝容器中的數(shù)據(jù)葵礼。一次請求對應(yīng)的 Request 和 Response 的類轉(zhuǎn)化如下圖所示:

圖 8.Request 和 Response 的轉(zhuǎn)變過程

回頁首

Servlet 如何工作

我們已經(jīng)清楚了 Servlet 是如何被加載的、Servlet 是如何被初始化的虫蝶,以及 Servlet 的體系結(jié)構(gòu),現(xiàn)在的問題就是它是如何被調(diào)用的倦西。

當用戶從瀏覽器向服務(wù)器發(fā)起一個請求能真,通常會包含如下信息:http://hostname: port /contextpath/servletpath,hostname 和 port 是用來與服務(wù)器建立 TCP 連接扰柠,而后面的 URL 才是用來選擇服務(wù)器中那個子容器服務(wù)用戶的請求粉铐。那服務(wù)器是如何根據(jù)這個 URL 來達到正確的 Servlet 容器中的呢?

Tomcat7.0 中這件事很容易解決卤档,因為這種映射工作有專門一個類來完成的蝙泼,這個就是 org.apache.tomcat.util.http.mapper,這個類保存了 Tomcat 的 Container 容器中的所有子容器的信息劝枣,當 org.apache.catalina.connector. Request 類在進入 Container 容器之前汤踏,mapper 將會根據(jù)這次請求的 hostnane 和 contextpath 將 host 和 context 容器設(shè)置到 Request 的 mappingData 屬性中。所以當 Request 進入 Container 容器之前舔腾,它要訪問那個子容器這時就已經(jīng)確定了溪胶。

圖 9.Request 的 Mapper 類關(guān)系圖

可能你有疑問,mapper 中怎么會有容器的完整關(guān)系稳诚,這要回到圖 2 中 19 步 MapperListener 類的初始化過程哗脖,下面是 MapperListener 的 init 方法代碼 :

清單 5. MapperListener.init
public void init() { 
       findDefaultHost(); 
       Engine engine = (Engine) connector.getService().getContainer(); 
       engine.addContainerListener(this); 
       Container[] conHosts = engine.findChildren(); 
       for (Container conHost : conHosts) { 
           Host host = (Host) conHost; 
           if (!LifecycleState.NEW.equals(host.getState())) { 
               host.addLifecycleListener(this); 
               registerHost(host); 
           } 
       } 
}

這段代碼的作用就是將 MapperListener 類作為一個監(jiān)聽者加到整個 Container 容器中的每個子容器中,這樣只要任何一個容器發(fā)生變化,MapperListener 都將會被通知才避,相應(yīng)的保存容器關(guān)系的 MapperListener 的 mapper 屬性也會修改橱夭。for 循環(huán)中就是將 host 及下面的子容器注冊到 mapper 中。

圖 10.Request 在容器中的路由圖

上圖描述了一次 Request 請求是如何達到最終的 Wrapper 容器的桑逝,我們現(xiàn)正知道了請求是如何達到正確的 Wrapper 容器棘劣,但是請求到達最終的 Servlet 還要完成一些步驟,必須要執(zhí)行 Filter 鏈肢娘,以及要通知你在 web.xml 中定義的 listener呈础。

接下去就要執(zhí)行 Servlet 的 service 方法了,通常情況下橱健,我們自己定義的 servlet 并不是直接去實現(xiàn) javax.servlet.servlet 接口而钞,而是去繼承更簡單的 HttpServlet 類或者 GenericServlet 類,我們可以有選擇的覆蓋相應(yīng)方法去實現(xiàn)我們要完成的工作拘荡。

Servlet 的確已經(jīng)能夠幫我們完成所有的工作了臼节,但是現(xiàn)在的 web 應(yīng)用很少有直接將交互全部頁面都用 servlet 來實現(xiàn),而是采用更加高效的 MVC 框架來實現(xiàn)珊皿。這些 MVC 框架基本的原理都是將所有的請求都映射到一個 Servlet网缝,然后去實現(xiàn) service 方法,這個方法也就是 MVC 框架的入口蟋定。

當 Servlet 從 Servlet 容器中移除時粉臊,也就表明該 Servlet 的生命周期結(jié)束了,這時 Servlet 的 destroy 方法將被調(diào)用驶兜,做一些掃尾工作扼仲。

回頁首

Session 與 Cookie

前面我們已經(jīng)說明了 Servlet 如何被調(diào)用,我們基于 Servlet 來構(gòu)建應(yīng)用程序抄淑,那么我們能從 Servlet 獲得哪些數(shù)據(jù)信息呢屠凶?

Servlet 能夠給我們提供兩部分數(shù)據(jù),一個是在 Servlet 初始化時調(diào)用 init 方法時設(shè)置的 ServletConfig肆资,這個類基本上含有了 Servlet 本身和 Servlet 所運行的 Servlet 容器中的基本信息矗愧。根據(jù)前面的介紹 ServletConfig 的實際對象是 StandardWrapperFacade,到底能獲得哪些容器信息可以看看這類提供了哪些接口郑原。還有一部分數(shù)據(jù)是由 ServletRequest 類提供唉韭,它的實際對象是 RequestFacade,從提供的方法中發(fā)現(xiàn)主要是描述這次請求的 HTTP 協(xié)議的信息犯犁。所以要掌握 Servlet 的工作方式必須要很清楚 HTTP 協(xié)議纽哥,如果你還不清楚趕緊去找一些參考資料。關(guān)于這一塊還有一個讓很多人迷惑的 Session 與 Cookie栖秕。

Session 與 Cookie 不管是對 Java Web 的熟練使用者還是初學(xué)者來說都是一個令人頭疼的東西春塌。Session 與 Cookie 的作用都是為了保持訪問用戶與后端服務(wù)器的交互狀態(tài)。它們有各自的優(yōu)點也有各自的缺陷。然而具有諷刺意味的是它們優(yōu)點和它們的使用場景又是矛盾的只壳,例如使用 Cookie 來傳遞信息時俏拱,隨著 Cookie 個數(shù)的增多和訪問量的增加,它占用的網(wǎng)絡(luò)帶寬也很大吼句,試想假如 Cookie 占用 200 個字節(jié)锅必,如果一天的 PV 有幾億的時候组去,它要占用多少帶寬巧涧。所以大訪問量的時候希望用 Session甲抖,但是 Session 的致命弱點是不容易在多臺服務(wù)器之間共享瘩扼,所以這也限制了 Session 的使用。

不管 Session 和 Cookie 有什么不足近尚,我們還是要用它們委刘。下面詳細講一下筑悴,Session 如何基于 Cookie 來工作谁鳍。實際上有三種方式能可以讓 Session 正常工作:

  1. 基于 URL Path Parameter癞季,默認就支持
  2. 基于 Cookie,如果你沒有修改 Context 容器個 cookies 標識的話倘潜,默認也是支持的
  3. 基于 SSL绷柒,默認不支持,只有 connector.getAttribute("SSLEnabled") 為 TRUE 時才支持

第一種情況下涮因,當瀏覽器不支持 Cookie 功能時废睦,瀏覽器會將用戶的 SessionCookieName 重寫到用戶請求的 URL 參數(shù)中,它的傳遞格式如 /path/Servlet;name=value;name2=value2? Name3=value3养泡,其中“Servlet嗜湃;”后面的 K-V 對就是要傳遞的 Path Parameters,服務(wù)器會從這個 Path Parameters 中拿到用戶配置的 SessionCookieName瓤荔。關(guān)于這個 SessionCookieName净蚤,如果你在 web.xml 中配置 session-config 配置項的話钥组,其 cookie-config 下的 name 屬性就是這個 SessionCookieName 值输硝,如果你沒有配置 session-config 配置項,默認的 SessionCookieName 就是大家熟悉的“JSESSIONID”程梦。接著 Request 根據(jù)這個 SessionCookieName 到 Parameters 拿到 Session ID 并設(shè)置到 request.setRequestedSessionId 中点把。

請注意如果客戶端也支持 Cookie 的話,Tomcat 仍然會解析 Cookie 中的 Session ID屿附,并會覆蓋 URL 中的 Session ID郎逃。

如果是第三種情況的話將會根據(jù) javax.servlet.request.ssl_session 屬性值設(shè)置 Session ID。

有了 Session ID 服務(wù)器端就可以創(chuàng)建 HttpSession 對象了挺份,第一次觸發(fā)是通過 request. getSession() 方法褒翰,如果當前的 Session ID 還沒有對應(yīng)的 HttpSession 對象那么就創(chuàng)建一個新的,并將這個對象加到 org.apache.catalina. Manager 的 sessions 容器中保存,Manager 類將管理所有 Session 的生命周期优训,Session 過期將被回收朵你,服務(wù)器關(guān)閉,Session 將被序列化到磁盤等揣非。只要這個 HttpSession 對象存在抡医,用戶就可以根據(jù) Session ID 來獲取到這個對象,也就達到了狀態(tài)的保持早敬。

圖 11.Session 相關(guān)類圖

上從圖中可以看出從 request.getSession 中獲取的 HttpSession 對象實際上是 StandardSession 對象的門面對象忌傻,這與前面的 Request 和 Servlet 是一樣的原理。下圖是 Session 工作的時序圖:

圖 12.Session 工作的時序圖(查看大圖

還有一點與 Session 關(guān)聯(lián)的 Cookie 與其它 Cookie 沒有什么不同搞监,這個配置的配置可以通過 web.xml 中的 session-config 配置項來指定水孩。

回頁首

Servlet 中的 Listener

整個 Tomcat 服務(wù)器中 Listener 使用的非常廣泛,它是基于觀察者模式設(shè)計的腺逛,Listener 的設(shè)計對開發(fā) Servlet 應(yīng)用程序提供了一種快捷的手段荷愕,能夠方便的從另一個縱向維度控制程序和數(shù)據(jù)。目前 Servlet 中提供了 5 種兩類事件的觀察者接口棍矛,它們分別是:4 個 EventListeners 類型的安疗,ServletContextAttributeListener、ServletRequestAttributeListener够委、ServletRequestListener荐类、HttpSessionAttributeListener 和 2 個 LifecycleListeners 類型的,ServletContextListener茁帽、HttpSessionListener玉罐。如下圖所示:

圖 13.Servlet 中的 Listener(查看大圖

它們基本上涵蓋了整個 Servlet 生命周期中,你感興趣的每種事件潘拨。這些 Listener 的實現(xiàn)類可以配置在 web.xml 中的 <listener> 標簽中吊输。當然也可以在應(yīng)用程序中動態(tài)添加 Listener,需要注意的是 ServletContextListener 在容器啟動之后就不能再添加新的铁追,因為它所監(jiān)聽的事件已經(jīng)不會再出現(xiàn)季蚂。掌握這些 Listener 的使用,能夠讓我們的程序設(shè)計的更加靈活

Java NIO和IO的區(qū)別

下表總結(jié)了Java NIO和IO之間的主要差別琅束,我會更詳細地描述表中每部分的差異扭屁。

復(fù)制代碼代碼如下:

IO NIO
面向流 面向緩沖
阻塞IO 非阻塞IO
無 選擇器

面向流與面向緩沖

Java NIO和IO之間第一個最大的區(qū)別是,IO是面向流的涩禀,NIO是面向緩沖區(qū)的料滥。 Java IO面向流意味著每次從流中讀一個或多個字節(jié),直至讀取所有字節(jié)艾船,它們沒有被緩存在任何地方葵腹。此外高每,它不能前后移動流中的數(shù)據(jù)。如果需要前后移動從流中讀取的數(shù)據(jù)践宴,需要先將它緩存到一個緩沖區(qū)觉义。 Java NIO的緩沖導(dǎo)向方法略有不同。數(shù)據(jù)讀取到一個它稍后處理的緩沖區(qū)浴井,需要時可在緩沖區(qū)中前后移動晒骇。這就增加了處理過程中的靈活性。但是磺浙,還需要檢查是否該緩沖區(qū)中包含所有您需要處理的數(shù)據(jù)洪囤。而且,需確保當更多的數(shù)據(jù)讀入緩沖區(qū)時撕氧,不要覆蓋緩沖區(qū)里尚未處理的數(shù)據(jù)瘤缩。

阻塞與非阻塞IO

Java IO的各種流是阻塞的。這意味著伦泥,當一個線程調(diào)用read() 或 write()時剥啤,該線程被阻塞,直到有一些數(shù)據(jù)被讀取不脯,或數(shù)據(jù)完全寫入府怯。該線程在此期間不能再干任何事情了。 Java NIO的非阻塞模式防楷,使一個線程從某通道發(fā)送請求讀取數(shù)據(jù)牺丙,但是它僅能得到目前可用的數(shù)據(jù),如果目前沒有數(shù)據(jù)可用時复局,就什么都不會獲取冲簿。而不是保持線程阻塞,所以直至數(shù)據(jù)變的可以讀取之前亿昏,該線程可以繼續(xù)做其他的事情峦剔。 非阻塞寫也是如此。一個線程請求寫入一些數(shù)據(jù)到某通道角钩,但不需要等待它完全寫入吝沫,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閑時間用于在其它通道上執(zhí)行IO操作彤断,所以一個單獨的線程現(xiàn)在可以管理多個輸入和輸出通道(channel)野舶。

選擇器(Selectors)

Java NIO的選擇器允許一個單獨的線程來監(jiān)視多個輸入通道易迹,你可以注冊多個通道使用一個選擇器宰衙,然后使用一個單獨的線程來“選擇”通道:這些通道里已經(jīng)有可以處理的輸入,或者選擇已準備寫入的通道睹欲。這種選擇機制供炼,使得一個單獨的線程很容易來管理多個通道一屋。

NIO和IO如何影響應(yīng)用程序的設(shè)計

無論您選擇IO或NIO工具箱,可能會影響您應(yīng)用程序設(shè)計的以下幾個方面:

1.對NIO或IO類的API調(diào)用袋哼。
2.數(shù)據(jù)處理冀墨。
3.用來處理數(shù)據(jù)的線程數(shù)。

API調(diào)用

當然涛贯,使用NIO的API調(diào)用時看起來與使用IO時有所不同诽嘉,但這并不意外,因為并不是僅從一個InputStream逐字節(jié)讀取弟翘,而是數(shù)據(jù)必須先讀入緩沖區(qū)再處理虫腋。

數(shù)據(jù)處理

使用純粹的NIO設(shè)計相較IO設(shè)計,數(shù)據(jù)處理也受到影響稀余。

在IO設(shè)計中悦冀,我們從InputStream或 Reader逐字節(jié)讀取數(shù)據(jù)。假設(shè)你正在處理一基于行的文本數(shù)據(jù)流睛琳,例如:

復(fù)制代碼代碼如下:

Name: Anna
Age: 25
Email: anna@mailserver.com
Phone: 1234567890

該文本行的流可以這樣處理:

復(fù)制代碼代碼如下:

BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String nameLine = reader.readLine();
String ageLine = reader.readLine();
String emailLine = reader.readLine();
String phoneLine = reader.readLine();

請注意處理狀態(tài)由程序執(zhí)行多久決定盒蟆。換句話說,一旦reader.readLine()方法返回师骗,你就知道肯定文本行就已讀完历等, readline()阻塞直到整行讀完,這就是原因辟癌。你也知道此行包含名稱募闲;同樣,第二個readline()調(diào)用返回的時候愿待,你知道這行包含年齡等浩螺。 正如你可以看到,該處理程序僅在有新數(shù)據(jù)讀入時運行仍侥,并知道每步的數(shù)據(jù)是什么要出。一旦正在運行的線程已處理過讀入的某些數(shù)據(jù),該線程不會再回退數(shù)據(jù)(大多如此)农渊。下圖也說明了這條原則:

img

(Java IO: 從一個阻塞的流中讀數(shù)據(jù)) 而一個NIO的實現(xiàn)會有所不同患蹂,下面是一個簡單的例子:

復(fù)制代碼代碼如下:

ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);

注意第二行,從通道讀取字節(jié)到ByteBuffer砸紊。當這個方法調(diào)用返回時传于,你不知道你所需的所有數(shù)據(jù)是否在緩沖區(qū)內(nèi)。你所知道的是醉顽,該緩沖區(qū)包含一些字節(jié)沼溜,這使得處理有點困難。
假設(shè)第一次 read(buffer)調(diào)用后游添,讀入緩沖區(qū)的數(shù)據(jù)只有半行系草,例如通熄,“Name:An”,你能處理數(shù)據(jù)嗎找都?顯然不能唇辨,需要等待,直到整行數(shù)據(jù)讀入緩存能耻,在此之前赏枚,對數(shù)據(jù)的任何處理毫無意義。

所以晓猛,你怎么知道是否該緩沖區(qū)包含足夠的數(shù)據(jù)可以處理呢嗡贺?好了,你不知道鞍帝。發(fā)現(xiàn)的方法只能查看緩沖區(qū)中的數(shù)據(jù)诫睬。其結(jié)果是,在你知道所有數(shù)據(jù)都在緩沖區(qū)里之前帕涌,你必須檢查幾次緩沖區(qū)的數(shù)據(jù)摄凡。這不僅效率低下,而且可以使程序設(shè)計方案雜亂不堪蚓曼。例如:

復(fù)制代碼代碼如下:

ByteBuffer buffer = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buffer);
while(! bufferFull(bytesRead) ) {
bytesRead = inChannel.read(buffer);
}

bufferFull()方法必須跟蹤有多少數(shù)據(jù)讀入緩沖區(qū)亲澡,并返回真或假,這取決于緩沖區(qū)是否已滿纫版。換句話說床绪,如果緩沖區(qū)準備好被處理,那么表示緩沖區(qū)滿了其弊。

bufferFull()方法掃描緩沖區(qū)癞己,但必須保持在bufferFull()方法被調(diào)用之前狀態(tài)相同。如果沒有梭伐,下一個讀入緩沖區(qū)的數(shù)據(jù)可能無法讀到正確的位置痹雅。這是不可能的,但卻是需要注意的又一問題糊识。

如果緩沖區(qū)已滿绩社,它可以被處理。如果它不滿赂苗,并且在你的實際案例中有意義愉耙,你或許能處理其中的部分數(shù)據(jù)。但是許多情況下并非如此拌滋。下圖展示了“緩沖區(qū)數(shù)據(jù)循環(huán)就緒”:

img

3) 用來處理數(shù)據(jù)的線程數(shù)

NIO可讓您只使用一個(或幾個)單線程管理多個通道(網(wǎng)絡(luò)連接或文件)朴沿,但付出的代價是解析數(shù)據(jù)可能會比從一個阻塞流中讀取數(shù)據(jù)更復(fù)雜。

如果需要管理同時打開的成千上萬個連接鸠真,這些連接每次只是發(fā)送少量的數(shù)據(jù)悯仙,例如聊天服務(wù)器,實現(xiàn)NIO的服務(wù)器可能是一個優(yōu)勢吠卷。同樣锡垄,如果你需要維持許多打開的連接到其他計算機上,如P2P網(wǎng)絡(luò)中祭隔,使用一個單獨的線程來管理你所有出站連接货岭,可能是一個優(yōu)勢。一個線程多個連接的設(shè)計方案如

img

Java NIO: 單線程管理多個連接

如果你有少量的連接使用非常高的帶寬疾渴,一次發(fā)送大量的數(shù)據(jù)千贯,也許典型的IO服務(wù)器實現(xiàn)可能非常契合。下圖說明了一個典型的IO服務(wù)器設(shè)計:

img

Java IO: 一個典型的IO服務(wù)器設(shè)計- 一個連接通過一個線程處理

Java中堆內(nèi)存和棧內(nèi)存區(qū)別

Java把內(nèi)存分成兩種搞坝,一種叫做棧內(nèi)存搔谴,一種叫做堆內(nèi)存

在函數(shù)中定義的一些基本類型的變量和對象的引用變量都是在函數(shù)的棧內(nèi)存中分配。當在一段代碼塊中定義一個變量時桩撮,java就在棧中為這個變量分配內(nèi)存空間敦第,當超過變量的作用域后,java會自動釋放掉為該變量分配的內(nèi)存空間店量,該內(nèi)存空間可以立刻被另作他用芜果。

堆內(nèi)存用于存放由new創(chuàng)建的對象和數(shù)組。在堆中分配的內(nèi)存融师,由java虛擬機自動垃圾回收器來管理右钾。在堆中產(chǎn)生了一個數(shù)組或者對象后,還可以在棧中定義一個特殊的變量旱爆,這個變量的取值等于數(shù)組或者對象在堆內(nèi)存中的首地址舀射,在棧中的這個特殊的變量就變成了數(shù)組或者對象的引用變量,以后就可以在程序中使用棧內(nèi)存中的引用變量來訪問堆中的數(shù)組或者對象怀伦,引用變量相當于為數(shù)組或者對象起的一個別名后控,或者代號。

引用變量是普通變量空镜,定義時在棧中分配內(nèi)存浩淘,引用變量在程序運行到作用域外釋放。而數(shù)組&對象本身在堆中分配吴攒,即使程序運行到使用new產(chǎn)生數(shù)組和對象的語句所在地代碼塊之外张抄,數(shù)組和對象本身占用的堆內(nèi)存也不會被釋放,數(shù)組和對象在沒有引用變量指向它的時候洼怔,才變成垃圾署惯,不能再被使用,但是仍然占著內(nèi)存镣隶,在隨后的一個不確定的時間被垃圾回收器釋放掉极谊。這個也是java比較占內(nèi)存的主要原因诡右,********實際上汁蝶,棧中的變量指向堆內(nèi)存中的變量摸屠,這就是 Java 中的指針!


java中內(nèi)存分配策略及堆和棧的比較
  1 內(nèi)存分配策略
  按照編譯原理的觀點,程序運行時的內(nèi)存分配有三種策略,分別是靜態(tài)的,棧式的,和堆式的.
  靜態(tài)存儲分配是指在編譯時就能確定每個數(shù)據(jù)目標在運行時刻的存儲空間需求,因而在編譯時就可以給他們分配固定的內(nèi)存空間.這種分配策略要求程序代碼中不允許有可變數(shù)據(jù)結(jié)構(gòu)(比如可變數(shù)組)的存在,也不允許有嵌套或者遞歸的結(jié)構(gòu)出現(xiàn),因為它們都會導(dǎo)致編譯程序無法計算準確的存儲空間需求.
  棧式存儲分配也可稱為動態(tài)存儲分配,是由一個類似于堆棧的運行棧來實現(xiàn)的.和靜態(tài)存儲分配相反,在棧式存儲方案中,程序?qū)?shù)據(jù)區(qū)的需求在編譯時是完全未知的,只有到運行的時候才能夠知道,但是規(guī)定在運行中進入一個程序模塊時,必須知道該程序模塊所需的數(shù)據(jù)區(qū)大小才能夠為其分配內(nèi)存.和我們在數(shù)據(jù)結(jié)構(gòu)所熟知的棧一樣,棧式存儲分配按照先進后出的原則進行分配。
  靜態(tài)存儲分配要求在編譯時能知道所有變量的存儲要求,棧式存儲分配要求在過程的入口處必須知道所有的存儲要求,而堆式存儲分配則專門負責(zé)在編譯時或運行時模塊入口處都無法確定存儲要求的數(shù)據(jù)結(jié)構(gòu)的內(nèi)存分配,比如可變長度串和對象實例.堆由大片的可利用塊或空閑塊組成,堆中的內(nèi)存可以按照任意順序分配和釋放.
  2 堆和棧的比較
  上面的定義從編譯原理的教材中總結(jié)而來,除靜態(tài)存儲分配之外,都顯得很呆板和難以理解,下面撇開靜態(tài)存儲分配,集中比較堆和棧:
  從堆和棧的功能和作用來通俗的比較,堆主要用來存放對象的播掷,棧主要是用來執(zhí)行程序的.而這種不同又主要是由于堆和棧的特點決定的:
  在編程中咙边,例如C/C++中猜煮,所有的方法調(diào)用都是通過棧來進行的,所有的局部變量,形式參數(shù)都是從棧中分配內(nèi)存空間的。實際上也不是什么分配,只是從棧頂向上用就行,就好像工廠中的傳送帶(conveyor belt)一樣,Stack Pointer會自動指引你到放東西的位置,你所要做的只是把東西放下來就行.退出函數(shù)的時候败许,修改棧指針就可以把棧中的內(nèi)容銷毀.這樣的模式速度最快, 當然要用來運行程序了.需要注意的是,在分配的時候,比如為一個即將要調(diào)用的程序模塊分配數(shù)據(jù)區(qū)時,應(yīng)事先知道這個數(shù)據(jù)區(qū)的大小,也就說是雖然分配是在程序運行時進行的,但是分配的大小多少是確定的,不變的,而這個"大小多少"是在編譯時確定的,不是在運行時.
  堆是應(yīng)用程序在運行的時候請求操作系統(tǒng)分配給自己內(nèi)存王带,由于從操作系統(tǒng)管理的內(nèi)存分配,所以在分配和銷毀時都要占用時間,因此用堆的效率非常低.但是堆的優(yōu)點在于,編譯器不必知道要從堆里分配多少存儲空間市殷,也不必知道存儲的數(shù)據(jù)要在堆里停留多長的時間,因此,用堆保存數(shù)據(jù)時會得到更大的靈活性愕撰。事實上,面向?qū)ο蟮亩鄳B(tài)性,堆內(nèi)存分配是必不可少的,因為多態(tài)變量所需的存儲空間只有在運行時創(chuàng)建了對象之后才能確定.在C++中,要求創(chuàng)建一個對象時醋寝,只需用 new命令編制相關(guān)的代碼即可盟戏。執(zhí)行這些代碼時,會在堆里自動進行數(shù)據(jù)的保存.當然甥桂,為達到這種靈活性柿究,必然會付出一定的代價:在堆里分配存儲空間時會花掉更長的時間!這也正是導(dǎo)致我們剛才所說的效率低的原因,看來列寧同志說的好,人的優(yōu)點往往也是人的缺點,人的缺點往往也是人的優(yōu)點(暈~).
  3 JVM中的堆和棧
  JVM是基于堆棧的虛擬機.JVM為每個新創(chuàng)建的線程都分配一個堆棧.也就是說,對于一個Java程序來說,它的運行就是通過對堆棧的操作來完成的黄选。堆棧以幀為單位保存線程的狀態(tài)蝇摸。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。
  我們知道,某個線程正在執(zhí)行的方法稱為此線程的當前方法.我們可能不知道,當前方法使用的幀稱為當前幀办陷。當線程激活一個Java方法,JVM就會在線程的 Java堆棧里新壓入一個幀貌夕。這個幀自然成為了當前幀.在此方法執(zhí)行期間,這個幀將用來保存參數(shù),局部變量,中間計算過程和其他數(shù)據(jù).這個幀在這里和編譯原理中的活動紀錄的概念是差不多的.
  從Java的這種分配機制來看,堆棧又可以這樣理解:堆棧(Stack)是操作系統(tǒng)在建立某個進程時或者線程(在支持多線程的操作系統(tǒng)中是線程)為這個線程建立的存儲區(qū)域,該區(qū)域具有先進后出的特性民镜。
  每一個Java應(yīng)用都唯一對應(yīng)一個JVM實例啡专,每一個實例唯一對應(yīng)一個堆。應(yīng)用程序在運行中所創(chuàng)建的所有類實例或數(shù)組都放在這個堆中,并由應(yīng)用所有的線程共享.跟C/C++不同制圈,Java中分配堆內(nèi)存是自動初始化的们童。Java中所有對象的存儲空間都是在堆中分配的,但是這個對象的引用卻是在堆棧中分配,也就是說在建立一個對象時從兩個地方都分配內(nèi)存鲸鹦,在堆中分配的內(nèi)存實際建立這個對象慧库,而在堆棧中分配的內(nèi)存只是一個指向這個堆對象的指針(引用)而已。
  Java 中的堆和棧
  Java把內(nèi)存劃分成兩種:一種是棧內(nèi)存馋嗜,一種是堆內(nèi)存齐板。
  在函數(shù)中定義的一些基本類型的變量和對象的引用變量都在函數(shù)的棧內(nèi)存中分配。
  當在一段代碼塊定義一個變量時,Java就在棧中為這個變量分配內(nèi)存空間甘磨,當超過變量的作用域后橡羞,Java會自動釋放掉為該變量所分配的內(nèi)存空間,該內(nèi)存空間可以立即被另作他用济舆。
  堆內(nèi)存用來存放由new創(chuàng)建的對象和數(shù)組卿泽。
  在堆中分配的內(nèi)存,由Java虛擬機的自動垃圾回收器來管理吗冤。
  在堆中產(chǎn)生了一個數(shù)組或?qū)ο蠛笥掷鳎€可以在棧中定義一個特殊的變量九府,讓棧中這個變量的取值等于數(shù)組或?qū)ο笤诙褍?nèi)存中的首地址椎瘟,棧中的這個變量就成了數(shù)組或?qū)ο蟮囊米兞俊?br>   引用變量就相當于是為數(shù)組或?qū)ο笃鸬囊粋€名稱,以后就可以在程序中使用棧中的引用變量來訪問堆中的數(shù)組或?qū)ο蟆?br>   具體的說:
  棧與堆都是Java用來在Ram中存放數(shù)據(jù)的地方侄旬。與C++不同肺蔚,Java自動管理棧和堆,程序員不能直接地設(shè)置椑芨幔或堆宣羊。
  Java的堆是一個運行時數(shù)據(jù)區(qū),類的(對象從中分配空間。這些對象通過new汰蜘、newarray仇冯、anewarray和multianewarray等指令建立,它們不需要程序代碼來顯式的釋放族操。堆是由垃圾回收來負責(zé)的苛坚,堆的優(yōu)勢是可以動態(tài)地分配內(nèi)存大小,生存期也不必事先告訴編譯器色难,因為它是在運行時動態(tài)分配內(nèi)存的泼舱,Java的垃圾收集器會自動收走這些不再使用的數(shù)據(jù)。但缺點是枷莉,由于要在運行時動態(tài)分配內(nèi)存娇昙,存取速度較慢。
  棧的優(yōu)勢是笤妙,存取速度比堆要快冒掌,僅次于寄存器,棧數(shù)據(jù)可以共享蹲盘。但缺點是宋渔,存在棧中的數(shù)據(jù)大小與生存期必須是確定的,缺乏靈活性辜限。棧中主要存放一些基本類型的變量(,int, short, long, byte, float, double, boolean, char)和對象句柄皇拣。
  棧有一個很重要的特殊性,就是存在棧中的數(shù)據(jù)可以共享。假設(shè)我們同時定義:
  int a = 3;
  int b = 3;
  編譯器先處理int a = 3;首先它會在棧中創(chuàng)建一個變量為a的引用氧急,然后查找棧中是否有3這個值颗胡,如果沒找到,就將3存放進來吩坝,然后將a指向3毒姨。接著處理int b = 3;在創(chuàng)建完b的引用變量后,因為在棧中已經(jīng)有3這個值钉寝,便將b直接指向3弧呐。這樣,就出現(xiàn)了a與b同時均指向3的情況嵌纲。這時俘枫,如果再令a=4;那么編譯器會重新搜索棧中是否有4值,如果沒有逮走,則將4存放進來鸠蚪,并令a指向4;如果已經(jīng)有了,則直接將a指向這個地址师溅。因此a值的改變不會影響到b的值茅信。要注意這種數(shù)據(jù)的共享與兩個對象的引用同時指向一個對象的這種共享是不同的,因為這種情況a的修改并不會影響到b, 它是由編譯器完成的墓臭,它有利于節(jié)省空間蘸鲸。而一個對象引用變量修改了這個對象的內(nèi)部狀態(tài),會影響到另一個對象引用變量

反射講一講窿锉,主要是概念,都在哪需要反射機制酌摇,反射的性能,如何優(yōu)化

反射機制的定義:

是在運行狀態(tài)中榆综,對于任意的一個類妙痹,都能夠知道這個類的所有屬性和方法,對任意一個對象都能夠通過反射機制調(diào)用一個類的任意方法鼻疮,這種動態(tài)獲取類信息及動態(tài)調(diào)用類對象方法的功能稱為java的反射機制怯伊。

反射的作用:

1、動態(tài)地創(chuàng)建類的實例判沟,將類綁定到現(xiàn)有的對象中耿芹,或從現(xiàn)有的對象中獲取類型。

2挪哄、應(yīng)用程序需要在運行時從某個特定的程序集中載入一個特定的類

如何保證RESTful API安全性

友情鏈接: 如何設(shè)計好的RESTful API之安全性

如何預(yù)防MySQL注入

所謂SQL注入吧秕,就是通過把SQL命令插入到Web表單遞交或輸入域名或頁面請求的查詢字符串,最終達到欺騙服務(wù)器執(zhí)行惡意的SQL命令迹炼。

我們永遠不要信任用戶的輸入砸彬,我們必須認定用戶輸入的數(shù)據(jù)都是不安全的颠毙,我們都需要對用戶輸入的數(shù)據(jù)進行過濾處理。

1.以下實例中砂碉,輸入的用戶名必須為字母蛀蜜、數(shù)字及下劃線的組合,且用戶名長度為 8 到 20 個字符之間:

if (preg_match("/^\w{8,20}$/", $_GET['username'], $matches))
{
$result = mysql_query("SELECT * FROM users 
      WHERE username=$matches[0]");
}
else 
{
echo "username 輸入異常";
}

讓我們看下在沒有過濾特殊字符時增蹭,出現(xiàn)的SQL情況:

// 設(shè)定$name 中插入了我們不需要的SQL語句
$name = "Qadir'; DELETE FROM users;";
mysql_query("SELECT * FROM users WHERE name='{$name}'");

以上的注入語句中滴某,我們沒有對 $name 的變量進行過濾,$name 中插入了我們不需要的SQL語句滋迈,將刪除 users 表中的所有數(shù)據(jù)霎奢。

2.在PHP中的 mysql_query() 是不允許執(zhí)行多個SQL語句的,但是在 SQLite 和 PostgreSQL 是可以同時執(zhí)行多條SQL語句的饼灿,所以我們對這些用戶的數(shù)據(jù)需要進行嚴格的驗證幕侠。

防止SQL注入,我們需要注意以下幾個要點:

1.永遠不要信任用戶的輸入赔退。對用戶的輸入進行校驗橙依,可以通過正則表達式证舟,或限制長度硕旗;對單引號和 雙"-"進行轉(zhuǎn)換等。
2.永遠不要使用動態(tài)拼裝sql女责,可以使用參數(shù)化的sql或者直接使用存儲過程進行數(shù)據(jù)查詢存取漆枚。
3.永遠不要使用管理員權(quán)限的數(shù)據(jù)庫連接,為每個應(yīng)用使用單獨的權(quán)限有限的數(shù)據(jù)庫連接抵知。
4.不要把機密信息直接存放墙基,加密或者hash掉密碼和敏感的信息。
5.應(yīng)用的異常信息應(yīng)該給出盡可能少的提示刷喜,最好使用自定義的錯誤信息對原始錯誤信息進行包裝
6.sql注入的檢測方法一般采取輔助軟件或網(wǎng)站平臺來檢測残制,軟件一般采用sql注入檢測工具jsky,網(wǎng)站平臺就有億思網(wǎng)站安全平臺檢測工具掖疮。MDCSOFT SCAN等初茶。采用MDCSOFT-IPS可以有效的防御SQL注入,XSS攻擊等浊闪。

3.防止SQL注入

在腳本語言恼布,如Perl和PHP你可以對用戶輸入的數(shù)據(jù)進行轉(zhuǎn)義從而來防止SQL注入。

PHP的MySQL擴展提供了mysql_real_escape_string()函數(shù)來轉(zhuǎn)義特殊的輸入字符搁宾。

if (get_magic_quotes_gpc()) 
{
$name = stripslashes($name);
}
$name = mysql_real_escape_string($name);
mysql_query("SELECT * FROM users WHERE name='{$name}'");

4.Like語句中的注入

like查詢時折汞,如果用戶輸入的值有""和"%",則會出現(xiàn)這種情況:用戶本來只是想查詢"abcd"盖腿,查詢結(jié)果中卻有"abcd_"爽待、"abcde"、"abcdf"等等;用戶要查詢"30%"(注:百分之三十)時也會出現(xiàn)問題鸟款。

在PHP腳本中我們可以使用addcslashes()函數(shù)來處理以上情況揖庄,如下實例:

$sub = addcslashes(mysql_real_escape_string("%something_"), "%_");
// $sub == \%something\_
mysql_query("SELECT * FROM messages WHERE subject LIKE '{$sub}%'");

addcslashes()函數(shù)在指定的字符前添加反斜杠。

語法格式:

addcslashes(string,characters)

參數(shù) 描述
string 必需欠雌。規(guī)定要檢查的字符串蹄梢。
characters 可選。規(guī)定受 addcslashes() 影響的字符或字符范圍富俄。

ThreadLocal(線程變量副本)

Synchronized實現(xiàn)內(nèi)存共享禁炒,ThreadLocal為每個線程維護一個本地變量。

采用空間換時間霍比,它用于線程間的數(shù)據(jù)隔離幕袱,為每一個使用該變量的線程提供一個副本,每個線程都可以獨立地改變自己的副本悠瞬,而不會和其他線程的副本沖突们豌。

ThreadLocal類中維護一個Map,用于存儲每一個線程的變量副本浅妆,Map中元素的鍵為線程對象望迎,而值為對應(yīng)線程的變量副本。

ThreadLocal在spring中發(fā)揮著巨大的作用凌外,在管理Request作用域中的Bean辩尊、事務(wù)管理、任務(wù)調(diào)度康辑、AOP等模塊都出現(xiàn)了它的身影摄欲。

Spring中絕大部分Bean都可以聲明成Singleton作用域,采用ThreadLocal進行封裝疮薇,因此有狀態(tài)的Bean就能夠以singleton的方式在多線程中正常工作了胸墙。

你能不能談?wù)劊?a target="_blank" rel="nofollow">Java GC是在什么時候,對什么東西按咒,做了什么事情迟隅?

在什么時候:

1.新生代有一個Eden區(qū)和兩個survivor區(qū),首先將對象放入Eden區(qū)胖齐,如果空間不足就向其中的一個survivor區(qū)上放玻淑,如果仍然放不下就會引發(fā)一次發(fā)生在新生代的minor GC,將存活的對象放入另一個survivor區(qū)中呀伙,然后清空Eden和之前的那個survivor區(qū)的內(nèi)存补履。在某次GC過程中,如果發(fā)現(xiàn)仍然又放不下的對象剿另,就將這些對象放入老年代內(nèi)存里去箫锤。

2.大對象以及長期存活的對象直接進入老年區(qū)贬蛙。

3.當每次執(zhí)行minor GC的時候應(yīng)該對要晉升到老年代的對象進行分析,如果這些馬上要到老年區(qū)的老年對象的大小超過了老年區(qū)的剩余大小谚攒,那么執(zhí)行一次Full GC以盡可能地獲得老年區(qū)的空間阳准。

對什么東西:從GC Roots搜索不到,而且經(jīng)過一次標記清理之后仍沒有復(fù)活的對象馏臭。

做什么:
新生代:復(fù)制清理野蝇;
老年代:標記-清除和標記-壓縮算法
永久代:存放Java中的類和加載類的類加載器本身括儒。

GC Roots都有哪些:
\1. 虛擬機棧中的引用的對象
\2. 方法區(qū)中靜態(tài)屬性引用的對象绕沈,常量引用的對象
\3. 本地方法棧中JNI(即一般說的Native方法)引用的對象。

Volatile和Synchronized四個不同點:

1 粒度不同帮寻,前者鎖對象和類乍狐,后者針對變量
2 syn阻塞,volatile線程不阻塞
3 syn保證三大特性固逗,volatile不保證原子性
4 syn編譯器優(yōu)化浅蚪,volatile不優(yōu)化
volatile具備兩種特性:
\1. 保證此變量對所有線程的可見性,指一條線程修改了這個變量的值烫罩,新值對于其他線程來說是可見的惜傲,但并不是多線程安全的。
\2. 禁止指令重排序優(yōu)化嗡髓。
Volatile如何保證內(nèi)存可見性:
1.當寫一個volatile變量時操漠,JMM會把該線程對應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存收津。
2.當讀一個volatile變量時饿这,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效。線程接下來將從主內(nèi)存中讀取共享變量撞秋。

同步:就是一個任務(wù)的完成需要依賴另外一個任務(wù)长捧,只有等待被依賴的任務(wù)完成后,依賴任務(wù)才能完成吻贿。
異步:不需要等待被依賴的任務(wù)完成串结,只是通知被依賴的任務(wù)要完成什么工作,只要自己任務(wù)完成了就算完成了舅列,被依賴的任務(wù)是否完成會通知回來肌割。(異步的特點就是通知)。
打電話和發(fā)短信來比喻同步和異步操作帐要。
阻塞:CPU停下來等一個慢的操作完成以后把敞,才會接著完成其他的工作。
非阻塞:非阻塞就是在這個慢的執(zhí)行時榨惠,CPU去做其他工作奋早,等這個慢的完成后盛霎,CPU才會接著完成后續(xù)的操作。
非阻塞會造成線程切換增加耽装,增加CPU的使用時間能不能補償系統(tǒng)的切換成本需要考慮愤炸。

線程池的作用:

在程序啟動的時候就創(chuàng)建若干線程來響應(yīng)處理,它們被稱為線程池掉奄,里面的線程叫工作線程
第一:降低資源消耗规个。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
第二:提高響應(yīng)速度姓建。當任務(wù)到達時绰姻,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。
第三:提高線程的可管理性引瀑。
常用線程池:ExecutorService 是主要的實現(xiàn)類狂芋,其中常用的有
Executors.newSingleThreadPool(),newFixedThreadPool(),newcachedTheadPool(),newScheduledThreadPool()。

一致性哈希:

Redis數(shù)據(jù)結(jié)構(gòu): String—字符串(key-value 類型)

索引:B+憨栽,B-,全文索引

MySQL的索引是一個數(shù)據(jù)結(jié)構(gòu)帜矾,旨在使數(shù)據(jù)庫高效的查找數(shù)據(jù)。
常用的數(shù)據(jù)結(jié)構(gòu)是B+Tree屑柔,每個葉子節(jié)點不但存放了索引鍵的相關(guān)信息還增加了指向相鄰葉子節(jié)點的指針屡萤,這樣就形成了帶有順序訪問指針的B+Tree回梧,做這個優(yōu)化的目的是提高不同區(qū)間訪問的性能种吸。
什么時候使用索引:

  1. 經(jīng)常出現(xiàn)在group by,order by和distinc關(guān)鍵字后面的字段
  2. 經(jīng)常與其他表進行連接的表,在連接字段上應(yīng)該建立索引
  3. 經(jīng)常出現(xiàn)在Where子句中的字段
  4. 經(jīng)常出現(xiàn)用作查詢選擇的字段

Spring IOC AOP(控制反轉(zhuǎn)其屏,依賴注入)

IOC容器:就是具有依賴注入功能的容器唧瘾,是可以創(chuàng)建對象的容器措译,IOC容器負責(zé)實例化、定位饰序、配置應(yīng)用程序中的對象及建立這些對象間的依賴领虹。通常new一個實例,控制權(quán)由程序員控制求豫,而"控制反轉(zhuǎn)"是指new實例工作不由程序員來做而是交給Spring容器來做塌衰。。在Spring中BeanFactory是IOC容器的實際代表者蝠嘉。

DI(依賴注入Dependency injection) :在容器創(chuàng)建對象后最疆,處理對象的依賴關(guān)系。

Spring支持三種依賴注入方式蚤告,分別是屬性(Setter方法)注入努酸,構(gòu)造注入和接口注入。

在Spring中罩缴,那些組成應(yīng)用的主體及由Spring IOC容器所管理的對象被稱之為Bean蚊逢。

Spring的IOC容器通過反射的機制實例化Bean并建立Bean之間的依賴關(guān)系层扶。
簡單地講,Bean就是由Spring IOC容器初始化烙荷、裝配及被管理的對象镜会。
獲取Bean對象的過程,首先通過Resource加載配置文件并啟動IOC容器终抽,然后通過getBean方法獲取bean對象戳表,就可以調(diào)用他的方法。
Spring Bean的作用域
Singleton:Spring IOC容器中只有一個共享的Bean實例昼伴,一般都是Singleton作用域匾旭。
Prototype:每一個請求,會產(chǎn)生一個新的Bean實例圃郊。
Request:每一次http請求會產(chǎn)生一個新的Bean實例价涝。

AOP就是縱向的編程,如業(yè)務(wù)1和業(yè)務(wù)2都需要一個共同的操作持舆,與其往每個業(yè)務(wù)中都添加同樣的代碼色瘩,不如寫一遍代碼,讓兩個業(yè)務(wù)共同使用這段代碼逸寓。在日常有訂單管理居兆、商品管理、資金管理竹伸、庫存管理等業(yè)務(wù)泥栖,都會需要到類似日志記錄事務(wù)控制勋篓、****權(quán)限控制吧享、性能統(tǒng)計、異常處理及事務(wù)處理等生巡。AOP把所有共有代碼全部抽取出來耙蔑,放置到某個地方集中管理,然后在具體運行時孤荣,再由容器動態(tài)織入這些共有代碼。

Spring AOP應(yīng)用場景
性能檢測须揣,訪問控制盐股,日志管理,事務(wù)等耻卡。
默認的策略是如果目標類實現(xiàn)接口疯汁,則使用JDK動態(tài)代理技術(shù),如果目標對象沒有實現(xiàn)接口卵酪,則默認會采用CGLIB代理

友情鏈接: Spring框架IOC容器和AOP解析

友情鏈接:淺談Spring框架注解的用法分析

友情鏈接:關(guān)于Spring的69個面試問答——終極列表

代理的共有優(yōu)點:業(yè)務(wù)類只需要關(guān)注業(yè)務(wù)邏輯本身幌蚊,保證了業(yè)務(wù)類的重用性谤碳。

Java靜態(tài)代理
代理對象和目標對象實現(xiàn)了相同的接口,目標對象作為代理對象的一個屬性溢豆,具體接口實現(xiàn)中蜒简,代理對象可以在調(diào)用目標對象相應(yīng)方法前后加上其他業(yè)務(wù)處理邏輯。
缺點:一個代理類只能代理一個業(yè)務(wù)類漩仙。如果業(yè)務(wù)類增加方法時搓茬,相應(yīng)的代理類也要增加方法。
Java動態(tài)代理
Java動態(tài)代理是寫一個類實現(xiàn)InvocationHandler接口队他,重寫Invoke方法卷仑,在Invoke方法可以進行增強處理的邏輯的編寫,這個公共代理類在運行的時候才能明確自己要代理的對象麸折,同時可以實現(xiàn)該被代理類的方法的實現(xiàn)锡凝,然后在實現(xiàn)類方法的時候可以進行增強處理。
實際上:代理對象的方法 = 增強處理 + 被代理對象的方法

JDK和CGLIB生成動態(tài)代理類的區(qū)別:
JDK動態(tài)代理只能針對實現(xiàn)了接口的類生成代理(實例化一個類)垢啼。此時代理對象和目標對象實現(xiàn)了相同的接口私爷,目標對象作為代理對象的一個屬性,具體接口實現(xiàn)中膊夹,可以在調(diào)用目標對象相應(yīng)方法前后加上其他業(yè)務(wù)處理邏輯
CGLIB是針對類實現(xiàn)代理衬浑,主要是對指定的類生成一個子類(沒有實例化一個類),覆蓋其中的方法 放刨。

SpringMVC運行原理

\1. 客戶端請求提交到DispatcherServlet
\2. 由DispatcherServlet控制器查詢HandlerMapping工秩,找到并分發(fā)到指定的Controller中。
\4. Controller調(diào)用業(yè)務(wù)邏輯處理后进统,返回ModelAndView
\5. DispatcherServlet查詢一個或多個ViewResoler視圖解析器助币,找到ModelAndView指定的視圖
\6. 視圖負責(zé)將結(jié)果顯示到客戶端

友情鏈接:Spring:基于注解的Spring MVC(上)

友情鏈接: Spring:基于注解的Spring MVC(下)

友情鏈接:SpringMVC與Struts2區(qū)別與比較總結(jié)

友情鏈接:SpringMVC與Struts2的對比

TCP三次握手,四次揮手

TCP作為一種可靠傳輸控制協(xié)議螟碎,其核心思想:既要保證數(shù)據(jù)可靠傳輸眉菱,又要提高傳輸?shù)男剩?strong>三次恰恰可以滿足以上兩方面的需求掉分!****雙方都需要確認自己的發(fā)信和收信功能正常俭缓,收信功能通過接收對方信息得到確認,發(fā)信功能需要發(fā)出信息—>對方回復(fù)信息得到確認酥郭。

三次握手過程:

  1. 第一次握手:建立連接华坦。客戶端發(fā)送連接請求報文段不从,將SYN位置為1惜姐,Sequence Number為x;然后椿息,客戶端進入SYN_SEND狀態(tài)歹袁,等待服務(wù)器的確認坷衍;
  2. 第二次握手:服務(wù)器收到客戶端的SYN報文段,需要對這個SYN報文段進行確認条舔,設(shè)置ACK為x+1(Sequence Number+1)枫耳;同時,自己還要發(fā)送SYN請求信息逞刷,將SYN位置為1嘉涌,Sequence Number為y;服務(wù)器端將上述所有信息放到一個報文段(即SYN+ACK報文段)中夸浅,一并發(fā)送給客戶端仑最,此時服務(wù)器進入SYN_RECV狀態(tài);
  3. 第三次握手:客戶端收到服務(wù)器的SYN+ACK報文段帆喇。然后將Acknowledgment Number設(shè)置為y+1警医,向服務(wù)器發(fā)送ACK報文段,這個報文段發(fā)送完畢以后坯钦,客戶端和服務(wù)器端都進入ESTABLISHED狀態(tài)预皇,完成TCP三次握手。

TCP工作在網(wǎng)絡(luò)OSI的七層模型中的第四層——Transport層婉刀,IP在第三層——Network層
?ARP在第二層——Data Link層吟温;在第二層上的數(shù)據(jù),我們把它叫Frame突颊,在第三層上的數(shù)據(jù)叫Packet鲁豪,第四層的數(shù)據(jù)叫Segment。

四次揮手過程:

  1. 第一次分手:主機1(可以使客戶端律秃,也可以是服務(wù)器端)爬橡,設(shè)置Sequence NumberAcknowledgment Number,向主機2發(fā)送一個FIN報文段棒动;此時糙申,主機1進入FIN_WAIT_1狀態(tài);這表示主機1沒有數(shù)據(jù)要發(fā)送給主機2了船惨;
  2. 第二次分手:主機2收到了主機1發(fā)送的FIN報文段柜裸,向主機1回一個ACK報文段,Acknowledgment NumberSequence Number加1掷漱;主機1進入FIN_WAIT_2狀態(tài)粘室;主機2告訴主機1,我“同意”你的關(guān)閉請求卜范;
  3. 第三次分手:主機2向主機1發(fā)送FIN報文段,請求關(guān)閉連接鹿榜,同時主機2進入LAST_ACK狀態(tài)海雪;
  4. 第四次分手:主機1收到主機2發(fā)送的FIN報文段锦爵,向主機2發(fā)送ACK報文段,然后主機1進入TIME_WAIT狀態(tài)奥裸;主機2收到主機1的ACK報文段以后险掀,就關(guān)閉連接;此時湾宙,主機1等待2MSL后依然沒有收到回復(fù)樟氢,則證明Server端已正常關(guān)閉,那好侠鳄,主機1也可以關(guān)閉連接了埠啃。
    (2)而關(guān)閉連接卻是四次揮手呢?
    這是因為服務(wù)端在LISTEN狀態(tài)下伟恶,收到建立連接請求的SYN報文后碴开,把ACK和SYN放在一個報文里發(fā)送給客戶端。

為什么建立連接是三次握手

這是因為服務(wù)端在LISTEN狀態(tài)下博秫,收到建立連接請求的SYN報文后潦牛,把ACK和SYN放在一個報文里發(fā)送給客戶端。

關(guān)閉連接卻是四次揮手呢

而關(guān)閉連接時挡育,當收到對方的FIN報文時巴碗,僅僅表示對方不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù),己方也未必全部數(shù)據(jù)都發(fā)送給對方了即寒,所以己方可以立即close橡淆,也可以發(fā)送一些數(shù)據(jù)給對方后,再發(fā)送FIN報文給對方來表示同意現(xiàn)在關(guān)閉連接蒿叠,因此明垢,己方ACK和FIN一般都會分開發(fā)送。

HTTPS和HTTP 為什么更安全市咽,先看這些

http默認端口是80 https是443

http是HTTP協(xié)議運行在TCP之上痊银。所有傳輸?shù)膬?nèi)容都是明文,客戶端和服務(wù)器端都無法驗證對方的身份施绎。

https是HTTP運行在SSL/TLS之上溯革,SSL/TLS運行在TCP之上。所有傳輸?shù)膬?nèi)容都經(jīng)過加密谷醉,加密采用對稱加密致稀,但對稱加密的密鑰用服務(wù)器方的證書進行了非對稱加密。此外客戶端可以驗證服務(wù)器端的身份俱尼,如果配置了客戶端驗證抖单,服務(wù)器方也可以驗證客戶端的身份。HTTP(應(yīng)用層) 和TCP(傳輸層)之間插入一個SSL協(xié)議,

一個Http請求

DNS域名解析 –> 發(fā)起TCP的三次握手 –> 建立TCP連接后發(fā)起http請求 –> 服務(wù)器響應(yīng)http請求,瀏覽器得到html代碼 –> 瀏覽器解析html代碼矛绘,并請求html代碼中的資源(如js耍休、css、圖片等) –> 瀏覽器對頁面進行渲染呈現(xiàn)給用戶

友情鏈接: HTTP與HTTPS的區(qū)別

友情鏈接: HTTPS 為什么更安全货矮,先看這些

友情鏈接: HTTP請求報文和HTTP響應(yīng)報文

友情鏈接: HTTP 請求方式: GET和POST的比較

Mybatis

每一個Mybatis的應(yīng)用程序都以一個SqlSessionFactory對象的實例為核心羊精。首先用字節(jié)流通過Resource將配置文件讀入,然后通過SqlSessionFactoryBuilder().build方法創(chuàng)建SqlSessionFactory囚玫,然后再通過sqlSessionFactory.openSession()方法創(chuàng)建一個sqlSession為每一個數(shù)據(jù)庫事務(wù)服務(wù)喧锦。
經(jīng)歷了Mybatis初始化 –>創(chuàng)建SqlSession –>運行SQL語句 返回結(jié)果三個過程

Servlet和Filter的區(qū)別:

整的流程是:Filter對用戶請求進行預(yù)處理,接著將請求交給Servlet進行處理并生成響應(yīng)抓督,最后Filter再對服務(wù)器響應(yīng)進行后處理燃少。

Filter有如下幾個用處:
Filter可以進行對特定的url請求和相應(yīng)做預(yù)處理和后處理。
在HttpServletRequest到達Servlet之前本昏,攔截客戶的HttpServletRequest供汛。
根據(jù)需要檢查HttpServletRequest,也可以修改HttpServletRequest頭和數(shù)據(jù)涌穆。
在HttpServletResponse到達客戶端之前怔昨,攔截HttpServletResponse。
根據(jù)需要檢查HttpServletResponse宿稀,也可以修改HttpServletResponse頭和數(shù)據(jù)趁舀。

實際上Filter和Servlet極其相似,區(qū)別只是Filter不能直接對用戶生成響應(yīng)祝沸。實際上Filter里doFilter()方法里的代碼就是從多個Servlet的service()方法里抽取的通用代碼矮烹,通過使用Filter可以實現(xiàn)更好的復(fù)用。

Filter和Servlet的生命周期:
1.Filter在web服務(wù)器啟動時初始化
2.如果某個Servlet配置了 1 罩锐,該Servlet也是在Tomcat(Servlet容器)啟動時初始化奉狈。
3.如果Servlet沒有配置1 ,該Servlet不會在Tomcat啟動時初始化涩惑,而是在請求到來時初始化仁期。
4.每次請求, Request都會被初始化竭恬,響應(yīng)請求后跛蛋,請求被銷毀
5.Servlet初始化后痊硕,將不會隨著請求的結(jié)束而注銷赊级。
6.關(guān)閉Tomcat時,Servlet岔绸、Filter依次被注銷理逊。

HashMap和TreeMap區(qū)別

HashMap:基于哈希表實現(xiàn)橡伞。使用HashMap要求添加的鍵類明確定義了hashCode()和equals()[可以重寫hashCode()和equals()],為了優(yōu)化HashMap空間的使用挡鞍,您可以調(diào)優(yōu)初始容量和負載因子骑歹。 適合查找和刪除
(1)HashMap(): 構(gòu)建一個空的哈希映像
(2)HashMap(Map m): 構(gòu)建一個哈希映像预烙,并且添加映像m的所有映射
(3)HashMap(int initialCapacity): 構(gòu)建一個擁有特定容量的空的哈希映像
(4)HashMap(int initialCapacity, float loadFactor): 構(gòu)建一個擁有特定容量和加載因子的空的哈希映像
TreeMap:基于紅黑樹實現(xiàn)墨微。TreeMap沒有調(diào)優(yōu)選項,因為該樹總處于平衡狀態(tài)扁掸。 適合按照自然順序或者自定義的順序排序遍歷key
(1)TreeMap():構(gòu)建一個空的映像樹
(2)TreeMap(Map m): 構(gòu)建一個映像樹翘县,并且添加映像m中所有元素
(3)TreeMap(Comparator c): 構(gòu)建一個映像樹,并且使用特定的比較器對關(guān)鍵字進行排序
(4)TreeMap(SortedMap s): 構(gòu)建一個映像樹谴分,添加映像樹s中所有映射锈麸,并且使用與有序映像s相同的比較器排序

友情鏈接: Java中HashMap和TreeMap的區(qū)別深入理解

HashMap沖突

友情鏈接: HashMap沖突的解決方法以及原理分析

友情鏈接: HashMap的工作原理

友情鏈接: HashMap和Hashtable的區(qū)別

友情鏈接: 2種辦法讓HashMap線程安全

HashMap,ConcurrentHashMap與LinkedHashMap的區(qū)別

  1. ConcurrentHashMap是使用了鎖分段技術(shù)技術(shù)來保證線程安全的牺蹄,鎖分段技術(shù):首先將數(shù)據(jù)分成一段一段的存儲忘伞,然后給每一段數(shù)據(jù)配一把鎖,當一個線程占用鎖訪問其中一個段數(shù)據(jù)的時候沙兰,其他段的數(shù)據(jù)也能被其他線程訪問

  2. ConcurrentHashMap 是在每個段(segment)中線程安全的

  3. LinkedHashMap維護一個雙鏈表氓奈,可以將里面的數(shù)據(jù)按寫入的順序讀出

  4. ConcurrentHashMap應(yīng)用場景

1:ConcurrentHashMap的應(yīng)用場景是高并發(fā),但是并不能保證線程安全鼎天,而同步的HashMap和HashMap的是鎖住整個容器舀奶,而加鎖之后ConcurrentHashMap不需要鎖住整個容器,只需要鎖住對應(yīng)的Segment就好了斋射,所以可以保證高并發(fā)同步訪問育勺,提升了效率。

2:可以多線程寫罗岖。
ConcurrentHashMap把HashMap分成若干個Segmenet
1.get時涧至,不加鎖,先定位到segment然后在找到頭結(jié)點進行讀取操作桑包。而value是volatile變量南蓬,所以可以保證在競爭條件時保證讀取最新的值,如果讀到的value是null捡多,則可能正在修改蓖康,那么久調(diào)用ReadValueUnderLock函數(shù),加鎖保證讀到的數(shù)據(jù)是正確的垒手。
2.Put時會加鎖蒜焊,一律添加到hash鏈的頭部。
3.Remove時也會加鎖科贬,由于next是final類型不可改變泳梆,所以必須把刪除的節(jié)點之前的節(jié)點都復(fù)制一遍鳖悠。
4.ConcurrentHashMap允許多個修改操作并發(fā)進行,其關(guān)鍵在于使用了鎖分離技術(shù)优妙。它使用了多個鎖來控制對Hash表的不同Segment進行的修改乘综。

ConcurrentHashMap的應(yīng)用場景是高并發(fā),但是并不能保證線程安全套硼,而同步的HashMap和HashTable的是鎖住整個容器卡辰,而加鎖之后ConcurrentHashMap不需要鎖住整個容器,只需要鎖住對應(yīng)的segment就好了邪意,所以可以保證高并發(fā)同步訪問九妈,提升了效率。

友情鏈接:Java集合—ConcurrentHashMap原理分析

ThreadPoolExecutor 的內(nèi)部工作原理

進程間的通信方式

  1. 管道( pipe ):管道是一種半雙工的通信方式雾鬼,數(shù)據(jù)只能單向流動锻梳,而且只能在具有親緣關(guān)系的進程間使用绎狭。進程的親緣關(guān)系通常是指父子進程關(guān)系。
  2. 有名管道 (named pipe) : 有名管道也是半雙工的通信方式,但是它允許無親緣關(guān)系進程間的通信豆村。
    3.信號量( semophore ) : 信號量是一個計數(shù)器糟把,可以用來控制多個進程對共享資源的訪問袜炕。它常作為一種鎖機制,防止某進程正在訪問共享資源時竟块,其他進程也訪問該資源壶运。因此,主要作為進程間以及同一進程內(nèi)不同線程之間的同步手段浪秘。
  3. 消息隊列( message queue ) : 消息隊列是由消息的鏈表蒋情,存放在內(nèi)核中并由消息隊列標識符標識。消息隊列克服了信號傳遞信息少耸携、管道只能承載無格式字節(jié)流以及緩沖區(qū)大小受限等缺點棵癣。
    5.信號 ( sinal ) : 信號是一種比較復(fù)雜的通信方式,用于通知接收進程某個事件已經(jīng)發(fā)生夺衍。
    6.共享內(nèi)存( shared memory ) :共享內(nèi)存就是映射一段能被其他進程所訪問的內(nèi)存狈谊,這段共享內(nèi)存由一個進程創(chuàng)建,但多個進程都可以訪問沟沙。共享內(nèi)存是最快的 IPC 方式河劝,它是針對其他進程間通信方式運行效率低而專門設(shè)計的。它往往與其他通信機制矛紫,如信號量赎瞎,配合使用,來實現(xiàn)進程間的同步和通信颊咬。
    7.套接字( socket ) : 套解口也是一種進程間通信機制务甥,與其他通信機制不同的是牡辽,它可用于不同機器間的進程通信。

死鎖的必要條件

  1. 互斥 至少有一個資源處于非共享狀態(tài)
  2. 占有并等待
  3. 非搶占
  4. 循環(huán)等待
    解決死鎖敞临,第一個是死鎖預(yù)防态辛,就是不讓上面的四個條件同時成立。二是挺尿,合理分配資源奏黑。
    三是使用銀行家算法,如果該進程請求的資源操作系統(tǒng)剩余量可以滿足票髓,那么就分配攀涵。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市洽沟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜗细,老刑警劉巖裆操,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異炉媒,居然都是意外死亡踪区,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門吊骤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缎岗,“玉大人,你說我怎么就攤上這事白粉〈矗” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵鸭巴,是天一觀的道長眷细。 經(jīng)常有香客問我,道長鹃祖,這世上最難降的妖魔是什么溪椎? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮恬口,結(jié)果婚禮上校读,老公的妹妹穿的比我還像新娘。我一直安慰自己祖能,他們只是感情好歉秫,可當我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芯杀,像睡著了一般端考。 火紅的嫁衣襯著肌膚如雪雅潭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天却特,我揣著相機與錄音扶供,去河邊找鬼。 笑死裂明,一個胖子當著我的面吹牛椿浓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播闽晦,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼扳碍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了仙蛉?” 一聲冷哼從身側(cè)響起笋敞,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荠瘪,沒想到半個月后夯巷,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡哀墓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年趁餐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片篮绰。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡后雷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吠各,到底是詐尸還是另有隱情臀突,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布走孽,位于F島的核電站惧辈,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏磕瓷。R本人自食惡果不足惜盒齿,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望困食。 院中可真熱鬧边翁,春花似錦、人聲如沸硕盹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瘩例。三九已至啊胶,卻和暖如春甸各,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背焰坪。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工趣倾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人某饰。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓儒恋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親黔漂。 傳聞我的和親對象是個殘疾皇子诫尽,可洞房花燭夜當晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法炬守,內(nèi)部類的語法牧嫉,繼承相關(guān)的語法,異常的語法劳较,線程的語...
    子非魚_t_閱讀 31,581評論 18 399
  • 轉(zhuǎn)自:http://blog.csdn.net/jackfrued/article/details/4492194...
    王帥199207閱讀 8,498評論 3 93
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,790評論 0 11
  • 正像人們說的那樣驹止,當班主任過去靠經(jīng)驗,今天靠科學(xué)。我們要塑造的是健康的學(xué)生,我們要塑造學(xué)生的追求观蜗、學(xué)生的...
    賀子蘭閱讀 571評論 0 9
  • 作者:快樂小散 “和什么樣的人同行真的很重要”,十年前就曾看到過這句話衣洁,但沒有深刻的理解墓捻。因為那時無權(quán)選擇和誰在一...
    快樂小散閱讀 1,561評論 0 0