Java 後臺開發面試題分享六

2020-10-28 18:00:01

堆(Heap)和棧(Stack)的區別

(1)管理方式不同。棧由作業系統自動分配釋放,無需手動控制;堆的申請和釋放工作由程式設計師控制,容易產生記憶體漏失。

(2)空間大小不同。每個程序擁有的棧的大小要遠遠小於堆的大小。理論上,程式設計師可申請的堆大小為虛擬記憶體的大小,程序棧的大小 64bits 的 Windows 預設 1MB,64bits 的 Linux 預設 10MB。

(3)生長方向不同。堆的生長方向向上,記憶體地址由低到高;棧的生長方向向下,記憶體地址由高到低。

(4)分配方式不同。堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是由作業系統完成的,比如區域性變數的分配。動態分配由 alloc 函數進行分配,但是棧的動態分配和堆是不同的,他的動態分配是由作業系統進行釋放,無需我們手工實現。

(5)分配效率不同。棧由作業系統自動分配,會在硬體層級對棧提供支援:分配專門的暫存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是由 C/C++ 提供的庫函數或運運算元來完成申請與管理,實現機制較為複雜,頻繁的記憶體申請容易產生記憶體碎片。顯然,堆的效率比棧要低得多。

(6)存放內容不同。棧存放的內容,函數返回地址、相關引數、區域性變數和暫存器內容等。當主函數呼叫另外一個函數的時候,要對當前函數執行斷點進行儲存,需要使用棧來實現,首先入棧的是主函數下一條語句的地址,即擴充套件指標暫存器的內容(EIP),然後是當前棧幀的底部地址,即擴充套件基址指標暫存器內容(EBP),再然後是被調函數的實參等,一般情況下是按照從右向左的順序入棧,之後是被調函數的區域性變數,注意靜態變數是存放在資料段或者 BSS 段,是不入棧的。出棧的順序正好相反,最終棧頂指向主函數下一條語句的地址,主程式又從該地址開始執行。堆,一般情況堆頂使用一個位元組的空間來存放堆的大小,而堆中具體存放內容是由程式設計師來填充的。


死鎖是什麼

死鎖:多執行緒相互巢狀就會造成死鎖。

比如執行緒在獲得一個鎖 L1 的情況下再去申請另外一個鎖 L2,也就是鎖 L1 想要包含了鎖 L2,也就是說在獲得了鎖 L1,並且沒有釋放鎖 L1 的情況下,又去申請獲得鎖 L2,就會產生死鎖。

在編寫程式時應該儘量避免出現死鎖。下面有幾種常見的方式用來解決死鎖問題:

  1. 避免多次鎖定。儘量避免同一個執行緒對多個 Lock 進行鎖定。例如上面的死鎖程式,主執行緒要對 A、B 兩個物件的 Lock 進行鎖定,副執行緒也要對 A、B 兩個物件的 Lock 進行鎖定,這就埋下了導致死鎖的隱患。
  2. 具有相同的加鎖順序。如果多個執行緒需要對多個 Lock 進行鎖定,則應該保證它們以相同的順序請求加鎖。比如上面的死鎖程式,主執行緒先對 A 物件的 Lock 加鎖,再對 B 物件的 Lock 加鎖;而副執行緒則先對 B 物件的 Lock 加鎖,再對 A 物件的 Lock 加鎖。這種加鎖順序很容易形成巢狀鎖定,進而導致死鎖。如果讓主執行緒、副執行緒按照相同的順序加鎖,就可以避免這個問題。
  3. 使用定時鎖。程式在呼叫 acquire() 方法加鎖時可指定 timeout 引數,該引數指定超過 timeout 秒後會自動釋放對 Lock 的鎖定,這樣就可以解開死鎖了。
  4. 死鎖檢測。死鎖檢測是一種依靠演演算法機制來實現的死鎖預防機制,它主要是針對那些不可能實現按序加鎖,也不能使用定時鎖的場景的。

自旋鎖

自旋鎖(spinlock):是指當一個執行緒在獲取鎖的時候,如果鎖已經被其它執行緒獲取,那麼該執行緒將回圈等待,然後不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖才會退出迴圈。


列出常見的幾種 RunException

NullPointerException - 空指標參照異常

ClassCastException - 型別強制轉換異常

IllegalArgumentException - 傳遞非法引數異常

ArithmeticException - 算術運算異常

ArrayStoreException - 向陣列中存放與宣告型別不相容物件異常

IndexOutOfBoundsException - 下標越界異常

NegativeArraySizeException - 建立一個大小為負數的陣列錯誤異常

NumberFormatException - 數位格式異常

SecurityException - 安全異常

UnsupportedOperationException - 不支援的操作異常

NegativeArrayException - 陣列負下標異常

EOFException - 檔案已結束異常

FileNotFoundException - 檔案未找到異常

SQLException - 運算元據庫異常

IOException - 輸入輸出異常

NoSuchMethodException - 方法未找到異常

java.lang.AbstractMethodError - 抽象方法錯誤。當應用試圖呼叫抽象方法時丟擲。

java.lang.AssertionError - 斷言錯。用來指示一個斷言失敗的錯誤。

java.lang.ClassCircularityError - 類迴圈依賴錯誤。在初始化一個類時,若檢測到類之間迴圈依賴則丟擲該異常。

java.lang.ClassFormatError - 類格式錯誤。當 Java 虛擬機器器試圖從一個檔案中讀取 Java 類,而檢測到該檔案的內容不符合類的有效格式時輸出。

java.lang.Error - 錯誤。是所有錯誤的基礎類別,用於標識嚴重的程式執行問題。這些問題通常描述一些不應被應用程式捕獲的反常情況。


Java 的 transient 修飾的作用?

作用:在物件序列化的時候,有些變數不需要序列化,比如密碼等,可以使用 transient 關鍵字來解決這個問題;transient 修飾的變數不會被序列化。


什麼是 java 序列化,如何實現 java 序列化?

序列化是一種用來處理物件流的機制,所謂物件流也就是將物件的內容進行流化。可以對流化後的物件進行讀寫操作,也可將流化後的物件傳輸於網路之間。序列化是為了解決在對物件流進行讀寫操作時所引發的問題。

序列化的實現:將需要被序列化的類實現 Serializable 介面,該介面沒有需要實現的方法,implements Serializable 只是為了標註該物件是可被序列化的,然後使用一個輸出流(如:FileOutputStream)來構造一個 ObjectOutputStream(物件流)物件,接著,使用 ObjectOutputStream 物件的 writeObject(Object obj) 方法就可以將引數為 obj 的物件寫出(即儲存其狀態),要恢復的話則用輸入流。


Java 的 final,finally,finalize() 的區別?

final - 修飾符

類:此類不能被繼承。
1. 為了類的安全,不允許子類隨意更改;
2. 類中有很多方法,方法之間有複雜的呼叫關係;
3. 類是最終版本的類,不需要擴充套件了。

方法:此方法不能被子類重寫,所有的子類呼叫的是同一個版本的方法。

變數: 常數,值不能更改。
常數規範 - 每個單詞字母都大寫,多個單詞用下劃線連線,如 MAX_VALUE
值不能改:
1. 基本型別:值不能改。
2. 參照型別:物件不能改,但物件的屬性值可以改。
3. 陣列:陣列的元素可以改。

finally

作為例外處理的一部分,它只能用在 try/catch 語句中,並且附帶一個語句塊,表示這段語句最終一定會被執行(不管有沒有丟擲異常),經常被用在需要釋放資源的情況下。System.exit(0) 語句,終止了 Java 虛擬機器器的執行,會讓 finally 的語句無法執行。

finalize()

是在 java.lang.Object 裡定義的,也就是說每一個物件都有這麼個方法。這個方法在 gc 啟動,該物件被回收的時候被呼叫。其實 gc 可以回收大部分的物件(凡是 new 出來的物件,gc 都能搞定,一般情況下又不會用 new 以外的方式去建立物件),所以一般是不需要程式設計師去實現 finalize 的。 

特殊情況下,需要程式設計師實現 finalize,當物件被回收的時候釋放一些資源,比如:一個 socket 連結,在物件初始化時建立,整個生命週期內有效,那麼就需要實現 finalize,關閉這個連結。 

finalize() 的呼叫具有不確定行,只保證方法會呼叫,但不保證方法裡的任務會被執行完。一個物件的 finalize() 方法只會被呼叫一次,而且 finalize() 被呼叫不意味著 gc 會立即回收該物件,所以有可能呼叫 finalize() 後,該物件又不需要被回收了,然後到了真正要被回收的時候,因為前面呼叫過一次,所以不會呼叫 finalize(),產生問題。 所以,推薦不要使用 finalize() 方法。

Java 的 abstract class 和 interface 有什麼區別?

相同點:

- 都不能被範例化。
- 介面的實現類或抽象類的子類都只有實現了介面或抽象類中的方法後才能範例化。

不同點:

- 抽象類中可以有構造方法, 而介面不可以有構造方法
- 介面只有定義,java 8 之前不能有方法的實現,java 1.8 中可以定義預設方法和靜態方法,Java 9 新增支援私有方法;而抽象類可以有定義與實現,方法可在抽象類中實現。
- 實現介面的關鍵字為 implements,繼承抽象類的關鍵字為 extends。一個類可以實現多個介面,但一個類只能繼承一個抽象類。所以,使用介面可以間接地實現多重繼承。
- 介面強調特定功能的實現,而抽象類強調所屬關係。
- 介面成員變數預設為 public static final,必須賦初值,不能被修改;其所有的成員方法都是 public、abstract 的。抽象類中成員變數預設 default,可在子類中被重新定義,也可被重新賦值;抽象方法被 abstract 修飾,不能被 private、static、synchronized 和 native 等修飾,必須以分號結尾,不帶花括號。
- 介面強調減少程式碼間的耦合度,不同實現類只是共用某些同樣的方法宣告,配合多型,支援動態方法解析;抽象類強調繼承關係,為多個子類定義同樣的行為,傾向於充當公共類的角色,強調程式碼的可重用性。
- 實現複雜功能時用抽象類的繼承,實現簡單功能時用介面,開發中一般採用面向介面程式設計
- 介面隱藏了更多的細節,只展現了重要的內容。

JDK、JRE、JVM 分別是什麼?包含關係是怎樣的?

JDK - Java Development Kit,Java 開發工具包。是 Java 開發的核心,包括了 Java 執行環境 jre,很多的 Java 工具,以及一些 Java 基礎類庫。

JRE - Java Runtime Environment,Java 執行環境。是執行基於 Java 語言編寫的程式所不可缺少的執行環境。

JVM - Java Virtual Machine,Java 虛擬機器器。是 Java 實現跨平臺的最核心部分。所有的 java 程式會首先被編譯為 .class 的類檔案,這種類檔案可以在虛擬機器器上執行。由虛擬機器器將程式解釋給本地系統執行。

包含關係:JDK 包含 JRE,JRE 包含 JVM。


Java 的 Collection 跟 Collections 區別?

Collection 是集合類的上級介面,子介面主要有 Set 和 List。
Collections 是針對集合類的一個幫助類,提供一系列靜態方法實現對各種集合的搜尋、排序、執行緒安全化等操作。

想了解更多,歡迎關注我的微信公眾號:Renda_Zhang