深入Java虛擬機器之 -- 總結面試篇

夏至的稻穗發表於2019-05-06

系列文章:

深入Java虛擬機器之 -- 總結面試篇

深入Java虛擬機器之 --- JVM的愛恨情仇

JAVA 垃圾回收機制(一) --- 物件回收與演算法初識

JAVA 垃圾回收機制(二) --- GC回收具體實現

深入Java虛擬機器之 -- 類檔案結構(位元組碼)

深入Java虛擬機器之 -- 類載入機制

在學習 JVM 相關知識,怎麼讓自己有動力看下去,且有思考性呢?筆者認為,開頭用一些常用的面試題,來引入讀者的興趣比較好,這樣才會有看下去的東西,所以,該篇文章會以面試+總結的方式,希望讀者能先思考寫出答案,再檢視相關知識。

一、JVM常見面試題

深入Java虛擬機器之 -- 總結面試篇

  • 介紹下 Java 記憶體區域
  • Java 物件的建立過程
  • 物件的訪問定位有幾種
  • String、StringBuilder、StringBuffer 有什麼不同?

這是一些常見的面試,很多人都看到網上的標準答案,但你知道為什麼嗎?

1.1 介紹下 Java 記憶體區域

首先看第一個,Java的記憶體區域,可以看一張編譯圖:

在這裡插入圖片描述
可以看到Java 的記憶體區域就是框框裡的東西,每一步的大概意思如下,具體細節參考深入Java虛擬機器之 --- JVM的愛恨情仇
在這裡插入圖片描述
總結,建議讀者學習之後,能自己默寫這些方法並指導每一步的意思;

1.2 Java 物件的建立過程

Java 物件的建立共分為5步,如下圖:

在這裡插入圖片描述
然後明白每個步驟做了哪些即可,如下:
在這裡插入圖片描述

1.3、物件的訪問定位有幾種

有兩種方式:控制程式碼和直接指標; 建立物件是為了使用物件,虛擬機器需要通過棧中的 reference 來獲取堆上的物件。

在這裡插入圖片描述
優缺點: 使用控制程式碼好處是,當物件發生改變,只需要移動控制程式碼的例項資料指標即可,而直接指標就是速度快。

1.4 String、StringBuilder、StringBuffer 有什麼不同

參考答案是: String 是用 final 修飾的類,由於它的不可變性,類似拼接、裁剪字串等,都會產生新的物件。 StringBuffer 解決上面拼接物件而提供一個類,可以通過 append等方法拼接,是執行緒安全的,由於執行緒安全,效率也下降 StringBuilder 跟StringBuffer 差不多,只是去掉了執行緒安全,所以優先使用 StringBuilder

說說String 為什麼會產生新的物件?比如 String a = "1" String b = a + "2",當執行這條指令時,會在常量池中產生一個物件指向a,而建立b時也會重新在常量池中生成b的物件;多次建立容易觸發 GC,這也是為什麼不建議使用 String 類去拼接的問題。

二、Java 回收機制常見面試題

深入Java虛擬機器之 --- JVM的愛恨情仇 JAVA 垃圾回收機制(二) --- GC回收具體實現

  • 簡單的介紹一下強引用、軟引用、弱引用、虛引用(虛引用與軟引用和弱引用的區別、使用軟引用能帶來的好處)
  • 談談final、finally、finalize 有什麼不同
  • 方法區會回收資源嗎?
  • 垃圾回收有哪些演算法,各自的特點?

2.1 簡單的介紹一下強引用、軟引用、弱引用、虛引用

首先,在講解這幾個引用之前,先明白虛擬機器為什麼會由這些引用的說明;我們都知道,物件需要回收,那怎麼去判斷哪些物件需要回收呢?這就需要一些判斷來確定哪些物件是需要回收的,一般有以下幾種方法:

在這裡插入圖片描述
無論是 引用計算演算法還是可達性分析演算法,都是涉及到物件的引用問題,所以,在 JDK1.2 之後,又分為以下幾類引用:
在這裡插入圖片描述
通過上面的介紹,知道了"引用"是什麼關係,這對理解各種引用還是很有必要的,那麼使用 軟引用的好處也在那裡了; 建議一些記憶體消耗較大的使用軟引用,比如 webview。。

2.2 談談final、finally、finalize 有什麼不同

final 和finally 比較好理解。首先 final 用來修飾的物件不可變;finally 則是保證重點程式碼一定要被執行的一種機制,一般用於 try - catch-finally 語句中。 但finalize 是什麼東西呢?在解釋標準程式碼之前,又得回到GC演算法中了。 首先,finalize 是 Object 的一個方法,用來特定資源的回收。 上面說到,當 GC Roots 不可達時,認為物件已經不再使用了,但是物件並非是非"死"不可,當 GC Roots 不可達時,系統首先會先判斷 物件的 finalize 是否執行,不執行則直接回收;如果可以執行,則放在佇列中,由finalize執行緒去執行它,如果有其他物件關聯時,則判斷物件不可回收,否則物件回收,finalize 執行一次,如下圖:

在這裡插入圖片描述
由於它的不確定性,在 JDK9時,已經標註為deprecated,但不影響我們對它的理解。

2.3 方法區會回收資源嗎?

雖說 Java 堆 可以回收70%~95%的空間,但方法區同樣可以回收一些資源,方法區主要回收兩個部分廢棄常量無用的類

在這裡插入圖片描述
所以,當發生 GC 時,非常常量和無用類是可以被回收,當然這裡也是說"可以",是否像物件一樣被回收,還需要對虛擬機器的引數配置,這裡就不細說了。

2.4 垃圾回收有哪些演算法,各自的特點?

物件的回收,基於上面講到的,GC Roots不可達,且判斷可以回收。衍生的演算法如下圖(建議能預設每種演算法的理解):

在這裡插入圖片描述
其中,基礎是 標記-清除是基礎,接下來都是在它的基礎上改進,分代演算法是主流 Java 虛擬機器的主要演算法; 其中各個演算法特點如下,詳細介紹看 JAVA 垃圾回收機制(一) --- 物件回收與演算法初識 第四節,垃圾回收篇。
在這裡插入圖片描述
關係新生代和老年代的問題,參考:JAVA 垃圾回收機制(二) --- GC回收具體實現

三、類載入的問題

深入Java虛擬機器之 -- 類檔案結構(位元組碼) 深入Java虛擬機器之 -- 類載入機制

  • 類載入過程
  • 寫出下列程式碼列印資訊,若將改成System.out.println(Child.c_value);改為System.out.println(Child.value); 如何?
public class Parent{
   static {
   	System.out.println("Parent");
   }
   public static int value = 123;
}

public class Child extends Parent{
   static {
   	System.out.println("Child");
   }
   public static int c_value = 123;
}

//mian 中執行
public static void main(String[] args) {
   System.out.println(Child.c_value);
}
複製程式碼
  • 說說你對類載入器的理解
  • 什麼是雙親委派模型

3.1 類載入的過程

類載入的過程如下圖所示(建議能預設每個步驟的理解):

在這裡插入圖片描述
也可以成為 載入-連線-初始化 這種叫法。 其中,載入、驗證、準備、初始化和解除安裝的順序是固定的,而解析則不一定,因為Java是動態語言,它可以在執行時解析,即初始化之後。該階段解析如下:
在這裡插入圖片描述

3.2 寫出下列程式碼列印資訊,若將改成System.out.println(Child.c_value);改為System.out.println(Child.value); 如何?

public class Parent{
   static {
   	System.out.println("Parent");
   }
   public static int value = 123;
}

public class Child extends Parent{
   static {
   	System.out.println("Child");
   }
   public static int c_value = 123;
}

//mian 中執行
public static void main(String[] args) {
   System.out.println(Child.c_value);
}
複製程式碼

列印資訊如:

Parent
123
複製程式碼

改為System.out.println(Child.value)時:

Parent
Child
123
複製程式碼

具體看:深入Java虛擬機器之 -- 類載入機制 擴充套件

class Parent{
		public static int value = 1;
		static {
			value = 2;
		}
	}
	
class Child extends Parent{

	public static int B = value ;
}


public static void main(String[] args) {
	System.out.println(Child.B);
}
複製程式碼

輸出什麼?

3.3 說說你對類載入器的理解

從上面我們知道,類在載入的時候,就是通過一個全限定名去載入這個類的二進位制位元組流,這個是系統自動完成的。這個動作如果從外部去做,以便於我們去獲取所需的類,則我們成為類載入器。比如通過一個路徑獲取到一個 class 位元組碼,然後通過反射,拿到相應的資訊。

3.4 什麼是雙親委派模型

它的工程流程是: 當一個類載入器收到類載入的請求,它首先不會自己去嘗試載入這個類,而是委派給她的父類載入器去完成,每一個層次的類載入器都是如此,因此所有的載入器都會傳遞到父載入器中;只有父載入器無法完成時,子載入器才會嘗試自己去載入,它的模型如下:

類載入雙親委派模型

相關文章