能讓應屆生拿到阿里 Offer 的Java面試題

CSDN學院發表於2019-08-27

金九銀十又到了跳槽的高峰期,然而有效的準備面試,無疑是獲得高薪水的關鍵。凡事預則立不預則廢,無論你是近期打算跳槽,還是過完年準備跳槽,我想此刻開始準備面試,無疑是最明智的選擇。資訊過載的今天,想要找一份靠譜的高頻面試題和權威的答案非常不容易,值得慶幸的是這篇文章為你彙總了大量的乾貨面試資料,下面一起來看吧。

640?wx_fmt=png

Java程式是怎麼執行的

我們日常的工作中都使用開發工具(IntelliJ IDEA 或 Eclipse 等)可以很方便的除錯程式,或者是通過打包工具把專案打包成 jar 包或者 war 包,放入 Tomcat 等 Web 容器中就可以正常執行了,但你有沒有想過 Java 程式內部是如何執行的?其實不論是在開發工具中執行還是在 Tomcat 中執行,Java 程式的執行流程基本都是相同的,它的執行流程如下:

  • 先把 Java 程式碼編譯成位元組碼,也就是把 .java 型別的檔案編譯成 .class 型別的檔案。這個過程的大致執行流程:Java 原始碼 -> 詞法分析器 -> 語法分析器 -> 語義分析器 -> 字元碼生成器 -> 最終生成位元組碼,其中任何一個節點執行失敗就會造成編譯失敗;

  • 把 class 檔案放置到 Java 虛擬機器,這個虛擬機器通常指的是 Oracle 官方自帶的 Hotspot JVM;

  • Java 虛擬機器使用類載入器(Class Loader)裝載 class 檔案;

  • 類載入完成之後,會進行位元組碼效驗,位元組碼效驗通過之後 JVM 直譯器會把位元組碼翻譯成機器碼交由作業系統執行。但不是所有程式碼都是解釋執行的,JVM 對此做了優化,比如,以 Hotspot 虛擬機器來說,它本身提供了 JIT(Just In Time)也就是我們通常所說的動態編譯器,它能夠在執行時將熱點程式碼編譯為機器碼,這個時候位元組碼就變成了編譯執行。

Java 程式執行流程圖如下:

640?wx_fmt=png

640?wx_fmt=png

Java虛擬機器是如何判定熱點程式碼的

Java 虛擬機器判定熱點程式碼的方式有兩種:

  • 基於取樣的熱點判定:主要是虛擬機器會週期性的檢查各個執行緒的棧頂,若某個或某些方法經常出現在棧頂,那這個方法就是“熱點方法”。這種判定方式的優點是實現簡單;缺點是很難精確一個方法的熱度,容易受到執行緒阻塞或外界因素的影響。

  • 基於計數器的熱點判定:主要就是虛擬機器給每一個方法甚至程式碼塊建立了一個計數器,統計方法的執行次數,超過一定的閥值則標記為此方法為熱點方法。

Hotspot 虛擬機器使用的基於計數器的熱點探測方法。它使用了兩類計數器:方法呼叫計數器和回邊計數器,當到達一定的閥值是就會觸發 JIT 編譯。方法呼叫計數器:在 client 模式下的閥值是 1500 次,Server 是 10000 次,可以通過虛擬機器引數:-XX:CompileThreshold=N 對其進行設定。但是JVM還存在熱度衰減,時間段內呼叫方法的次數較少,計數器就減小。回邊計數器:主要統計的是方法中迴圈體程式碼執行的次數。

640?wx_fmt=png

以下Integer程式碼輸出的結果是


true,false題目解析:此道題目考察的是,面試者對於基礎型別高頻區快取的掌握,因為 Integer 的高頻區的取值是 -128-127,所以在這個區間的值會複用已有的快取,對比的結果自然是 true,false 。

640?wx_fmt=png

以下StringBuffer傳值修改後的執行結果是什麼


hilaowang題目解析:String 為不可變型別,在方法內對 String 修改的時候,相當修改傳遞過來的是一個 String 副本,所以 String 本身的值是不會被修改的,而 StringBuffer 為可變型別,傳遞過來的引數相當於物件本身,所以列印的結果就為 hilaowang

640?wx_fmt=png

 以下陣列比較的結果分別是什麼


答:true、 false、 false。題目解析:strArr == strArr2 為引用比較,因此結果一定是 false,而陣列本身的比較也就是 strArr.equals(strArr2) 為 false 的原因是因為陣列沒有重寫 equals 方法,因此也是引用比較。陣列 equals 原始碼實現如下:


 Arrays.equals 的結果之所以是 true 是因為 Arrays.equals 重寫了 equals 方法。原始碼實現如下:

  • 
    

640?wx_fmt=png

常用的序列化方式都有哪些

答:常用的序列化方式有以下三種:1) Java 原生序列化方式請參考以下程式碼:

2) JSON 格式,可使用 fastjson 或 GSONJSON 是一種輕量級的資料格式,JSON 序列化的優點是可讀性比較高,方便除錯。我們本篇以 fastjson 的序列化為例,請參考以下程式碼:


3) Hessian 方式序列化Hessian 序列化的優點是可以跨程式語言,比 Java 原生的序列化和反序列化效率高。請參考以下示例程式碼:


640?wx_fmt=png

有哪些方法可以解決雜湊衝突

答:雜湊衝突的常用解決方案有以下 4 種:

  • 開放定址法:當關鍵字的雜湊地址 p=H(key)出現衝突時,以 p 為基礎,產生另一個雜湊地址 p1,如果 p1 仍然衝突,再以 p 為基礎,產生另一個雜湊地址 p2,迴圈此過程直到找出一個不衝突的雜湊地址,將相應元素存入其中;

  • 再雜湊法:這種方法是同時構造多個不同的雜湊函式,當雜湊地址 Hi=RH1(key)發生衝突時,再計算 Hi=RH2(key),迴圈此過程直到找到一個不衝突的雜湊地址,這種方法唯一的缺點就是增加了計算時間;

  • 鏈地址法:這種方法的基本思想是將所有雜湊地址為 i 的元素構成一個稱為同義詞鏈的單連結串列,並將單連結串列的頭指標存在雜湊表的第 i 個單元中,因而查詢、插入和刪除主要在同義詞鏈中進行。鏈地址法適用於經常進行插入和刪除的情況;

  • 建立公共溢位區:將雜湊表分為基本表和溢位表兩部分,凡是和基本表發生衝突的元素,一律填入溢位表。

640?wx_fmt=png

JVM記憶體佈局是怎樣的

答:不同虛擬機器實現可能略微有所不同,但都會遵從 Java 虛擬機器規範,Java 8 虛擬機器規範規定,Java 虛擬機器所管理的記憶體將會包括以下幾個區域:

  • 程式計數器(Program Counter Register)

  • Java 虛擬機器棧(Java Virtual Machine Stacks)

  • 本地方法棧(Native Method Stack)

  • Java 堆(Java Heap)

  • 方法區(Methed Area)

1) 程式計數器

程式計數器(Program Counter Register)是一塊較小的記憶體空間,它可以看作是當前執行緒所執行的位元組碼的行號指示器。在虛擬機器的概念模型裡,位元組碼解析器的工作是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成。

由於 JVM 的多執行緒是通過執行緒輪流切換並分配處理器執行時間的方式來實現的,也就是任何時刻,一個處理器(或者說一個核心)都只會執行一條執行緒中的指令。因此為了執行緒切換後能恢復到正確的執行位置,每個執行緒都有獨立的程式計數器。

如果執行緒正在執行 Java 中的方法,程式計數器記錄的就是正在執行虛擬機器位元組碼指令的地址,如果是 Native 方法,這個計數器就為空(undefined),因此該記憶體區域是唯一一個在 Java 虛擬機器規範中沒有規定 OutOfMemoryError 的區域。

2) Java虛擬機器棧

Java虛擬機器棧(Java Virtual Machine Stacks)描述的是 Java 方法執行的記憶體模型,每個方法在執行的同時都會建立一個線幀(Stack Frame)用於儲存區域性變數表、運算元棧、動態連結、方法出口等資訊,每個方法從呼叫直至執行完成的過程,都對應著一個線幀在虛擬機器棧中入棧到出棧的過程。

  • 如果執行緒請求的棧深度大於虛擬機器所允許的棧深度就會丟擲 StackOverflowError 異常。

  • 如果虛擬機器是可以動態擴充套件的,如果擴充套件時無法申請到足夠的記憶體就會丟擲 OutOfMemoryError 異常。

3) 本地方法棧

本地方法棧(Native Method Stack)與虛擬機器棧的作用是一樣的,只不過虛擬機器棧是服務 Java 方法的,而本地方法棧是為虛擬機器呼叫 Native 方法服務的。在 Java 虛擬機器規範中對於本地方法棧沒有特殊的要求,虛擬機器可以自由的實現它,因此在 Sun HotSpot 虛擬機器直接把本地方法棧和虛擬機器棧合二為一了。

4) Java 堆

Java堆(Java Heap)是 JVM 中記憶體最大的一塊,是被所有執行緒共享的,在虛擬機器啟動時候建立,Java堆唯一的目的就是存放物件例項,幾乎所有的物件例項都在這裡分配記憶體,隨著JIT編譯器的發展和逃逸分析技術的逐漸成熟,棧上分配、標量替換優化的技術將會導致一些微妙的變化,所有的物件都分配在堆上漸漸變得不那麼絕對了。

如果在堆中沒有記憶體完成例項分配,並且堆不可以再擴充套件時,將會丟擲 OutOfMemoryError。Java 虛擬機器規範規定,Java 堆可以處在物理上不連續的記憶體空間中,只要邏輯上連續即可,就像我們的磁碟空間一樣。在實現上也可以是固定大小的,也可以是可擴充套件的,不過當前主流的虛擬機器都是可擴充套件的,通過 -Xmx 和 -Xms 控制。

5) 方法區

方法區(Methed Area)用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯後的程式碼等資料。很多人把方法區稱作“永久代”(Permanent Generation),本質上兩者並不等價,只是 HotSpot 虛擬機器垃圾回收器團隊把 GC 分代收集擴充套件到了方法區,或者說是用來永久代來實現方法區而已,這樣能省去專門為方法區編寫記憶體管理的程式碼,但是在 JDK 8 也移除了“永久代”,使用 Native Memory 來實現方法區。當方法無法滿足記憶體分配需求時會丟擲 OutOfMemoryError 異常。

640?wx_fmt=png

synchronized是如何實現鎖升級的

答:在鎖物件的物件頭裡面有一個 threadid 欄位,在第一次訪問的時候 threadid 為空,JVM(Java 虛擬機器)讓其持有偏向鎖,並將 threadid 設定為其執行緒 id,再次進入的時候會先判斷 threadid 是否尤其執行緒 id 一致。

如果一致則可以直接使用,如果不一致,則升級偏向鎖為輕量級鎖,通過自旋迴圈一定次數來獲取鎖,不會阻塞,執行一定次數之後就會升級為重量級鎖,進入阻塞,整個過程就是鎖升級的過程。

 

640?wx_fmt=png

RabbitMQ有哪種重要的元件

它們有什麼作用

答:RabbitMQ 包含的重要元件有:ConnectionFactory(連線管理器)、Channel(通道)、Exchange(交換器)、Queue(佇列)、RoutingKey(路由鍵)、BindingKey(繫結鍵) 等重要的元件,它們的作用如下:

  • ConnectionFactory(連線管理器):應用程式與 RabbitMQ 之間建立連線的管理器,程式程式碼中使用;

  • Channel(通道):訊息推送使用的通道;

  • Exchange(交換器):用於接受、分配訊息;

  • Queue(佇列):用於儲存生產者的訊息;

  • RoutingKey(路由鍵):用於把生成者的資料分配到交換器上;

  • BindingKey(繫結鍵):用於把交換器的訊息繫結到佇列上。

執行流程,如下圖所示:640?wx_fmt=gif

相關文章