備戰金三銀四,JVM和MyBatis、spring知識點大梳理(附面試題大全

JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用於計算設備的規範,它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的。

引入Java語言虛擬機後,Java語言在不同平臺上運行時不需要重新編譯。Java語言使用Java虛擬機屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平臺上不加修改地運行。

JVM

前言:

Java 虛擬機,是一個可以執行 Java 字節碼的虛擬機進程。Java 源文件被編譯成能被 Java 虛擬機執行的字節碼文件( .class )。

跨平臺的是 Java 程序(包括字節碼文件),,而不是 JVM。JVM 是用 C/C++ 開發的,是編譯後的機器碼,不能跨平臺,不同平臺下需要安裝不同版本的 JVM 。

JVM 組成部分


類加載器,在 JVM 啟動時或者類運行時將需要的 class 加載到 JVM 中。內存區,將內存劃分成若干個區以模擬實際機器上的存儲、記錄和調度功能模塊,如實際機器上的各種功能的寄存器或者 PC 指針的記錄器等。執行引擎,執行引擎的任務是負責執行 class 文件中包含的字節碼指令,相當於實際機器上的 CPU 。本地方法調用,調用 C 或 C++ 實現的本地方法的代碼返回結果。

1、類加載器

從類被加載到虛擬機內存中開始,到卸御出內存為止,它的整個生命週期分為7個階段, 加載(Loading)驗證(Verification)準備(Preparation)解析(Resolution)初始化(Initialization)使用(Using)卸御(Unloading) 。其中驗證、準備、解析三個部分統稱為連接。7個階段發生的順序如下:

加載(Loading)、驗證(Verification)、準備(Preparation)、初始化(Initialization)、卸載(Unloading) 這五個階段的過程是 固定 的,在類加載過程中必須按照這種順序按部就班地進行,而解析階段則不一定,他在某種情況下可以在初始化之後進行,這個是為了支持Java語言的運行時綁定(也稱為動態綁定或者晚期綁定)。

1.1、加載

加載階段,虛擬機需要完成3件事:

通過一個類的全限定名獲取定義此類的二進制字節流。將這個字節流所代表的靜態存儲結構轉換為方法區的運行時數據結構。在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據結構的訪問入口。

加載階段完成後,虛擬機外部的 二進制字節流就按照虛擬機所需的格式存儲在方法區之中,而且在Java堆中也創建一個java.lang.Class類的對象,這樣便可以通過該對象訪問方法區中的這些數據。

1.2、驗證

驗證階段主要,這一階段的目的是為了確保Class文件的字節流中包含的信息符合虛擬機的要求,並且不會危害虛擬機自身的安全。

驗證階段主要完成下面4個階段的校驗動作:

文件格式驗證:驗證字節流是否符合Class文件格式的規範;例如:是否以0xCAFEBABE開頭、主次版本號是否在當前虛擬機的處理範圍之內、常量池中的常量是否有不被支持的類型。元數據驗證:對字節碼描述的信息進行語義分析(注意:對比javac編譯階段的語義分析),以保證其描述的信息符合Java語言規範的要求;例如:這個類是否有父類,除了java.lang.Object之外。字節碼驗證:通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的。符號引用驗證:確保解析動作能正確執行。


1.3、準備

準備階段是正式為類變量分配內存並設置初始值的階段,這些變量所使用的內存都將在方法區分配。

進行內存分配的僅包括類變量(static),而不包括實例變量,實例變量會在對象實例化時隨著對象一塊分配在Java堆中。

初始值通常情況下是數據類型默認的零值(如0、0L、null、false等)

1.4、解析

解析階段是將虛擬機常量池內的符號引用替換為直接引用的過程。解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符號進行.

符號引用:簡單的理解就是字符串,比如引用一個類,java.util.ArrayList 這就是一個符號引用,字符串引用的對象不一定被加載。

直接引用:指針或者地址偏移量。引用對象一定在內存(已經加載)。

1.5、初始化

類初始化是類加載的最後一步,除了加載階段,用戶可以通過自定義的類加載器參與,其他階段都完全由虛擬機主導和控制。到了初始化階段才真正執行Java代碼。

類的初始化的主要工作是為 靜態變量賦程序設定的初值

如static int a = 100;在準備階段,a被賦默認值0,在初始化階段就會被賦值為100。

Java虛擬機規範中嚴格規定了有且只有五種情況必須對類進行初始化:

1、使用new字節碼指令創建類的實例,或者使用getstatic、putstatic讀取或設置一個靜態字段的值(放入常量池中的常量除外),或者調用一個靜態方法的時候,對應類必須進行過初始化。2、通過java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則要首先進行初始化。3、當初始化一個類的時候,如果發現其父類沒有進行過初始化,則首先觸發父類初始化。4、當虛擬機啟動時,用戶需要指定一個主類(包含main()方法的類),虛擬機會首先初始化這個類。5、使用jdk1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,並且這個方法句柄對應的類沒有進行初始化,則需要先觸發其初始化。

2、對象的創建過程


Java 中對象的創建就是在堆上分配內存空間的過程,此處說的對象創建僅限於 new 關鍵字創建的普通 Java 對象,不包括數組對象的創建。

當虛擬機遇到一條含有new的指令時,會進行一系列對象創建的操作:

2.1、檢查類是否被加載

1、檢查常量池中是否有即將要創建的這個對象所屬的類的符號引用;若常量池中沒有這個類的符號引用,說明這個類還沒有被定義!拋出ClassNotFoundException;

2、進而檢查這個符號引用所代表的類是否已經被JVM加載;若該類還沒有被加載,就找該類的class文件,並加載進方法區;若該類已經被JVM加載,則準備為對象分配內存;

2.2、為對象分配內存

根據方法區中該類的信息確定該類所需的內存大小;一個對象所需的內存大小是在這個對象所屬類被定義完就能確定的!且一個類所生產的所有對象的內存大小是一樣的!JVM在一個類被加載進方法區的時候就知道該類生產的每一個對象所需要的內存大小。4、從堆中劃分一塊對應大小的內存空間給新的對象;分配堆中內存有兩種方式:指針碰撞 如果JVM的垃圾收集器採用複製算法或標記-整理算法,那麼堆中空閒內存是完整的區域,並且空閒內存和已使用內存之間由一個指針標記。那麼當為一個對象分配內存時,只需移動指針即可。因此,這種在完整空閒區域上通過移動指針來分配內存的方式就叫做“指針碰撞”。空閒列表 如果JVM的垃圾收集器採用標記-清除算法,那麼堆中空閒區域和已使用區域交錯,因此需要用一張“空閒列表”來記錄堆中哪些區域是空閒區域,從而在創建對象的時候根據這張“空閒列表”找到空閒區域,並分配內存。綜上所述:JVM究竟採用哪種內存分配方法,取決於它使用了何種垃圾收集器。

多線程併發時會出現正在給對象 A 分配內存,還沒來得及修改指針,對象 B 又用這個指針分配內存,這樣就出現問題了。

解決這種問題有兩種方案:

第一種,是採用同步的辦法,使用 CAS 來保證操作的原子性。

另一種,是每個線程分配內存都在自己的空間內進行,即是每個線程都在堆中預先分配一小塊內存,稱為本地線程分配緩衝(Thread Local Allocation Buffer, TLAB),分配內存的時候再TLAB上分配,互不干擾。可以通過 -XX:+/-UseTLAB 參數決定。

2.3、為分配的內存空間初始化零值

為對象中的成員變量賦上初始值(默認初始化);

對象的內存分配完成後,還需要將對象的內存空間都初始化為零值,這樣能保證對象即使沒有賦初值,也可以直接使用

2.4、為對象進行其他設置

設置對象頭中的信息;

所屬的類,類的元數據信息,對象的 hashcode ,GC 分代年齡等信息

2.5、執行 init 方法

調用對象的構造函數進行初始化

執行完上面的步驟之後,在虛擬機裡這個對象就算創建成功了,但是對於 Java 程序來說還需要執行 init 方法才算真正的創建完成,因為這個時候對象只是被初始化零值了,還沒有真正的去根據程序中的代碼分配初始值,調用了 init 方法之後,這個對象才真正能使用。

初始化順序:

在new B一個實例時首先要進行類的裝載。(類只有在使用New調用創建的時候才會被java類裝載器裝入)在裝載類時,先裝載父類A,再裝載子類B裝載父類A後,完成靜態動作(包括靜態代碼和變量,它們的級別是相同的,按照代碼中出現的順序初始化)裝載子類B後,完成靜態動作類裝載完成,開始進行實例化在實例化子類B時,先要實例化父類A2,實例化父類A時,先成員實例化(非靜態代碼)父類A的構造方法子類B的成員實例化(非靜態代碼)子類B的構造方法


先初始化父類的靜態代碼--->初始化子類的靜態代碼-->初始化父類的非靜態代碼--->初始化父類構造函數--->初始化子類非靜態代碼--->初始化子類構造函數

3、對象的內存佈局

3.1、對象頭(markword)

第一部分用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC 分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程 ID、偏向時間戳、對象分代年齡,這部分信息稱為“Mark Word”;Mark Word 被設計成一個非固定的數據結構以便在極小的空間內存儲儘量多的信息,它會根據自己的狀態複用自己的存儲空間。第二部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例;Klass Word 這裡其實是虛擬機設計的一個oop-klass model模型,這裡的OOP是指Ordinary Object Pointer(普通對象指針),看起來像個指針實際上是藏在指針裡的對象。而 klass 則包含 元數據和方法信息,用來描述 Java 類。它在64位虛擬機開啟壓縮指針的環境下佔用 32bits 空間。如果對象是一個 Java 數組,那在對象頭中還必須有一塊用於記錄數組長度的數據。因為虛擬機可以通過普通 Java 對象的元數據信息確定 Java 對象的大小,但是從數組的元數據中無法確定數組的大小。

在32位系統下,對象頭8字節,64位則是16個字節【未開啟壓縮指針,開啟後12字節】。

假設當前為32bit,在對象未被鎖定情況下。25bit為存儲對象的哈希碼、4bit用於存儲分代年齡,2bit用於存儲鎖標誌位,1bit固定為0。

不同狀態下存放數據:

這其中鎖標識位需要特別關注下。鎖標誌位與是否為偏向鎖對應到唯一的鎖狀態。

鎖的狀態分為四種無鎖狀態、偏向鎖、輕量級鎖和重量級鎖

不同狀態時對象頭的區間含義,如圖所示。

3.2、實例數據(Instance Data)

實例數據部分是對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內容。

這部分的存儲順序會受到虛擬機分配策略參數(FieldsAllocationStyle)和字段在 Java 源碼中定義順序的影響。

分配策略:相同寬度的字段總是放在一起,比如double和long

3.3、對其填充(Padding)

對齊填充不是必然存在的,沒有特別的含義,它僅起到佔位符的作用。

由於HotSpot規定對象的大小必須是8的整數倍,對象頭剛好是整數倍,如果實例數據不是的話,就需要佔位符對齊填充。

3.4、預估對象大小

32 位系統下,當使用 new Object() 時,JVM 將會分配 8(Mark Word+類型指針) 字節的空間,128 個 Object 對象將佔用 1KB 的空間。如果是 new Integer(),那麼對象裡還有一個 int 值,其佔用 4 字節,這個對象也就是 8+4=12 字節,對齊後,該對象就是 16 字節。

以上只是一些簡單的對象,那麼對象的內部屬性是怎麼排布的?

Class A { int i; byte b; String str;}

其中對象頭部佔用 ‘Mark Word’4 + ‘類型指針’4 = 8 字節;byte 8 位長,佔用 1 字節;int 32 位長,佔用 4 字節;String 只有引用,佔用 4 字節;那麼對象 A 一共佔用了 8+1+4+4=17 字節,按照 8 字節對齊原則,對象大小也就是 24 字節。

這個計算看起來是沒有問題的,對象的大小也確實是 24 字節,但是對齊(padding)的位置並不對:

在 HotSpot VM 中,對象排布時,間隙是在 4 字節基礎上的(在 32 位和 64 位壓縮模式下),上述例子中,int 後面的 byte,空隙只剩下 3 字節,接下來的 String 對象引用需要 4 字節來存放,因此 byte 和對象引用之間就會有 3 字節對齊,對象引用排布後,最後會有 4 字節對齊,因此結果上依然是 7 字節對齊。此時對象的結構示意圖,如下圖所示:

4、對象訪問

對象的訪問方式由虛擬機決定,java虛擬機提供兩種主流的方式

1.句柄訪問對象

2.直接指針訪問對象。(Sun HotSpot使用這種方式)

4.1、句柄訪問

簡單來說就是java堆劃出一塊內存作為句柄池,引用中存儲對象的句柄地址,句柄中包含對象實例數據、類型數據的地址信息。

優點:引用中存儲的是穩定的句柄地址,在對象被移動【垃圾收集時移動對象是常態】只需改變句柄中實例數據的指針,不需要改動引用【ref】本身。

4.2、直接指針

與句柄訪問不同的是,ref中直接存儲的就是對象的實例數據,但是類型數據跟句柄訪問方式一樣。

優點:優勢很明顯,就是速度快,相比於句柄訪問少了一次指針定位的開銷時間。【可能是出於Java中對象的訪問時十分頻繁的,平時我們常用的JVM HotSpot採用此種方式】

5、JVM 內存區域


5.1、虛擬機棧

描述的是方法執行時的 內存模型 ,是 線程私有 的,生命週期與線程相同,每個方法被執行的同時會創建棧楨,主要保存執行方法時的 局部變量表、操作數棧、動態連接和方法返回地址 等信息,方法執行時入棧,方法執行完出棧,出棧就相當於清空了數據,入棧出棧的時機很明確,所以這塊區域不需要進行 GC。

Java虛擬機棧可能出現兩種類型的異常:

線程請求的棧深度大於虛擬機允許的棧深度,將拋出 StackOverflowError虛擬機棧空間可以動態擴展,當動態擴展是無法申請到足夠的空間時,拋出 OutOfMemory異常拓展link: 棧幀

5.2、本地方法棧

與虛擬機棧功能非常類似,主要區別在於虛擬機棧為虛擬機執行 Java 方法時服務,而本地方法棧為虛擬機執行 本地方法

時服務的。這塊區域也不需要進行 GC。

5.3、程序計數器

程序計數器是一塊很小的內存空間,它是線程私有的,可以認作為當前線程的行號指示器。程序計數器的主要作用是記錄線程運行時的狀態,方便線程被喚醒時能從上一次被掛起時的狀態繼續執行程序計數器是唯一一個在 Java 虛擬機規範中沒有規定任何 OOM 情況的區域 ,所以這塊區域也不需要進行 GC

5.4、本地內存

線程共享區域,Java 8 中,本地內存,也是我們通常說的堆外內存,包括元空間和方法區主要存儲類的信息,常量,靜態變量,即時編譯器編譯後代碼等,這部分由於是在堆中實現的,受 GC 的管理,不過由於永久代有 -XX:MaxPermSize 的上限所以如果動態生成類(將類信息放入永久代)或大量地執行 String.intern (將字段串放入永久代中的常量區),很容易造成 OOM,有人說可以把永久代設置得足夠大,但很難確定一個合適的大小,受類數量,常量數量的多少影響很大。所以在 Java 8 中就把方法區的實現移到了本地內存中的元空間中,這樣方法區就不受 JVM 的控制了,也就不會進行 GC,也因此提升了性能(發生 GC 會發生 Stop The Word,造成性能受到一定影響,後文會提到),也就不存在由於永久代限制大小而導致的 OOM 異常了(假設總內存2G,JVM 被分配內存 100M, 理論上元空間可以分配 2G-100M = 1.9G,空間大小足夠),也方便在元空間中統一管理。綜上所述,在 Java 8 以後這一區域也不需要進行 GC拓展link: 堆外內存回收

5.5、堆

對象實例和數組都是在堆上分配的,GC 也主要對這兩類數據進行回收。java虛擬機規範對這塊的描述是:所有對象實例及數組都要在堆上分配內存,但隨著JIT編譯器的發展和 逃逸分析技術 的成熟,這個說法也不是那麼絕對,但是大多數情況都是這樣的。堆細分:新生代(Eden,survior)和老年代

6、對象存活判斷

引用計數可達性分析

6.1、引用計數

每個對象有一個引用計數屬性,新增一個引用時計數加 1 ,引用釋放時計數減 1 ,計數為 0 時可以回收。此方法簡單,無法解決對象相互循環引用的問題。目前在用的有 Python、ActionScript3 等語言。

6.2、可達性分析

從 GC Roots 開始向下搜索,搜索所走過的路徑稱為引用鏈。當一個對象到 GC Roots 沒有任何引用鏈相連時,則證明此對象是不可用的。不可達對象。目前在用的有 Java、C# 等語言。

GC Roots 對象:

虛擬機棧(棧幀中的本地變量表)中引用的對象。方法區中的類靜態屬性引用的對象。方法區中常量引用的對象。本地方法棧中 JNI(即一般說的 Native 方法)中引用的對象。

如何判斷無用的類:

該類所有實例都被回收(Java 堆中沒有該類的對象)。加載該類的 ClassLoader 已經被回收。該類對應的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方利用反射訪問該類。


6.3、finalize

finallize()方法,是在釋放該對象內存前由 GC (垃圾回收器)調用。

通常建議在這個方法中釋放該對象持有的資源,例如持有的堆外內存、和遠程服務的長連接。一般情況下,不建議重寫該方法。對於一個對象,該方法有且僅會被調用一次。

6.4、對象引用類型

強引用軟引用(SoftReference)弱引用(WeakReference)虛引用(PhantomReference)

6.4.1、強引用

如果一個對象具有強引用,那就類似於必不可少的生活用品,垃圾回收器絕不會回收它。當內存空間不足,Java 虛擬機寧願拋出 OutOfMemoryError 錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足問題

6.4.2、軟引用

如果一個對象只具有軟引用,那就類似於可有可無的生活用品。如果內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。

6.4.3、弱引用

弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它 所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。

6.4.4、虛引用

“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命週期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。。當垃 圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。程序如果發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的內存被回收之前採取必要的行動。

拓展

利用軟引用和弱引用解決 OOM 問題。用一個 HashMap 來保存圖片的路徑和相應圖片對象關聯的軟引用之間的映射關係,在內存不足時,JVM 會自動回收這些緩存圖片對象所佔用的空間,從而有效地避免了 OOM 的問題. 通過軟引用實現 Java 對象的高速緩存。比如我們創建了一 Person 的類,如果每次需要查詢一個人的信息,哪怕是幾秒中之前剛剛查詢過的,都要重新構建一個實例,這將引起大量 Person 對象的消耗,並且由於這些對象的生命週期相對較短,會引起多次 GC 影響性能。此時,通過軟引用和 HashMap 的結合可以構建高速緩存,提供性能。

7、垃圾回收算法

標記-清除算法標記-整理算法複製算法分代收集算法

7.1、標記-清除

在標記階段,首先通過根節點,標記所有從根節點開始的可達對象。因此,未被標記的對象就是未被引用的垃圾對象(好多資料說標記出要回收的對象,其實明白大概意思就可以了)。然後,在清除階段,清除所有未被標記的對象。

缺點:

1、效率問題,標記和清除兩個過程的效率都不高。2、空間問題,標記清除之後會產生大量不連續的內存碎片,空間碎片太多可能會導致以後在程序運行過程中需要分配較大的對象時,無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。

7.2、標記-整理

標記整理算法,類似與標記清除算法,不過它標記完對象後,不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉邊界以外的內存。

優點:

1、相對標記清除算法,解決了內存碎片問題。2、沒有內存碎片後,對象創建內存分配也更快速了(可以使用TLAB進行分配)。

缺點:

1、效率問題,(同標記清除算法)標記和整理兩個過程的效率都不高。

7.3、複製算法

複製算法,可以解決效率問題,它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊,當這一塊內存用完了,就將還存活著的對象複製到另一塊上面,然後再把已經使用過的內存空間一次清理掉,這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況,只要移動堆頂指針,按順序分配內存即可(還可使用TLAB進行高效分配內存)

優點:

1、效率高,沒有內存碎片。

缺點:

1、浪費一半的內存空間。2、複製收集算法在對象存活率較高時就要進行較多的複製操作,效率將會變低。

7.4、分代算法

當前商業虛擬機都是採用分代收集算法,它根據對象存活週期的不同將內存劃分為幾塊,一般是把 Java 堆分為新生代和老年代,然後根據各個年代的特點採用最適當的收集算法。

在新生代中,每次垃圾收集都發現有大批對象死去,只有少量存活,就選用複製算法。而老年代中,因為對象存活率高,沒有額外空間對它進行分配擔保,就必須使用“標記清理”或者“標記整理”算法來進行回收。

圖的左半部分是未回收前的內存區域,右半部分是回收後的內存區域。

對象分配策略:對象優先在 Eden 區域分配,如果對象過大直接分配到 Old 區域。長時間存活的對象進入到 Old 區域。

改進自複製算法

現在的商業虛擬機都採用這種收集算法來回收新生代,IBM 公司的專門研究表明,新生代中的對象 98% 是“朝生夕死”的,所以並不需要按照 1:1 的比例來劃分內存空間,而是將內存分為一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 和其中一塊 Survivor 。當回收時,將 Eden 和 Survivor 中還存活著的對象一次性地複製到另外一塊 Survivor 空間上,最後清理掉 Eden 和剛才用過的 Survivor 空間。HotSpot 虛擬機默認 Eden 和 2 塊 Survivor 的大小比例是 8:1:1,也就是每次新生代中可用內存空間為整個新生代容量的 90%(80%+10%),只有 10% 的內存會被“浪費”。當然,98% 的對象可回收只是一般場景下的數據,我們沒有辦法保證每次回收都只有不多於 10% 的對象存活,當 Survivor 空間不夠用時,需要依賴其他內存(這裡指老年代)進行分配擔保(Handle Promotion)。

8、安全點

8.1、安全點

SafePoint 安全點,顧名思義是指一些特定的位置,當線程運行到這些位置時,線程的一些狀態可以被確定(the thread’s representation of it’s Java machine state is well described),比如記錄OopMap 的狀態,從而確定 GC Root 的信息,使 JVM 可以安全的進行一些操作,比如開始 GC 。

SafePoint 指的特定位置主要有:

循環的末尾 (防止大循環的時候一直不進入 Safepoint ,而其他線程在等待它進入 Safepoint )。方法返回前。調用方法的 Call 之後。拋出異常的位置。

8.2、安全區域

安全點完美的解決了如何進入GC問題,實際情況可能比這個更復雜,但是如果程序長時間不執行,比如線程調用的sleep方法,這時候程序無法響應JVM中斷請求這時候線程無法到達安全點,顯然JVM也不可能等待程序喚醒,這時候就需要安全區域了。

安全區域是指一段代碼片中,引用關係不會發生變化,在這個區域任何地方GC都是安全的,安全區域可以看做是安全點的一個擴展。線程執行到安全區域的代碼時,首先標識自己進入了安全區域,這樣GC時就不用管進入安全區域的線層了,線層要離開安全區域時就檢查JVM是否完成了GC Roots枚舉,如果完成就繼續執行,如果沒有完成就等待直到收到可以安全離開的信號。

9、JVM 垃圾回收器



9.1、Serial (新生代)

最基本的單線程垃圾收集器。使用一個CPU或一條收集線程去執行垃圾收集工作。工作時會Stop The World,暫停所有用戶線程,造成卡頓。適合運行在Client模式下的虛擬機。用作新生代收集器,複製算法。

9.2、ParNew(新生代)

Serial收集器的多線程版本,和Serial的唯一區別就是使用了多條線程去垃圾收集。除了Serial,只有它可以和CMS搭配使用的收集器。用作新生代收集器,複製算法。

9.3、Parallel Scavenge(新生代)

用作新生代收集器,複製算法。關注高吞吐量,可以高效率地利用CPU時間,儘快完成程序的運算任務,主要適合在後臺運算而不需要太多交互的任務。Parallel Scavenge收集器提供了兩個參數用於精確控制吞吐量,分別是控制最大垃圾收集停頓時間的-XX:MaxGCPauseMillis參數以及直接設置吞吐量大小的-XX:GCTimeRatio參數。

9.4、Serial Old(老年代)

Serial收集器的老年代版本,單線程,標記-整理 算法。一般用於Client模式的虛擬機。當虛擬機是Server模式時,有2個用途:一種用途是在JDK 1.5以及之前的版本中與Parallel Scavenge收集器搭配使用 ,另一種用途就是作為CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用。

9.5、Parallel Old(老年代)

Parallel Scavenge收集器的老年代版本,使用多線程和 標記-整理 算法。在JDK 1.6中開始提供。在注重吞吐量的場合,配合Parallel Scavenge收集器使用。

9.6、CMS(Concurrent Mark Sweep)(老年代)

一種以獲取最短回收停頓時間為目標的收集器。適合需要與用戶交互的程序,良好的響應速度能提升用戶體驗。基於 標記—清除 算法。適合作為老年代收集器。收集過程分4步:初始標記(CMS initial mark):只是標記一下GC Roots能直接關聯到的對象,速度很快,會Stop The World。併發標記(CMS concurrent mark):進行GC Roots Tracing(可達性分析)的過程。重新標記(CMS remark):會Stop The -World。為了修正併發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間一般比初始標記階段稍長些,但遠比並發標記的時間短。併發清除(CMS concurrent sweep):回收內存。

耗時最長的併發標記和併發清除過程收集器線程都可以與用戶線程一起工作,所以時併發執行的。

缺點:

併發階段,雖然不會導致用戶線程暫停,但會佔用一部分線程(CPU資源),導致應用變慢,吞吐量降低。默認啟動收集線程數是(CPU數量+3)/4。即當CPU在4個以上時,併發回收時垃圾收集線程不少於25%的CPU資源,並且隨著CPU數量的增加而下降。但是當CPU不足4個(譬如2個)時,CMS對用戶程序的影響就可能變得很大。無法清除浮動垃圾。併發清除階段,用戶線程還在運行,還會產生新垃圾。這些垃圾不會在此次GC中被標記,只能等到下次GC被回收。標記-清除 算法會產生大量不連續內存,導致分配大對象時內存不夠,提前觸發Full GC。

9.7、G1

-XX:G1HeapRegionSize


E:eden區,新生代S:survivor區,新生代O:old區,老年代H:humongous區,用來放大對象。當新建對象大小超過region大小一半時,直接在新的一個或多個連續region中分配,並標記為H

可預測的停頓時間:估算每個region內的垃圾可回收的空間以及回收需要的時間(經驗值),記錄在一個優先列表中。收集時,優先回收價值最大的region,而不是在整個堆進行全區域回收。這樣提高了回收效率,得名:Garbage-First。G1中有2種GC:

young GC:新生代eden區沒有足夠可用空間時觸發。存活的對象移到survivor區或晉升old區。mixed GC:當old區對象很多時,老年代對象空間佔堆總空間的比值達到閾值(-XX:InitiatingHeapOccupancyPercent默認45%)會觸發,它除了回收年輕代,也回收 部分 老年代(回收價值高的部分region)。

mixed GC回收步驟:

初始標記(Initial Marking):只是標記一下GC Roots能直接關聯到的對象,並且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序併發運行時,能在正確可用的Region中創建新對象。這階段需要停頓線程(STW),但耗時很短,共用YGC的停頓,所以一般伴隨著YGC發生。併發標記(Concurrent Marking):進行可達性分析,找出存活對象,耗時長,但可與用戶線程併發執行。最終標記(Final Marking):修正併發標記階段用戶線程運行導致的變動記錄。會STW,但可以並行執行,時間不會很長。篩選回收(Live Data Counting and Evacuation):根據每個region的回收價值和回收成本排序,根據用戶配置的GC停頓時間開始回收。


當對象分配過快,mixed GC來不及回收,G1會退化,觸發Full GC,它使用單線程的Serial收集器來回收,整個過程STW,要儘量避免這種情況。

當內存很少的時候(存活對象佔用大量空間),沒有足夠空間來複制對象,會導致回收失敗。這時會保留被移動過的對象和沒移動的對象,只調整引用。失敗發生後,收集器認為存活對象被移動了,有足夠空間讓應用程序使用,於是用戶線程繼續工作,等待下一次觸發GC。如果內存不夠,就會觸發Full GC。

9.8、ZGC

在JDK 11當中,加入了實驗性質的ZGC。它的回收耗時平均不到2毫秒。它是一款低停頓高併發的收集器。

ZGC幾乎在所有地方併發執行的,除了初始標記的是STW的。所以停頓時間幾乎就耗費在初始標記上,這部分的實際是非常少的。那麼其他階段是怎麼做到可以併發執行的呢?

ZGC主要新增了兩項技術,

著色指針Colored Pointer,讀屏障Load Barrier。

ZGC 是一個 併發、基於區域(region)、增量式壓縮 的收集器。Stop-The-World 階段只會在根對象掃描(root scanning)階段發生,這樣的話 GC 暫停時間並不會隨著堆和存活對象的數量而增加。

處理階段:

標記(Marking);重定位(Relocation)/壓縮(Compaction);重新分配集的選擇(Relocation set selection);引用處理(Reference processing);弱引用的清理(WeakRefs Cleaning);字符串常量池(String Table)和符號表(Symbol Table)的清理;類卸載(Class unloading)

著色指針Colored Pointer

ZGC利用指針的64位中的幾位表示Finalizable、Remapped、Marked1、Marked0(ZGC僅支持64位平臺),以標記該指向內存的存儲狀態。

相當於在對象的指針上標註了對象的信息。注意,這裡的指針相當於Java術語當中的引用。

在這個被指向的內存發生變化的時候(內存在Compact被移動時),顏色就會發生變化。

由於著色指針的存在,在程序運行時訪問對象的時候,可以輕易知道對象在內存的存儲狀態(通過指針訪問對象),

讀屏障Load Barrier

若請求讀的內存在被著色了,那麼則會觸發讀屏障。讀屏障會更新指針再返回結果,此過程有一定的耗費,從而達到與用戶線程併發的效果。

與標記對象的傳統算法相比,ZGC在指針上做標記,在訪問指針時加入Load Barrier(讀屏障),比如當對象正被GC移動,指針上的顏色就會不對,這個屏障就會先把指針更新為有效地址再返回,也就是,永遠只有單個對象讀取時有概率被減速,而不存在為了保持應用與GC一致而粗暴整體的Stop The World。

最後

看完本章,相信你對JVM重新有了一定的認識,如果覺得有收穫的話,可以幫我點一個在看,謝謝你的支持。

MyBatis

前言

是一個優秀的持久層ORM框架,它對jdbc的操作數據庫的過程進行封裝,使開發者只需要關注SQL 本身,而不需要花費精力去處理例如註冊驅動、創建connection、創建statement、手動設置參數、結果集檢索等jdbc繁雜的過程代碼。

Mybatis通過xml或註解的方式將要執行的statement配置起來,並通過java對象和statement中的sql進行映射生成最終執行的sql語句,最後由mybatis框架執行sql並將結果映射成java對象並返回。

MyBatis面試題

MyBatis是什麼?為什麼說Mybatis是半自動ORM映射工具? 它與全自動的區別在哪裡?MyBatis與Hibernate的區別MyBatis編程步驟是什麼樣的?請說說MyBatis的工作原理MyBatis的功能架構是怎樣的Mybatis都有哪些Executor執行器? 它們之間的區別是什麼?Mybatis中如何指定使用哪一種Executor執行器?Mybatis是否支持延遲加載? 如果支持,它的實現原理是什麼?什麼是MyBatis的接口綁定? 有哪些實現方式?使用MyBatis的mapper接口調用時有哪些要求?Mybatis動態sql是做什麼的? 都有哪些動態sql? 能簡述一下動態sql的執行原理不?Mybatis是如何進行分頁的? 分頁插件的原理是什麼?簡述Mybatis的插件運行原理,以及如何編寫一個插件。緩存 Mybatis的一級、二級緩存Mybatis源碼中用了哪些設計模式?


1、MyBatis是什麼?

MyBatis 是一款優秀的持久層框架,一個半 ORM(對象關係映射)框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis消除了幾乎所有的JDBC代碼,比如註冊驅動,獲取、關閉數據庫連接,創建Statement對象,手動設置參數,結果集檢索,這些都不需要,開發人員只需要使用xml進行簡單的配置、映射,就可以操作數據庫。

常見的封裝了jdbc的框架:DbUtils、Hibernate、MyBatis、Spring的JdbcTemplate。

ORM是什麼

ORM(Object Relational Mapping),對象關係映射,對象指的是pojo,關係指的是關係型數據庫中的表、記錄。是一種為了解決關係型數據庫數據與簡單Java對象(POJO)的映射關係的技術。簡單的說,ORM是通過使用描述對象和數據庫之間映射的元數據,將程序中的對象自動持久化到關係型數據庫中。

常用的ORM框架包括MyBatis、Hibernate。


2、為什麼說Mybatis是半自動ORM映射工具? 它與全自動的區別在哪裡?

Hibernate屬於全自動ORM映射工具,使用Hibernate查詢關聯對象或者關聯集合對象時,可以根據對象關係模型直接獲取,所以它是全自動的。

而Mybatis在查詢關聯對象或關聯集合對象時,需要手動編寫sql來完成,所以,稱之為半自動ORM映射工具。

3、MyBatis與Hibernate的區別

Hibernate是一個標準的ORM框架,是全表映射框架。

開發效率要高於MyBatis。開發者只需定義映射關係、pojo,Hibernate會自動生成對應的sql語句、操作數據庫的方法,開發者使用Hibernate提供的方法操作持久層就ok,不需要熟練掌握sql、不需要編寫大量代碼。學習門檻高。開發者要有良好的數據關係模型基礎,需要學習複雜的hql。偏死板、維護有難度。Hibernate自動生成了大量的sql語句、方法,很多都用不到,冗雜。具有良好的數據庫無關性,移植性較好。更換數據庫時,比如從mysql換為oracle,只需更改方言,無需大量修改代碼。適合場景不復雜、對性能要求不高的項目。Hibernate對多表關聯查詢支持較差,更新操作需要發送整個pojo對象(所有字段),不支持儲存過程,不能進行sql優化來提高性能。

MyBatis是一個半自動映射框架。

編碼工作量要比HIbernate大。除了要定義映射關係、pojo,還需要自己寫sql語句、操作數據庫的方法。簡單易上手。稍微有點sql基礎就行。靈活、好維護。可根據需求編寫sql,支持動態sql,可以自定義映射規則、支持存儲過程。不支持數據庫無關性。因為要自己編寫sql,比如查詢,mysql用limit,oracle用rownum,更換數據庫時需要修改sql語句。對於複雜、對性能有要求的項目,MyBatis更合適。對關聯映射、多表查詢支持較好,因為是自己寫sql,可以通過優化sql來提高性能。

總結

MyBatis 是一個小巧、方便、高效、簡單、直接、半自動化的持久層框架,

Hibernate 是一個強大、方便、高效、複雜、間接、全自動化的持久層框架。

MyBatis的解析和運行原理

4、MyBatis編程步驟是什麼樣的?

1、 創建SqlSessionFactory

2、 通過SqlSessionFactory創建SqlSession

3、 通過sqlsession執行數據庫操作

4、 調用session.commit()提交事務

5、 調用session.close()關閉會話

5、請說說MyBatis的工作原理

在學習 MyBatis 程序之前,需要了解一下 MyBatis 工作原理,以便於理解程序。MyBatis 的工作原理如下圖

1)讀取 MyBatis 配置文件:mybatis-config.xml 為 MyBatis 的全局配置文件,配置了 MyBatis 的運行環境等信息,例如數據庫連接信息。

2)加載映射文件。映射文件即 SQL 映射文件,該文件中配置了操作數據庫的 SQL 語句,需要在 MyBatis 配置文件 mybatis-config.xml 中加載。mybatis-config.xml 文件可以加載多個映射文件,每個文件對應數據庫中的一張表。

3)構造會話工廠:通過 MyBatis 的環境等配置信息構建會話工廠 SqlSessionFactory。

4)創建會話對象:由會話工廠創建 SqlSession 對象,該對象中包含了執行 SQL 語句的所有方法。

5)Executor 執行器:MyBatis 底層定義了一個 Executor 接口來操作數據庫,它將根據 SqlSession 傳遞的參數動態地生成需要執行的 SQL 語句,同時負責查詢緩存的維護。

6)MappedStatement 對象:在 Executor 接口的執行方法中有一個 MappedStatement 類型的參數,該參數是對映射信息的封裝,用於存儲要映射的 SQL 語句的 id、參數等信息。

7)輸入參數映射:輸入參數類型可以是 Map、List 等集合類型,也可以是基本數據類型和 POJO 類型。輸入參數映射過程類似於 JDBC 對 preparedStatement 對象設置參數的過程。

8)輸出結果映射:輸出結果類型可以是 Map、 List 等集合類型,也可以是基本數據類型和 POJO 類型。輸出結果映射過程類似於 JDBC 對結果集的解析過程。

6、MyBatis的功能架構是怎樣的

MyBatis 最上面是接口層,接口層就是開發人員在 Mapper 或者是 Dao 接口中的接口定義,是查詢、新增、更新還是刪除操作;中間層是數據處理層,主要是配置 Mapper -> XML 層級之間的參數映射,SQL 解析,SQL 執行,結果映射的過程。上述兩種流程都由基礎支持層來提供功能支撐,基礎支持層包括連接管理,事務管理,配置加載,緩存處理等。


我們把Mybatis的功能架構分為三層:

API接口層:提供給外部使用的接口API,開發人員通過這些本地API來操縱數據庫。接口層一接收到調用請求就會調用數據處理層來完成具體的數據處理。數據處理層:負責具體的SQL查找、SQL解析、SQL執行和執行結果映射處理等。它主要的目的是根據調用的請求完成一次數據庫操作。基礎支撐層:負責最基礎的功能支撐,包括連接管理、事務管理、配置加載和緩存處理,這些都是共用的東西,將他們抽取出來作為最基礎的組件。為上層的數據處理層提供最基礎的支撐。

7、Mybatis都有哪些Executor執行器? 它們之間的區別是什麼?

Mybatis有三種基本的Executor執行器,SimpleExecutor、ReuseExecutor、BatchExecutor。

SimpleExecutor:每執行一次update或select,就開啟一個Statement對象,用完立刻關閉Statement對象。ReuseExecutor:執行update或select,以sql作為key查找Statement對象,存在就使用,不存在就創建,用完後,不關閉Statement對象,而是放置於Map<string>內,供下一次使用。簡言之,就是重複使用Statement對象。/<string>BatchExecutor:執行update(沒有select,JDBC批處理不支持select),將所有sql都添加到批處理中(addBatch()),等待統一執行(executeBatch()),它緩存了多個Statement對象,每個Statement對象都是addBatch()完畢後,等待逐一執行executeBatch()批處理。與JDBC批處理相同。

作用範圍:Executor的這些特點,都嚴格限制在SqlSession生命週期範圍內。

8、Mybatis中如何指定使用哪一種Executor執行器?

在Mybatis配置文件中,在設置(settings)可以指定默認的ExecutorType執行器類型,也可以手動給DefaultSqlSessionFactory的創建SqlSession的方法傳遞ExecutorType類型參數,如SqlSession openSession(ExecutorType execType)。

配置默認的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(prepared statements);BATCH 執行器將重用語句並執行批量更新。

9、Mybatis是否支持延遲加載? 如果支持,它的實現原理是什麼?

Mybatis僅支持association關聯對象和collection關聯集合對象的延遲加載,association指的就是一對一,collection指的就是一對多查詢。在Mybatis配置文件中,可以配置是否啟用延遲加載lazyLoadingEnabled=true|false。

它的原理是,使用CGLIB創建目標對象的代理對象,當調用目標方法時,進入攔截器方法,比如調用a.getB().getName(),攔截器invoke()方法發現a.getB()是null值,那麼就會單獨發送事先保存好的查詢關聯B對象的sql,把B查詢上來,然後調用a.setB(b),於是a的對象b屬性就有值了,接著完成a.getB().getName()方法的調用。這就是延遲加載的基本原理。

當然了,不光是Mybatis,幾乎所有的包括Hibernate,支持延遲加載的原理都是一樣的。

10、什麼是MyBatis的接口綁定? 有哪些實現方式?

接口綁定,就是在MyBatis中任意定義接口,然後把接口裡面的方法和SQL語句綁定,我們直接調用接口方法就可以,這樣比起原來了SqlSession提供的方法我們可以有更加靈活的選擇和設置。

接口綁定有兩種實現方式

通過註解綁定,就是在接口的方法上面加上 @Select、@Update等註解,裡面包含Sql語句來綁定;

通過xml裡面寫SQL來綁定, 在這種情況下,要指定xml映射文件裡面的namespace必須為接口的全路徑名。當Sql語句比較簡單時候,用註解綁定, 當SQL語句比較複雜時候,用xml綁定,一般用xml綁定的比較多。

11、使用MyBatis的mapper接口調用時有哪些要求?

1、Mapper接口方法名和mapper.xml中定義的每個sql的id相同。

2、Mapper接口方法的輸入參數類型和mapper.xml中定義的每個sql 的parameterType的類型相同。

3、Mapper接口方法的輸出參數類型和mapper.xml中定義的每個sql的resultType的類型相同。

4、Mapper.xml文件中的namespace即是mapper接口的類路徑。

12、Mybatis動態sql是做什麼的? 都有哪些動態sql? 能簡述一下動態sql的執行原理不?

Mybatis動態sql可以讓我們在Xml映射文件內,以標籤的形式編寫動態sql,完成邏輯判斷和動態拼接sql的功能,Mybatis提供了9種動態sql標籤trim|where|set|foreach|if|choose|when|otherwise|bind。

其執行原理為,使用OGNL從sql參數對象中計算表達式的值,根據表達式的值動態拼接sql,以此來完成動態sql的功能。

插件模塊

13、Mybatis是如何進行分頁的? 分頁插件的原理是什麼?

Mybatis使用RowBounds對象進行分頁,它是針對ResultSet結果集執行的內存分頁,而非物理分頁,可以在sql內直接書寫帶有物理分頁的參數來完成物理分頁功能,也可以使用Mybatis分頁插件來完成物理分頁。分頁插件的基本原理是使用Mybatis提供的插件接口,實現自定義插件,在插件的攔截方法內攔截待執行的sql,然後重寫sql,根據dialect方言,添加對應的物理分頁語句和物理分頁參數。

舉例:select * from student,攔截sql後重寫為:select t.* from (select * from student) t limit 0, 10

14、簡述Mybatis的插件運行原理,以及如何編寫一個插件。

Mybatis僅可以編寫針對ParameterHandler、ResultSetHandler、StatementHandler、Executor這4種接口的插件,Mybatis使用JDK的動態代理,為需要攔截的接口生成代理對象以實現接口方法攔截功能,每當執行這4種接口對象的方法時,就會進入攔截方法,具體就是InvocationHandler的invoke()方法,當然,只會攔截那些你指定需要攔截的方法。

實現Mybatis的Interceptor接口並複寫intercept()方法,然後在給插件編寫註解,指定要攔截哪一個接口的哪些方法即可,記住,別忘了在配置文件中配置你編寫的插件。

15、緩存 Mybatis的一級、二級緩存

1)一級緩存: 基於 PerpetualCache 的 HashMap 本地緩存,其存儲作用域為 Session,當 Session flush 或 close 之後,該 Session 中的所有 Cache 就將清空,默認打開一級緩存。

2)二級緩存與一級緩存其機制相同,默認也是採用 PerpetualCache,HashMap 存儲,不同在於其存儲作用域為 Mapper(Namespace),並且可自定義存儲源,如 Ehcache。默認不打開二級緩存,要開啟二級緩存,使用二級緩存屬性類需要實現Serializable序列化接口(可用來保存對象的狀態),可在它的映射文件中配置<cache> ;

3)對於緩存數據更新機制,當某一個作用域(一級緩存 Session/二級緩存Namespaces)的進行了C/U/D 操作後,默認該作用域下所有 select 中的緩存將被 clear。

16、Mybatis源碼中用了哪些設計模式?

Builder模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;工廠模式 ,例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;單例模式 ,例如ErrorContext和LogFactory;代理模式 ,Mybatis實現的核心,比如MapperProxy、ConnectionLogger,用的jdk的動態代理;還有executor.loader包使用了cglib或者javassist達到延遲加載的效果;組合模式 ,例如SqlNode和各個子類ChooseSqlNode等;模板方法模式 ,例如BaseExecutor和SimpleExecutor,還有BaseTypeHandler和所有的子類例如IntegerTypeHandler;適配器模式 ,例如Log的Mybatis接口和它對jdbc、log4j等各種日誌框架的適配實現;裝飾者模式 ,例如Cache包中的cache.decorators子包中等各個裝飾者的實現;迭代器模式 ,例如迭代器模式PropertyTokenizer;

至於為什麼要用這些設計模式,大家可以觀看我的往期文章,裡面會給你想要的答案。

看完如果覺得有收穫的話,可以幫我點一個在看,謝謝你的支持。

spring

一、關於Spring

Spring是一個分層的Java SE/EE應用一站式的 輕量級開源框架

Spring主要優點包括:

方便解耦,簡化開發,通過Spring提供的IoC容器,我們可以將對象之間的依賴關係交由Spring進行控制,避免硬編碼造成的程序耦合度高。AOP編程的支持,通過Spring提供的AOP功能,方便進行面向切面編程。聲明式事務的支持,在Spring中,我們可以從單調煩悶的事務管理代碼中解脫出來,通過聲明式方式靈活地進行事務的管理,提高開發效率和質量。方便程序的測試,可以用非容器依賴的編程方式進行幾乎所有的測試工作。方便集成各種優秀框架,Spring提供了對各種優秀框架的直接支持。

Spring核心是 IOCAOP

IOC

IOC(Inversion Of Controll,控制反轉)是一種設計思想,將原本在程序中手動創建對象的控制權,交由給Spring框架來管理。IOC容器是Spring用來實現IOC的載體,IOC容器實際上就是一個Map(key, value),Map中存放的是各種對象。

AOP

AOP(Aspect-Oriented Programming,面向切面編程)能夠將那些與業務無關,卻為業務模塊所共同調用的邏輯或責任(例如事務處理、日誌管理、權限控制等)封裝起來,便於減少系統的重複代碼,降低模塊間的耦合度,並有利於未來的可擴展性和可維護性。使用AOP之後我們可以把一些通用功能抽象出來,在需要用到的地方直接使用即可,這樣可以大大簡化代碼量,提高了系統的擴展性。

Spring AOP / AspectJ AOP 的區別?

Spring AOP屬於運行時增強,而AspectJ是編譯時增強。

Spring AOP基於代理(Proxying),而AspectJ基於字節碼操作(Bytecode Manipulation)。

AspectJ相比於Spring AOP功能更加強大,但是Spring AOP相對來說更簡單。如果切面比較少,那麼兩者性能差異不大。但是,當切面太多的話,最好選擇AspectJ,它比SpringAOP快很多。

二、Spring體系結構

如下圖所示,整個spring框架按其所屬功能可以劃分為五個主要模塊,這五個模塊幾乎為企業應用提供了所需的一切,從持久層、業務層到表現層都擁有相應的支持,這就是為什麼稱Spring是一站式框架的原因。

1、核心模塊(Core Container)

Spring的核心模塊實現了IoC的功能,它將類和類之間的依賴從代碼中脫離出來,用配置的方式進行依賴關係描述。由IoC容器負責類的創建,管理,獲取等。BeanFactory接口是Spring框架的核心接口,實現了容器很多核心的功能。

Context模塊構建於核心模塊之上,擴展了BeanFactory的功能,包括國際化,資源加載,郵件服務,任務調度等多項功能。ApplicationContext是Context模塊的核心接口。

表達式語言(Expression Language)是統一表達式語言(EL)的一個擴展,支持設置和獲取對象屬性,調用對象方法,操作數組、集合等。使用它可以很方便的通過表達式和Spring IoC容器進行交互。

2、AOP模塊

Spring AOP模塊提供了滿足AOP Alliance規範的實現,還整合了AspectJ這種AOP語言級的框架。通過AOP能降低耦合。

3、數據訪問集成模塊(Data Access/Integration )

該模塊包括了JDBC、ORM、OXM、JMS和事務管理:

事務模塊:該模塊用於Spring管理事務,只要是Spring管理對象都能得到Spring管理事務的好處,無需在代碼中進行事務控制了,而且支持編程和聲明性的事務管理。JDBC模塊:提供了一個JBDC的樣例模板,使用這些模板能消除傳統冗長的JDBC編碼還有必須的事務控制,而且能享受到Spring管理事務的好處。ORM模塊:提供與流行的“對象-關係”映射框架的無縫集成,包括hibernate、JPA、MyBatis等。而且可以使用Spring事務管理,無需額外控制事務。OXM模塊:提供了一個對Object/XML映射實現,將Java對象映射成XML數據,或者將XML數據映射成java對象,Object/XML映射實現包括JAXB、Castor、XMLBeans和XStream。JMS模塊:用於JMS(Java Messaging Service),提供一套“消息生產者、消息消費者”模板用於更加簡單的使用JMS,JMS用於在兩個應用程序之間,或分佈式系統中發送消息,進行異步通信。

4、Web模塊

該模塊建立在ApplicationContext模塊之上,提供了Web應用的功能,如文件上傳、FreeMarker等。Spring可以整合Struts2等MVC框架。此外,Spring自己提供了MVC框架Spring MVC。

5、測試模塊

Spring可以用非容器依賴的編程方式進行幾乎所有的測試工作,支持JUnit和TestNG等測試框架。

最後

看完本次分享的內容,你是否對Spring有了一個更深層次的認識了呢?

如果有哪位有緣的朋友看到了這篇文章,恰好有對本次Spring分享的內容補充 ,歡迎留言交流。

這三塊內容特別重要,希望大家能夠融匯貫通,最後給大家分享一波一線大廠面試題大全,比較全面,轉發此文關注小編之後,私信小編“面試”即可得到獲取方式!