本文已受權玉剛說公衆號web
系列文章:面試
在學習 JVM 相關知識,怎麼讓本身有動力看下去,且有思考性呢?筆者認爲,開頭用一些經常使用的面試題,來引入讀者的興趣比較好,這樣纔會有看下去的東西,因此,該篇文章會以面試+總結的方式,但願讀者能先思考寫出答案,再查看相關知識。spa
這是一些常見的面試,不少人都看到網上的標準答案,但你知道爲何嗎?
首先看第一個,Java的內存區域,能夠看一張編譯圖:
能夠看到Java 的內存區域就是框框裏的東西,每一步的大概意思以下,具體細節參考 深刻Java虛擬機之 --- JVM的愛恨情仇: 總結,建議讀者學習以後,能本身默寫這些方法並指導每一步的意思;Java 對象的建立共分爲5步,以下圖:
而後明白每一個步驟作了哪些便可,以下:有兩種方式:句柄和直接指針; 建立對象是爲了使用對象,虛擬機須要經過棧中的 reference 來獲取堆上的對象。
優缺點: 使用句柄好處是,當對象發生改變,只須要移動句柄的實例數據指針便可,而直接指針就是速度快。參考答案是: String 是用 final 修飾的類,因爲它的不可變性,相似拼接、裁剪字符串等,都會產生新的對象。 StringBuffer 解決上面拼接對象而提供一個類,能夠經過 append等方法拼接,是線程安全的,因爲線程安全,效率也降低 StringBuilder 跟StringBuffer 差很少,只是去掉了線程安全,因此優先使用 StringBuilder
說說String 爲何會產生新的對象?好比 String a = "1" String b = a + "2",當執行這條指令時,會在常量池中產生一個對象指向a,而建立b時也會從新在常量池中生成b的對象;屢次建立容易觸發 GC,這也是爲何不建議使用 String 類去拼接的問題。
深刻Java虛擬機之 --- JVM的愛恨情仇 JAVA 垃圾回收機制(二) --- GC回收具體實現
首先,在講解這幾個引用以前,先明白虛擬機爲何會由這些引用的說明;咱們都知道,對象須要回收,那怎麼去判斷哪些對象須要回收呢?這就須要一些判斷來肯定哪些對象是須要回收的,通常有如下幾種方法:
不管是 引用計算算法仍是可達性分析算法,都是涉及到對象的引用問題,因此,在 JDK1.2 以後,又分爲如下幾類引用: 經過上面的介紹,知道了" 引用"是什麼關係,這對理解各類引用仍是頗有必要的,那麼使用 軟引用的好處也在那裏了; 建議一些內存消耗較大的使用軟引用,好比 webview。。final 和finally 比較好理解。首先 final 用來修飾的對象不可變;finally 則是保證重點代碼必定要被執行的一種機制,通常用於 try - catch-finally 語句中。 但finalize 是什麼東西呢?在解釋標準代碼以前,又得回到GC算法中了。 首先,finalize 是 Object 的一個方法,用來特定資源的回收。 上面說到,當 GC Roots 不可達時,認爲對象已經再也不使用了,可是對象並不是是非"死"不可,當 GC Roots 不可達時,系統首先會先判斷 對象的 finalize 是否執行,不執行則直接回收;若是能夠執行,則放在隊列中,由finalize線程去執行它,若是有其餘對象關聯時,則判斷對象不可回收,不然對象回收,finalize 執行一次,以下圖:
因爲它的不肯定性,在 JDK9時,已經標註爲deprecated,但不影響咱們對它的理解。雖然說 Java 堆 能夠回收70%~95%的空間,但方法區一樣能夠回收一些資源,方法區主要回收兩個部分廢棄常量和無用的類。
因此,當發生 GC 時,很是常量和無用類是能夠被回收,固然這裏也是說"能夠",是否像對象同樣被回收,還須要對虛擬機的參數配置,這裏就不細說了。對象的回收,基於上面講到的,GC Roots不可達,且判斷能夠回收。衍生的算法以下圖(建議能默認每種算法的理解):
其中,基礎是 標記-清除是基礎,接下來都是在它的基礎上改進,分代算法是主流 Java 虛擬機的主要算法; 其中各個算法特色以下,詳細介紹看 JAVA 垃圾回收機制(一) --- 對象回收與算法初識 第四節,垃圾回收篇。 關係新生代和老年代的問題,參考: JAVA 垃圾回收機制(二) --- GC回收具體實現深刻Java虛擬機之 -- 類文件結構(字節碼) 深刻Java虛擬機之 -- 類加載機制
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);
}
複製代碼
類加載的過程以下圖所示(建議能默認每一個步驟的理解):
也能夠成爲 加載-鏈接-初始化 這種叫法。 其中, 加載、驗證、準備、初始化和卸載的順序是固定的,而解析則不必定,由於Java是動態語言,它能夠在運行時解析,即初始化以後。該階段解析以下: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
Child
123
複製代碼
改成System.out.println(Child.value)時:
Parent
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);
}
複製代碼
輸出什麼?
從上面咱們知道,類在加載的時候,就是經過一個全限定名去加載這個類的二進制字節流,這個是系統自動完成的。這個動做若是從外部去作,以便於咱們去獲取所需的類,則咱們成爲類加載器。好比經過一個路徑獲取到一個 class 字節碼,而後經過反射,拿到相應的信息。
它的工程流程是: 當一個類加載器收到類加載的請求,它首先不會本身去嘗試加載這個類,而是委派給她的父類加載器去完成,每個層次的類加載器都是如此,所以全部的加載器都會傳遞到父加載器中;只有父加載器沒法完成時,子加載器纔會嘗試本身去加載,它的模型以下: