《二十不惑》,多看看JVM面試全解

2020-08-09 13:22:42

最近要準備秋招了,會更新一些面試相關的知識

JRE和JDK的區別

JRE是Java Runtime Environment的縮寫,顧名思義是java執行時環境,包含了java虛擬機器,java基礎類庫。是使用java語言編寫的程式執行所需要的軟體環境,是提供給想執行java程式的使用者使用的,還有所有的Java類庫的class檔案,都在lib目錄下,並且都打包成了jar。

Jdk是Java Development Kit的縮寫,顧名思義是java開發工具包,是程式設計師使用java語言編寫java程式所需的開發工具包,是提供給程式設計師使用的。JDK包含了JRE,同時還包含了編譯java原始碼的編譯器javac,還包含了很多java程式偵錯和分析的工具:jconsole,jvisualvm等工具軟體,還包含了java程式編寫所需的文件和demo例子程式。

如果你需要執行java程式,只需安裝JRE就可以了。如果你需要編寫java程式,需要安裝JDK。

引言

我們知道java能夠實現跨平臺執行,可以同一份程式碼在Windows和Linux平臺下執行,而且知道實現跨平臺是因爲java有JVM.

那麼爲什麼有了JVM就能實現java的跨平臺執行呢?

JVM實現java跨平臺執行原理
image-20200809094714383

Java虛擬機器從軟體側面遮蔽了底層硬體指令層面的細節,也就是說,我們在操作系統的底層需要的是機器碼,而java程式編譯後生成的同一份位元組碼通過JVM虛擬機器(當然需要不同的虛擬機器),達到了將位元組碼解析爲不同環境所需的機器碼,從而實現了在編寫程式時,我們不需要考慮在不同環境(Windows和Linux)下能用到的不同的類庫,在java中,只需要編寫程式,底層又JVM做底層的硬體指令的細節.


JVM

1.JVM是什麼?

JVM它是Java Virtual Machine 的縮寫,主要是通過在實際計算機模仿各種計算機功能來實現的,組成部分包括堆、方法區、棧、本地方法棧、程式計算器等部分組成的,其中方法回收堆和方法區是共用區,也就是誰都可以使用,而棧和程式計算器、本地方法棧區是歸JVM的。Java能夠被稱爲「一次編譯,到處執行」的原因就是Java遮蔽了很多的操作系統平臺相關資訊,使得Java只需要生成在JVM虛擬機器執行的目的碼也就是所說的位元組碼,就可以在多種平臺執行。

image-20200809111434086

2.JVM的實現原理

  • 類裝載器(ClassLoader)主要負責載入class檔案,是否能執行主要取決於execution engine它是負責執行被載入類中包含的指令。有兩種類載入器分別爲啓動類載入器和使用者自定義類載入器,然而啓動類載入器是JVM實現的一部分,使用者自定義類載入器是Java程式一部分。
  • 本地方法棧(native method stack)主要作用是登記native方法,然後在execution engine執行的時候載入本地方法庫。
  • 程式計數器,是指方法區中的方法位元組碼由引擎讀取下一條指令,它是一個非常小的記憶體空間。爲什麼有這種東西呢,大家都知道每個執行緒都是有一個程式計數器的,是執行緒私有的,相當一個指針。
  • 方法區它是指執行緒共用的,誰都可以共用使用,我們通常用來儲存裝載類的元結構資訊。
  • (heap)它是Java虛擬機器用來儲存物件範例的,比我們在開發過程使用的new物件,只要通過new建立的物件的記憶體的物件都在堆分配,注意一點的是堆中的物件記憶體需要等待垃圾器(GC)進行回收,也是Java虛擬機器共用區。
  • 本地介面(native interface)作用是融合不同的程式語言爲Java所用,注意底層是C、C++寫的,學習JVM時瞭解C語言一些更好,最起碼能看懂,這個方法的行爲就是native method stack中登記native方法,然後在execution engine執行時載入native libraries的。

3.JVM調優

image-20200809111824693

我們的程式是基於JVM上面的,而多個程式組成的軟體最終是執行在硬體上的(比如最終執行在我們的計算機電腦上),而計算機的記憶體是有限的,如果不斷的執行程式,而不管產生的垃圾記憶體,這樣JVM所佔用的記憶體就會越來越大,從而導致最終佔用很大的記憶體,導致計算機的其他功能無法正常執行,甚至專案崩潰.

這時,就需要定期的將程式執行所產生的垃圾記憶體進行回收

也就是給JVM設定一個佔用最大記憶體的臨界點:保證其他程式的正常執行,而且我們的程式也要能正常執行

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

代。

image-20200809113818427

新生代

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

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

  • Eden區:Java 新物件的出生地(如果新建立的物件佔用記憶體很大,則直接分配到老

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

    一次垃圾回收。

  • ServivorFrom:上一次GC的倖存者,作爲這一次GC的被掃描的物件

  • ServivorTo:保留了一次 MinorGC 過程中的倖存者

MinorGC的過程

採用的是複製演算法

1edenservicorFrom 複製到 ServicorTo,年齡+1

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

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

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

2:清空 edenservicorFrom

然後,清空 Eden 和 ServicorFrom 中的物件;

3ServicorTo ServicorFrom 互換

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

區。

老年代

主要存放應用程式中生命週期長的記憶體物件。

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

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

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

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

有標記的物件。MajorGC 的耗時比較長,因爲要掃描再回收。MajorGC 會產生記憶體碎片,爲了減

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

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

總結:

1. 確認哪些記憶體需要回收?

GC能明確記憶體的佔用,當記憶體不夠時,GC就會啓動並掃描哪些記憶體需要被回收

2. 什麼時候回收?

JVM的垃圾回收演算法,不是我們自主呼叫就立即執行的,而是等待程式認爲合適的時間然後執行

3. 怎麼回收?

GC採用垃圾回收演算法:標記清除演算法,複製演算法,標記整理演算法,分代收集

4. JVM怎麼確認哪些物件是垃圾?(哪些物件已經"死亡"?)

參照計演算法和根搜尋演算法

  • 參照計演算法:在 Java 中,參照和物件是有關聯的。如果要操作物件則必須用參照進行。因此,很顯然一個簡單的辦法是通過參照計數來判斷一個物件是否可以回收。簡單說,即一個物件如果沒有任何與之關聯的參照,即他們的參照計數都不爲 0,則說明物件不太可能再被用到,那麼這個物件就是可回收物件。
  • 可達性分析(根搜尋演算法):爲了解決參照計數法的回圈參照問題,Java 使用了可達性分析的方法。通過一系列的「GC roots」物件作爲起點搜尋。如果在「GC roots」和一個物件之間沒有可達路徑,則稱該物件是不可達的。要注意的是,不可達物件不等價於可回收物件,不可達物件變爲可回收物件至少要經過兩次標記過程。兩次標記後仍然是可回收物件,則將面臨回收。

5. 垃圾收集演算法?

新生代採用的是複製演算法,老生代採用的是Major GC

6. 爲什麼要將新生代和老生代,分代收集垃圾?

爲了減少full GC的次數,因爲在老生代進行full GC時,都會觸發STW=stop the world不能提供任何服務,程式在此時不能進行任何的操作,相當於程式中斷掉了,而這在我們程式中是不希望發生的,因此進行分代收集垃圾,新生代垃圾回收的次數多,老生代垃圾回收的次數少,從而減少了full GC,也就讓STW觸發的次數減少.

ull GC時,都會觸發STW=stop the world不能提供任何服務,程式在此時不能進行任何的操作,相當於程式中斷掉了,而這在我們程式中是不希望發生的,因此進行分代收集垃圾,新生代垃圾回收的次數多,老生代垃圾回收的次數少,從而減少了full GC,也就讓STW觸發的次數減少.