2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!


2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

就目前大環境來看,跳槽成功的難度比往年高很多。總結一下2019面試的感受:無論一面還是二面,都很考驗Java程序員的技術功底!!
跑了幾家大的公司面試,現在在家等面試結果,應該可以拿到幾個比較好的offer,就這幾家面試而言,我整理了一份複習用的面試題及面試高頻的考點題及技術點梳理成一份“Java程序員高頻面試解析及知識點體系筆記.pdf(實際上比預期多花了不少精力),現在分享給大家。

2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

事實上目錄比較長就不全部發出來了,我後面會陸續更新,大家可以持續關注一下

2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

我們都知道 Java 源文件,通過編譯器,能夠生產相應的.Class 文件,也就是字節碼文件,

而字節碼文件又通過 Java 虛擬機中的解釋器,編譯成特定機器上的機器碼 。

也就是如下:

① Java 源文件—->編譯器—->字節碼文件

② 字節碼文件—->JVM—->機器碼

每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的,這也就是 Java 為什麼能夠

跨平臺的原因了 ,當一個程序從開始運行,這時虛擬機就開始實例化了,多個程序啟動就會

存在多個虛擬機實例。程序退出或者關閉,則虛擬機實例消亡,多個虛擬機實例之間數據不

能共享。


2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

2.1.線程

這裡所說的線程指程序執行過程中的一個線程實體。JVM 允許一個應用併發執行多個線程。

Hotspot JVM 中的 Java 線程與原生操作系統線程有直接的映射關係。當線程本地存儲、緩

衝區分配、同步對象、棧、程序計數器等準備好以後,就會創建一個操作系統原生線程。

Java 線程結束,原生線程隨之被回收。操作系統負責調度所有線程,並把它們分配到任何可

用的 CPU 上。當原生線程初始化完畢,就會調用 Java 線程的 run() 方法。當線程結束時,會釋放原生線程和 Java 線程的所有資源。

Hotspot JVM 後臺運行的系統線程主要有下面幾個:

虛擬機線程

(VM thread)

這個線程等待 JVM 到達安全點操作出現。這些操作必須要在獨立的線程裡執行,因為當

堆修改無法進行時,線程都需要 JVM 位於安全點。這些操作的類型有:stop-the\u0002

world 垃圾回收、線程棧 dump、線程暫停、線程偏向鎖(biased locking)解除。

週期性任務線程

這線程負責定時器事件(也就是中斷),用來調度週期性操作的執行。

GC 線程

這些線程支持 JVM 中不同的垃圾回收活動。

編譯器線程

這些線程在運行時將字節碼動態編譯成本地平臺相關的機器碼。

信號分發線程

這個線程接收發送到 JVM 的信號並調用適當的 JVM 方法處理。

2.2.JVM 內存區域


2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

JVM 內存區域主要分為線程私有區域【程序計數器、虛擬機棧、本地方法區】、線程共享區

域【JAVA 堆、方法區】、直接內存。

線程私有數據區域生命週期與線程相同, 依賴用戶線程的啟動/結束 而 創建/銷燬(在 Hotspot

VM 內, 每個線程都與操作系統的本地線程直接映射, 因此這部分內存區域的存/否跟隨本地線程的

生/死對應)。

線程共享區域隨虛擬機的啟動/關閉而創建/銷燬。

直接內存並不是 JVM 運行時數據區的一部分, 但也會被頻繁的使用: 在 JDK 1.4 引入的 NIO 提

供了基於 Channel 與 Buffer 的 IO 方式, 它可以使用 Native 函數庫直接分配堆外內存, 然後使用

DirectByteBuffer 對象作為這塊內存的引用進行操作(詳見: Java I/O 擴展), 這樣就避免了在 Java

堆和 Native 堆中來回複製數據, 因此在一些場景中可以顯著提高性能。


2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

2.2.1. 程序計數器(線程私有)

一塊較小的內存空間, 是當前線程所執行的字節碼的行號指示器,每條線程都要有一個獨立的

程序計數器,這類內存也稱為“線程私有”的內存。

正在執行 java 方法的話,計數器記錄的是虛擬機字節碼指令的地址(當前指令的地址)。如

果還是 Native 方法,則為空。

這個內存區域是唯一一個在虛擬機中沒有規定任何 OutOfMemoryError 情況的區域。

2.2.2. 虛擬機棧(線程私有)

是描述java方法執行的內存模型,每個方法在執行的同時都會創建一個棧幀(Stack Frame)

用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法從調用直至執行完成

的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。

棧幀( Frame)是用來存儲數據和部分過程結果的數據結構,同時也被用來處理動態鏈接

(Dynamic Linking)、 方法返回值和異常分派( Dispatch Exception)。棧幀隨著方法調用而創建,隨著方法結束而銷燬——無論方法是正常完成還是異常完成(拋出了在方法內未被捕獲的異

常)都算作方法結束。


2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

2.2.3. 本地方法區(線程私有)

本地方法區和 Java Stack 作用類似, 區別是虛擬機棧為執行 Java 方法服務, 而本地方法棧則為

Native 方法服務, 如果一個 VM 實現使用 C-linkage 模型來支持 Native 調用, 那麼該棧將會是一個

C 棧,但 HotSpot VM 直接就把本地方法棧和虛擬機棧合二為一。

2.2.4. 堆(Heap-線程共享)-運行時數據區

是被線程共享的一塊內存區域,創建的對象和數組都保存在 Java 堆內存中,也是垃圾收集器進行

垃圾收集的最重要的內存區域。由於現代 VM 採用分代收集算法, 因此 Java 堆從 GC 的角度還可以

細分為: 新生代(Eden 區、From Survivor 區和 To Survivor 區)和老年代。

2.2.5. 方法區/永久代(線程共享)

即我們常說的永久代(Permanent Generation), 用於存儲被 JVM 加載的類信息常量

態變量即時編譯器編譯後的代碼等數據. HotSpot VM把GC分代收集擴展至方法區, 即

使用Java

堆的永久代來實現方法區, 這樣 HotSpot 的垃圾收集器就可以像管理 Java 堆一樣管理這部分內存,

而不必為方法區開發專門的內存管理器(永久帶的內存回收的主要目標是針對常量池的回收類型

的卸載, 因此收益一般很小)。

運行時常量池(Runtime Constant Pool)是方法區的一部分。Class 文件中除了有類的版

本、字段、方法、接口等描述等信息外,還有一項信息是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加

載後存放到方法區的運行時常量池中。 Java 虛擬機對 Class 文件的每一部分(自然也包括常量

池)的格式都有嚴格的規定,每一個字節用於存儲哪種數據都必須符合規範上的要求,這樣才會

被虛擬機認可、裝載和執行。

2.3.JVM 運行時內存

Java 堆從 GC 的角度還可以細分為: 新生代(Eden 區、From Survivor 區和 To Survivor 區)和老年

代。

2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!


2.3.1. 新生代

是用來存放新生的對象。一般佔據堆的 1/3 空間。由於頻繁創建對象,所以新生代會頻繁觸發

MinorGC 進行垃圾回收。新生代又分為 Eden 區、ServivorFrom、ServivorTo 三個區。

2.3.1.1.

Eden 區

Java 新對象的出生地(如果新創建的對象佔用內存很大,則直接分配到老

年代)。當 Eden 區內存不夠的時候就會觸發 MinorGC,對新生代區進行

一次垃圾回收。

2.3.1.2.

ServivorFrom

上一次 GC 的倖存者,作為這一次 GC 的被掃描者。

2.3.1.3.

ServivorTo

保留了一次 MinorGC 過程中的倖存者。

2.3.1.4. MinorGC 的過程(複製->清空->互換)

MinorGC 採用複製算法。

1:eden、servicorFrom 複製到 ServicorTo,年齡+1

首先,把 Eden 和 ServivorFrom 區域中存活的對象複製到 ServicorTo 區域(如果有對象的年

齡以及達到了老年的標準,則賦值到老年代區),同時把這些對象的年齡+1(如果 ServicorTo 不

夠位置了就放到老年區);

2:清空 eden、servicorFrom

然後,清空 Eden 和 ServicorFrom 中的對象;

3:ServicorTo 和 ServicorFrom 互換

最後,ServicorTo 和 ServicorFrom 互換,原 ServicorTo 成為下一次 GC 時的 ServicorFrom

區。

2.3.2. 老年代

主要存放應用程序中生命週期長的內存對象。

老年代的對象比較穩定,所以 MajorGC 不會頻繁執行。在進行 MajorGC 前一般都先進行

了一次 MinorGC,使得有新生代的對象晉身入老年代,導致空間不夠用時才觸發。當無法找到足

夠大的連續空間分配給新創建的較大對象時也會提前觸發一次 MajorGC 進行垃圾回收騰出空間。

MajorGC 採用標記清除算法:首先掃描一次所有老年代,標記出存活的對象,然後回收沒

有標記的對象。MajorGC 的耗時比較長,因為要掃描再回收。MajorGC 會產生內存碎片,為了減

少內存損耗,我們一般需要進行合併或者標記出來方便下次直接分配。當老年代也滿了裝不下的

時候,就會拋出 OOM(Out of Memory)異常。

2.3.3. 永久代

指內存的永久保存區域,主要存放 Class 和 Meta(元數據)的信息,Class 在被加載的時候被

放入永久區域,它和和存放實例的區域不同,GC 不會在主程序運行期對永久區域進行清理。所以這

也導致了永久代的區域會隨著加載的 Class 的增多而脹滿,最終拋出 OOM 異常。

2.3.3.1. JAVA8 與元數據

在 Java8 中,永久代已經被移除,被一個稱為“元數據區”(元空間)的區域所取代。元空間

的本質和永久代類似,元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用

本地內存。因此,默認情況下,元空間的大小僅受本地內存限制。類的元數據放入 native

memory, 字符串池和類的靜態變量放入 java 堆中,這樣可以加載多少類的元數據就不再由

MaxPermSize 控制, 而由系統的實際可用空間來控制。


2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

2.4.1. 如何確定垃圾

2.4.1.1.

引用計數法

在 Java 中,引用和對象是有關聯的。如果要操作對象則必須用引用進行。因此,很顯然一個簡單

的辦法是通過引用計數來判斷一個對象是否可以回收。簡單說,即一個對象如果沒有任何與之關

聯的引用,即他們的引用計數都不為 0,則說明對象不太可能再被用到,那麼這個對象就是可回收

對象。

2.4.1.2.

可達性分析

為了解決引用計數法的循環引用問題,Java 使用了可達性分析的方法。通過一系列的“GC roots”

對象作為起點搜索。如果在“GC roots”和一個對象之間沒有可達路徑,則稱該對象是不可達的。

要注意的是,不可達對象不等價於可回收對象,不可達對象變為可回收對象至少要經過兩次標記

過程。兩次標記後仍然是可回收對象,則將面臨回收。

2.4.2. 標記清除算法(Mark-Sweep)

最基礎的垃圾回收算法,分為兩個階段,標註和清除。標記階段標記出所有需要回收的對象,清

除階段回收被標記的對象所佔用的空間。如圖


2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

從圖中我們就可以發現,該算法最大的問題是內存碎片化嚴重,後續可能發生大對象不能找到可

利用空間的問題。

2.4.3. 複製算法(copying)

為了解決 Mark-Sweep 算法內存碎片化的缺陷而被提出的算法。按內存容量將內存劃分為等大小

的兩塊。每次只使用其中一塊,當這一塊內存滿後將尚存活的對象複製到另一塊上去,把已使用

的內存清掉,如圖:


2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

這種算法雖然實現簡單,內存效率高,不易產生碎片,但是最大的問題是可用內存被壓縮到了原

本的一半。且存活對象增多的話,Copying 算法的效率會大大降低。

2.4.4. 標記整理算法(Mark-Compact)

結合了以上兩個算法,為了避免缺陷而提出。標記階段和 Mark-Sweep 算法相同,標記後不是清

理對象,而是將存活對象移向內存的一端。然後清除端邊界外的對象。如圖:

2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

2.4.5. 分代收集算法

分代收集法是目前大部分 JVM 所採用的方法,其核心思想是根據對象存活的不同生命週期將內存

劃分為不同的域,一般情況下將 GC 堆劃分為老生代(Tenured/Old Generation)和新生代(Young

Generation)。老生代的特點是每次垃圾回收時只有少量對象需要被回收,新生代的特點是每次垃

圾回收時都有大量垃圾需要被回收,因此可以根據不同區域選擇不同的算法。

2.4.5.1. 新生代與複製算法

目前大部分 JVM 的 GC 對於新生代都採取 Copying 算法,因為新生代中每次垃圾回收都要

回收大部分對象,即要複製的操作比較少,但通常並不是按照 1:1 來劃分新生代。一般將新生代

劃分為一塊較大的 Eden 空間和兩個較小的 Survivor 空間(From Space, To Space),每次使用

Eden 空間和其中的一塊 Survivor 空間,當進行回收時,將該兩塊空間中還存活的對象複製到另

一塊 Survivor 空間中。

2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

2.4.5.2. 老年代與標記複製算法

而老年代因為每次只回收少量對象,因而採用 Mark-Compact 算法。

1. JAVA 虛擬機提到過的處於方法區的永生代(Permanet Generation),它用來存儲 class 類,

常量,方法描述等。對永生代的回收主要包括廢棄常量和無用的類。

2. 對象的內存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目

前存放對象的那一塊),少數情況會直接分配到老生代。

3. 當新生代的 Eden Space 和 From Space 空間不足時就會發生一次 GC,進行 GC 後,Eden

Space 和 From Space 區的存活對象會被挪到 To Space,然後將 Eden Space 和 From

Space 進行清理。

4. 如果 To Space 無法足夠存儲某個對象,則將這個對象存儲到老生代。

5. 在進行 GC 後,使用的便是 Eden Space 和 To Space 了,如此反覆循環。

6. 當對象在 Survivor 區躲過一次 GC 後,其年齡就會+1。默認情況下年齡到達 15 的對象會被

移到老生代中。13/04/2018

Page 30 of 283

2.5.JAVA 四中引用類型

2.5.1. 強引用

在 Java 中最常見的就是強引用,把一個對象賦給一個引用變量,這個引用變量就是一個強引

用。當一個對象被強引用變量引用時,它處於可達狀態,它是不可能被垃圾回收機制回收的,即

使該對象以後永遠都不會被用到 JVM 也不會回收。因此強引用是造成 Java 內存洩漏的主要原因之

一。

2.5.2. 軟引用

軟引用需要用 SoftReference 類來實現,對於只有軟引用的對象來說,當系統內存足夠時它

不會被回收,當系統內存空間不足時它會被回收。軟引用通常用在對內存敏感的程序中。

2.5.3. 弱引用

弱引用需要用 WeakReference 類來實現,它比軟引用的生存期更短,對於只有弱引用的對象

來說,只要垃圾回收機制一運行,不管 JVM 的內存空間是否足夠,總會回收該對象佔用的內存。

2.5.4. 虛引用

虛引用需要 PhantomReference 類來實現,它不能單獨使用,必須和引用隊列聯合使用。虛

引用的主要作用是跟蹤對象被垃圾回收的狀態。

2.6.GC 分代收集算法 VS 分區收集算法

2.6.1. 分代收集算法

當前主流 VM 垃圾收集都採用”分代收集”(Generational Collection)算法, 這種算法會根據

對象存活週期的不同將內存劃分為幾塊, 如 JVM 中的 新生代、老年代、永久代,這樣就可以根據

各年代特點分別採用最適當的 GC 算法

2.6.1.1. 在新生代-複製算法

每次垃圾收集都能發現大批對象已死, 只有少量存活. 因此選用複製算法, 只需要付出少量

存活對象的複製成本就可以完成收集.

2.6.1.2. 在老年代-標記整理算法

因為對象存活率高、沒有額外空間對它進行分配擔保, 就必須採用“標記—清理”或“標

記—整理”算法來進行回收, 不必進行內存複製, 且直接騰出空閒內存.2.6.2. 分區收集算法

分區算法則將整個堆空間劃分為連續的不同小區間, 每個小區間獨立使用, 獨立回收. 這樣做的

好處是可以控制一次回收多少個小區間 , 根據目標停頓時間, 每次合理地回收若干個小區間(而不是

整個堆), 從而減少一次 GC 所產生的停頓。

2.7.GC 垃圾收集器

Java 堆內存被劃分為新生代和年老代兩部分,新生代主要使用複製和標記-清除垃圾回收算法;

年老代主要使用標記-整理垃圾回收算法,因此 java 虛擬中針對新生代和年老代分別提供了多種不

同的垃圾收集器,JDK1.6 中 Sun HotSpot 虛擬機的垃圾收集器如下:

2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!


2.7.1. Serial 垃圾收集器(單線程、複製算法)

Serial(英文連續)是最基本垃圾收集器,使用複製算法,曾經是JDK1.3.1 之前新生代唯一的垃圾

收集器。Serial 是一個單線程的收集器,它不但只會使用一個 CPU 或一條線程去完成垃圾收集工

作,並且在進行垃圾收集的同時,必須暫停其他所有的工作線程,直到垃圾收集結束。

Serial 垃圾收集器雖然在收集垃圾過程中需要暫停所有其他的工作線程,但是它簡單高效,對於限

定單個 CPU 環境來說,沒有線程交互的開銷,可以獲得最高的單線程垃圾收集效率,因此 Serial

垃圾收集器依然是 java 虛擬機運行在 Client 模式下默認的新生代垃圾收集器。

2.7.2. ParNew 垃圾收集器(Serial+多線程)

ParNew 垃圾收集器其實是 Serial 收集器的多線程版本,也使用複製算法,除了使用多線程進行垃

圾收集之外,其餘的行為和 Serial 收集器完全一樣,ParNew 垃圾收集器在垃圾收集過程中同樣也

要暫停所有其他的工作線程。

ParNew 收集器默認開啟和 CPU 數目相同的線程數,可以通過-XX:ParallelGCThreads 參數來限

制垃圾收集器的線程數。【Parallel:平行的】

ParNew雖然是除了多線程外和Serial 收集器幾乎完全一樣,但是ParNew垃圾收集器是很多 java

虛擬機運行在 Server 模式下新生代的默認垃圾收集器。

2.7.3. Parallel Scavenge 收集器(多線程複製算法、高效)

Parallel Scavenge 收集器也是一個新生代垃圾收集器,同樣使用複製算法,也是一個多線程的垃

圾收集器,它重點關注的是程序達到一個可控制的吞吐量(Thoughput,CPU 用於運行用戶代碼

的時間/CPU 總消耗時間,即吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)),

高吞吐量可以最高效率地利用 CPU 時間,儘快地完成程序的運算任務,主要適用於在後臺運算而

不需要太多交互的任務。自適應調節策略也是 ParallelScavenge 收集器與 ParNew 收集器的一個

重要區別。

2.7.4. Serial Old 收集器(單線程標記整理算法 )

Serial Old 是 Serial 垃圾收集器年老代版本,它同樣是個單線程的收集器,使用標記-整理算法,

這個收集器也主要是運行在 Client 默認的 java 虛擬機默認的年老代垃圾收集器。

在 Server 模式下,主要有兩個用途:

1. 在 JDK1.5 之前版本中與新生代的 Parallel Scavenge 收集器搭配使用。

2. 作為年老代中使用 CMS 收集器的後備垃圾收集方案。

新生代 Serial 與年老代 Serial Old 搭配垃圾收集過程圖:

2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!


新生代 Parallel Scavenge 收集器與 ParNew 收集器工作原理類似,都是多線程的收集器,都使

用的是複製算法,在垃圾收集過程中都需要暫停所有的工作線程。新生代 Parallel

Scavenge/ParNew 與年老代 Serial Old 搭配垃圾收集過程圖:

2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

2.7.5. Parallel Old 收集器(多線程標記整理算法)

Parallel Old 收集器是Parallel Scavenge的年老代版本,使用多線程的標記-整理算法,在 JDK1.6

才開始提供。

在 JDK1.6 之前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只

能保證新生代的吞吐量優先,無法保證整體的吞吐量,Parallel Old 正是為了在年老代同樣提供吞

吐量優先的垃圾收集器,如果系統對吞吐量要求比較高,可以優先考慮新生代 Parallel Scavenge

和年老代 Parallel Old 收集器的搭配策略。

新生代 Parallel Scavenge 和年老代 Parallel Old 收集器搭配運行過程圖:

2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

2.7.6. CMS 收集器(多線程標記清除算法)

Concurrent mark sweep(CMS)收集器是一種年老代垃圾收集器,其最主要目標是獲取最短垃圾

回收停頓時間,和其他年老代使用標記-整理算法不同,它使用多線程的標記-清除算法。

最短的垃圾收集停頓時間可以為交互比較高的程序提高用戶體驗。

CMS 工作機制相比其他的垃圾收集器來說更復雜,整個過程分為以下 4 個階段:

2.7.6.1. 初始標記

只是標記一下 GC Roots 能直接關聯的對象,速度很快,仍然需要暫停所有的工作線程。

2.7.6.2. 併發標記

進行 GC Roots 跟蹤的過程,和用戶線程一起工作,不需要暫停工作線程。

2.7.6.3. 重新標記

為了修正在併發標記期間,因用戶程序繼續運行而導致標記產生變動的那一部分對象的標記

記錄,仍然需要暫停所有的工作線程。

2.7.6.4. 併發清除

清除 GC Roots 不可達對象,和用戶線程一起工作,不需要暫停工作線程。由於耗時最長的並

發標記和併發清除過程中,垃圾收集線程可以和用戶現在一起併發工作,所以總體上來看

CMS 收集器的內存回收和用戶線程是一起併發地執行。

CMS 收集器工作過程:

2020年最全面試複習資料,大廠資深面試官教你如何準備java面試!

2.7.7. G1 收集器

Garbage first 垃圾收集器是目前垃圾收集器理論發展的最前沿成果,相比與 CMS 收集器,G1 收

集器兩個最突出的改進是:

1. 基於標記-整理算法,不產生內存碎片。

2. 可以非常精確控制停頓時間,在不犧牲吞吐量前提下,實現低停頓垃圾回收。

G1 收集器避免全區域垃圾收集,它把堆內存劃分為大小固定的幾個獨立區域,並且跟蹤這些區域

的垃圾收集進度,同時在後臺維護一個優先級列表,每次根據所允許的收集時間,優先回收垃圾

最多的區域。區域劃分和優先級區域回收機制,確保 G1 收集器可以在有限時間獲得最高的垃圾收

集效率。


因為後面的資料有點長,頭條沒有辦法放太多,今天就先更到這裡,如果有興趣的朋友可以關注一下後續更新,後續將每天一更持續更新,感謝關注哦~如果覺得可以的話,也可以轉發給更多的朋友看到,謝謝喲!


分享到:


相關文章: