天天看點

Android-JVM面試專題:你知道面試官為什麼要問JVM原理嗎?原理包含哪些

比如 GC 的時候必須要等到 Java 線程都進入到 safepoint 的時候 VMThread 才能開始執行 GC,

  1. 循環的末尾 (防止大循環的時候一直不進入 safepoint,而其他線程在等待它進入safepoint)
  2. 方法傳回前
  3. 調用方法的 call 之後
  4. 抛出異常的位置

6. GC 的三種收集方法:标記清除、标記整理、複制算法的原理與特點,分别用在什麼地方,如果讓你優化收集方法,有什麼思路?

先标記,标記完畢之後再清除,效率不高,會産生碎片

**複制算法:**分為 8:1 的 Eden 區和 survivor 區,就是上面談到的 YGC

**标記整理:**标記完畢之後,讓所有存活的對象向一端移動

7. GC 收集器有哪些?CMS 收集器與 G1 收集器的特點。

**并行收集器:**串行收集器使用一個單獨的線程進行收集,GC 時服務有停頓時間

**串行收集器:**次要回收中使用多線程來執行

CMS 收集器是基于“标記—清除”算法實作的,經過多次标記才會被清除

G1 從整體來看是基于**“标記—整理”**算法實作的收集器,從局部(兩個 Region 之間)上來看是基于“複制”算法實作的

8. Minor GC 與 Full GC 分别在什麼時候發生?

新生代記憶體不夠用時候發生 MGC 也叫 YGC,JVM 記憶體不夠的時候發生 FGC

9. 幾種常用的記憶體調試工具:jmap、jstack、jconsole、jhat

jstack 可以看目前棧的情況,jmap 檢視記憶體,jhat 進行 dump 堆的資訊

mat(eclipse 的也要了解一下)

10. 類加載的幾個過程:

加載、驗證、準備、解析、初始化。然後是使用和解除安裝了

通過全限定名來加載生成 class 對象到記憶體中,然後進行驗證這個 class 檔案,包括檔案格式校驗、中繼資料驗證,位元組碼校驗等。準備是對這個對象配置設定記憶體。解析是将符号引用轉化為直接引用(指針引用),初始化就是開始執行構造器的代碼

11.JVM 記憶體分哪幾個區,每個區的作用是什麼?

java 虛拟機主要分為以下一個區:

方法區:

  1. 有時候也成為永久代,在該區内很少發生垃圾回收,但是并不代表不發生 GC,在這裡進行的 GC 主要是對方法區裡的常量池和對類型的解除安裝
  2. 方法區主要用來存儲已被虛拟機加載的類的資訊、常量、靜态變量和即時編譯器編譯後的代碼等資料。
  3. 該區域是被線程共享的。
  4. 方法區裡有一個運作時常量池,用于存放靜态編譯産生的字面量和符号引用。該常量池具有動态性,也就是說常量并不一定是編譯時确定,運作時生成的常量也會存在這個常量池中。

虛拟機棧:

  1. 虛拟機棧也就是我們平常所稱的棧記憶體,它為 java 方法服務,每個方法在執行的時候都會建立一個棧幀,用于存儲局部變量表、操作數棧、動态連結和方法出口等資訊。
  2. 虛拟機棧是線程私有的,它的生命周期與線程相同。
  3. 局部變量表裡存儲的是基本資料類型、returnAddress 類型(指向一條位元組碼指令的位址)和對象引用,這個對象引用有可能是指向對象起始位址的一個指針,也有可能是代表對象的句柄或者與對象相關聯的位置。局部變量所需的記憶體空間在編譯器間确定

4.操作數棧的作用主要用來存儲運算結果以及運算的操作數,它不同于局部變量表通過索引來通路,而是壓棧和出棧的方式

5.每個棧幀都包含一個指向運作時常量池中該棧幀所屬方法的引用,持有這個引用是為了支援方法調用過程中的動态連接配接.動态連結就是将常量池中的符号引用在運作期轉化為直接引用。

本地方法棧

本地方法棧和虛拟機棧類似,隻不過本地方法棧為 Native 方法服務。

java 堆是所有線程所共享的一塊記憶體,在虛拟機啟動時建立,幾乎所有的對象執行個體都在這裡建立,是以該區域經常發生垃圾回收操作。

程式計數器

記憶體空間小,位元組碼解釋器工作時通過改變這個計數值可以選取下一條需要執行的位元組碼指令,分支、循環、跳轉、異常處理和線程恢複等功能都需要依賴這個計數器完成。該記憶體區域是唯一一個 java 虛拟機規範沒有規定任何 OOM 情況的區域。

12.如和判斷一個對象是否存活?(或者 GC 對象的判定方

法)

判斷一個對象是否存活有兩種方法:

1. 引用計數法

所謂引用計數法就是給每一個對象設定一個引用計數器,每當有一個地方引用這個對象時,就将計數器加一,引用失效時,計數器就減一。當一個對象的引用計數器為零時,說明此對象沒有被引用,也就是“死對象”,将會被垃圾回收。

引用計數法有一個缺陷就是無法解決循環引用問題,也就是說當對象 A 引用對象 B,對象B 又引用者對象 A,那麼此時 A,B 對象的引用計數器都不為零,也就造成無法完成垃圾回收,是以主流的虛拟機都沒有采用這種算法。

2.可達性算法(引用鍊法)

該算法的思想是:從一個被稱為 GC Roots 的對象開始向下搜尋,如果一個對象到 GC Roots 沒有任何引用鍊相連時,則說明此對象不可用。

在 java 中可以作為 GC Roots 的對象有以下幾種:

  • 虛拟機棧中引用的對象
  • 方法區類靜态屬性引用的對象
  • 方法區常量池引用的對象
  • 本地方法棧 JNI 引用的對象

雖然這些算法可以判定一個對象是否能被回收,但是當滿足上述條件時,一個對象比不一定會被回收。當一個對象不可達 GC Root 時,這個對象并不會立馬被回收,而是出于一個死緩的階段,若要被真正的回收需要經曆兩次标記如果對象在可達性分析中沒有與 GC Root 的引用鍊,那麼此時就會被第一次标記并且進行一次篩選,篩選的條件是是否有必要執行 finalize()方法。當對象沒有覆寫 finalize()方法或者已被虛拟機調用過,那麼就認為是沒必要的。

如果該對象有必要執行 finalize()方法,那麼這個對象将會放在一個稱為 F-Queue 的對隊列中,虛拟機會觸發一個 Finalize()線程去執行,此線程是低優先級的,并且虛拟機不會承諾一直等待它運作完,這是因為如果 finalize()執行緩慢或者發生了死鎖,那麼就會造成 F-Queue 隊列一直等待,造成了記憶體回收系統的崩潰。GC 對處于 F-Queue 中的對象進行第二次被标記,這時,該對象将被移除”即将回收”集合,等待回收。

13.簡述 java 垃圾回收機制?

在 java 中,程式員是不需要顯示的去釋放一個對象的記憶體的,而是由虛拟機自行執行。在JVM 中,有一個垃圾回收線程,它是低優先級的,在正常情況下是不會執行的,隻有在虛拟機空閑或者目前堆記憶體不足時,才會觸發執行,掃面那些沒有被任何引用的對象,并将它們添加到要回收的集合中,進行回收。

14.java 中垃圾收集的方法有哪些?

1. 标記-清除:

這是垃圾收集算法中最基礎的,根據名字就可以知道,它的思想就是标記哪些要被回收的對象,然後統一回收。這種方法很簡單,但是會有兩個主要問題:

1)效率不高,标記和清除的效率都很低;

2)會産生大量不連續的記憶體碎片,導緻以後程式在配置設定較大的對象時,由于沒有充足的連續記憶體而提前觸發一次 GC 動作。

2. 複制算法:

為了解決效率問題,複制算法将可用記憶體按容量劃分為相等的兩部分,然後每次隻使用其中的一塊,當一塊記憶體用完時,就将還存活的對象複制到第二塊記憶體上,然後一次性清楚完第一塊記憶體,再将第二塊上的對象複制到第一塊。但是這種方式,記憶體的代價太高,每次基本上都要浪費一般的記憶體。

于是将該算法進行了改進,記憶體區域不再是按照 1:1 去劃分,而是将記憶體劃分為8:1:1 三部分,較大那份記憶體交 Eden 區,其餘是兩塊較小的記憶體區叫 Survior 區。每次都會優先使用 Eden 區,若 Eden 區滿,就将對象複制到第二塊記憶體區上,然後清除 Eden 區,如果此時存活的對象太多,以至于 Survivor 不夠時,會将這些對象通過配置設定擔保機制複制到老年代中。(java 堆又分為新生代和老年代)

3. 标記-整理:

該算法主要是為了解決标記-清除,産生大量記憶體碎片的問題;當對象存活率較高時,也解決了複制算法的效率問題。它的不同之處就是在清除對象的時候現将可回收對象移動到一端,然後清除掉端邊界以外的對象,這樣就不會産生記憶體碎片了。

4. 分代收集:

現在的虛拟機垃圾收集大多采用這種方式,它根據對象的生存周期,将堆分為新生代和老年代。在新生代中,由于對象生存期短,每次回收都會有大量對象死去,那麼這時就采用複制算法。老年代裡的對象存活率較高,沒有額外的空間進行配置設定擔保,是以可以使用标記-整理 或者 标記-清除。

15.java 記憶體模型

java 記憶體模型(JMM)是線程間通信的控制機制.JMM 定義了主記憶體和線程之間抽象關系。

線程之間的共享變量存儲在主記憶體(main memory)中,每個線程都有一個私有的本地記憶體(local memory),本地記憶體中存儲了該線程以讀/寫共享變量的副本。本地記憶體是JMM 的一個抽象概念,并不真實存在。它涵蓋了緩存,寫緩沖區,寄存器以及其他的硬體和編譯器優化。

Java 記憶體模型的抽象示意圖如下:

Android-JVM面試專題:你知道面試官為什麼要問JVM原理嗎?原理包含哪些

從上圖來看,線程 A 與線程 B 之間如要通信的話,必須要經曆下面 2 個步驟:

  1. 首先,線程 A 把本地記憶體 A 中更新過的共享變量重新整理到主記憶體中去。
  2. 然後,線程 B 到主記憶體中去讀取線程 A 之前已更新過的共享變量。

16.java 類加載過程?

java 類加載需要經曆一下 7 個過程:

加載

加載時類加載的第一個過程,在這個階段,将完成一下三件事情:

  1. 通過一個類的全限定名擷取該類的二進制流。
  2. 将該二進制流中的靜态存儲結構轉化為方法去運作時資料結構。
  3. 在記憶體中生成該類的 Class 對象,作為該類的資料通路入口。

驗證

驗證的目的是為了確定 Class 檔案的位元組流中的資訊不回危害到虛拟機.在該階段主要完成以下四鐘驗證:

  1. 檔案格式驗證:驗證位元組流是否符合 Class 檔案的規範,如主次

《Android學習筆記總結+最新移動架構視訊+大廠安卓面試真題+項目實戰源碼講義》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整資料開源分享

版本号是否在目前虛拟機範圍内,常量池中的常量是否有不被支援的類型。

2. 中繼資料驗證:對位元組碼描述的資訊進行語義分析,如這個類是否有父類,是否內建了不被繼承的類等。

3. 位元組碼驗證:是整個驗證過程中最複雜的一個階段,通過驗證資料流和控制流的分析,确定程式語義是否正确,主要針對方法體的驗證。如:方法中的類型轉換是否正确,跳轉指令是否正确等。

4. 符号引用驗證:這個動作在後面的解析過程中發生,主要是為了確定解析動作能正确執行。

準備

準備階段是為類的靜态變量配置設定記憶體并将其初始化為預設值,這些記憶體都将在方法區中進行配置設定。準備階段不配置設定類中的執行個體變量的記憶體,執行個體變量将會在對象執行個體化時随着對象一起配置設定在 Java 堆中。

public static int value=123;

//在準備階段 value 初始值為 0 。在初始化階段才會變為 123 。

解析

該階段主要完成符号引用到直接引用的轉換動作。解析動作并不一定在初始化動作完成之前,也有可能在初始化之後。

最後

對于很多國中級Android工程師而言,想要提升技能,往往是自己摸索成長,不成體系的學習效果低效漫長且無助。整理的這些架構技術希望對Android開發的朋友們有所參考以及少走彎路,本文的重點是你有沒有收獲與成長,其餘的都不重要,希望讀者們能謹記這一點。

同時我經過多年的收藏目前也算收集到了一套完整的學習資料以及高清詳細的Android架構進階學習導圖及筆記分享給大家,希望對想成為架構師的朋友有一定的參考和幫助。

下面是部分資料截圖,誠意滿滿:特别适合有開發經驗的Android程式員們學習。

Android-JVM面試專題:你知道面試官為什麼要問JVM原理嗎?原理包含哪些
不論遇到什麼困難,都不應該成為我們放棄的理由!

如果你看到了這裡,覺得文章寫得不錯就給個贊呗?如果你覺得那裡值得改進的,請給我留言,一定會認真查詢,修正不足,謝謝。

師的朋友有一定的參考和幫助。

下面是部分資料截圖,誠意滿滿:特别适合有開發經驗的Android程式員們學習。

[外鍊圖檔轉存中…(img-c41iurAe-1640923451265)]

不論遇到什麼困難,都不應該成為我們放棄的理由!

如果你看到了這裡,覺得文章寫得不錯就給個贊呗?如果你覺得那裡值得改進的,請給我留言,一定會認真查詢,修正不足,謝謝。

本文已被CODING開源項目:《Android學習筆記總結+移動架構視訊+大廠面試真題+項目實戰源碼》收錄