天天看點

java常見面試題及答案 11-20(JVM)

java虛拟機主要分為以下一個區: 方法區: 1. 有時候也成為永久代,在該區内很少發生垃圾回收,但是并不代表不發生GC,在這裡進行的GC主要是對方法區裡的常量池和對類型的解除安裝 2. 方法區主要用來存儲已被虛拟機加載的類的資訊、常量、靜态變量和即時編譯器編譯後的代碼等資料。 3. 該區域是被線程共享的。 4. 方法區裡有一個運作時常量池,用于存放靜态編譯産生的字面量和符号引用。該常量池具有動态性,也就是說常量并不一定是編譯時确定,運作時生成的常量也會存在這個常量池中。
java常見面試題及答案 11-20(JVM)
虛拟機棧: 1. 虛拟機棧也就是我們平常所稱的棧記憶體,它為java方法服務,每個方法在執行的時候都會建立一個棧幀,用于存儲局部變量表、操作數棧、動态連結和方法出口等資訊。 2. 虛拟機棧是線程私有的,它的生命周期與線程相同。 3. 局部變量表裡存儲的是基本資料類型、returnAddress類型(指向一條位元組碼指令的位址)和對象引用,這個對象引用有可能是指向對象起始位址的一個指針,也有可能是代表對象的句柄或者與對象相關聯的位置。局部變量所需的記憶體空間在編譯器間确定 4.操作數棧的作用主要用來存儲運算結果以及運算的操作數,它不同于局部變量表通過索引來通路,而是壓棧和出棧的方式 5.每個棧幀都包含一個指向運作時常量池中該棧幀所屬方法的引用,持有這個引用是為了支援方法調用過程中的動态連接配接.動态連結就是将常量池中的符号引用在運作期轉化為直接引用。 本地方法棧 本地方法棧和虛拟機棧類似,隻不過本地方法棧為Native方法服務。 堆 java堆是所有線程所共享的一塊記憶體,在虛拟機啟動時建立,幾乎所有的對象執行個體都在這裡建立,是以該區域經常發生垃圾回收操作。 程式計數器 記憶體空間小,位元組碼解釋器工作時通過改變這個計數值可以選取下一條需要執行的位元組碼指令,分支、循環、跳轉、異常處理和線程恢複等功能都需要依賴這個計數器完成。該記憶體區域是唯一一個java虛拟機規範沒有規定任何OOM情況的區域。
判斷一個對象是否存活有兩種方法: 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中的對象進行第二次被标記,這時,該對象将被移除”即将回收”集合,等待回收。
在java中,程式員是不需要顯示的去釋放一個對象的記憶體的,而是由虛拟機自行執行。在JVM中,有一個垃圾回收線程,它是低優先級的,在正常情況下是不會執行的,隻有在虛拟機空閑或者目前堆記憶體不足時,才會觸發執行,掃面那些沒有被任何引用的對象,并将它們添加到要回收的集合中,進行回收。
标記-清除: 這是垃圾收集算法中最基礎的,根據名字就可以知道,它的思想就是标記哪些要被回收的對象,然後統一回收。這種方法很簡單,但是會有兩個主要問題:1.效率不高,标記和清除的效率都很低;2.會産生大量不連續的記憶體碎片,導緻以後程式在配置設定較大的對象時,由于沒有充足的連續記憶體而提前觸發一次GC動作。 複制算法: 為了解決效率問題,複制算法将可用記憶體按容量劃分為相等的兩部分,然後每次隻使用其中的一塊,當一塊記憶體用完時,就将還存活的對象複制到第二塊記憶體上,然後一次性清楚完第一塊記憶體,再将第二塊上的對象複制到第一塊。但是這種方式,記憶體的代價太高,每次基本上都要浪費一般的記憶體。 于是将該算法進行了改進,記憶體區域不再是按照1:1去劃分,而是将記憶體劃分為8:1:1三部分,較大那份記憶體交Eden區,其餘是兩塊較小的記憶體區叫Survior區。每次都會優先使用Eden區,若Eden區滿,就将對象複制到第二塊記憶體區上,然後清除Eden區,如果此時存活的對象太多,以至于Survivor不夠時,會将這些對象通過配置設定擔保機制複制到老年代中。(java堆又分為新生代和老年代) 标記-整理 該算法主要是為了解決标記-清除,産生大量記憶體碎片的問題;當對象存活率較高時,也解決了複制算法的效率問題。它的不同之處就是在清除對象的時候現将可回收對象移動到一端,然後清除掉端邊界以外的對象,這樣就不會産生記憶體碎片了。 分代收集 現在的虛拟機垃圾收集大多采用這種方式,它根據對象的生存周期,将堆分為新生代和老年代。在新生代中,由于對象生存期短,每次回收都會有大量對象死去,那麼這時就采用複制算法。老年代裡的對象存活率較高,沒有額外的空間進行配置設定擔保,是以可以使用标記-整理 或者 标記-清除。
java記憶體模型(JMM)是線程間通信的控制機制.JMM定義了主記憶體和線程之間抽象關系。線程之間的共享變量存儲在主記憶體(main memory)中,每個線程都有一個私有的本地記憶體(local memory),本地記憶體中存儲了該線程以讀/寫共享變量的副本。本地記憶體是JMM的一個抽象概念,并不真實存在。它涵蓋了緩存,寫緩沖區,寄存器以及其他的硬體和編譯器優化。Java記憶體模型的抽象示意圖如下: 從上圖來看,線程A與線程B之間如要通信的話,必須要經曆下面2個步驟: 1. 首先,線程A把本地記憶體A中更新過的共享變量重新整理到主記憶體中去。 2. 然後,線程B到主記憶體中去讀取線程A之前已更新過的共享變量。 寫的很好:http://www.infoq.com/cn/articles/java-memory-model-1
java類加載需要經曆一下7個過程: 加載 加載時類加載的第一個過程,在這個階段,将完成一下三件事情: 1. 通過一個類的全限定名擷取該類的二進制流。 2. 将該二進制流中的靜态存儲結構轉化為方法去運作時資料結構。 3. 在記憶體中生成該類的Class對象,作為該類的資料通路入口。 驗證 驗證的目的是為了確定Class檔案的位元組流中的資訊不回危害到虛拟機.在該階段主要完成以下四鐘驗證: 1. 檔案格式驗證:驗證位元組流是否符合Class檔案的規範,如主次版本号是否在目前虛拟機範圍内,常量池中的常量是否有不被支援的類型. 2. 中繼資料驗證:對位元組碼描述的資訊進行語義分析,如這個類是否有父類,是否內建了不被繼承的類等。 3. 位元組碼驗證:是整個驗證過程中最複雜的一個階段,通過驗證資料流和控制流的分析,确定程式語義是否正确,主要針對方法體的驗證。如:方法中的類型轉換是否正确,跳轉指令是否正确等。 4. 符号引用驗證:這個動作在後面的解析過程中發生,主要是為了確定解析動作能正确執行。 準備 準備階段是為類的靜态變量配置設定記憶體并将其初始化為預設值,這些記憶體都将在方法區中進行配置設定。準備階段不配置設定類中的執行個體變量的記憶體,執行個體變量将會在對象執行個體化時随着對象一起配置設定在Java堆中。

1

2

解析 該階段主要完成符号引用到直接引用的轉換動作。解析動作并不一定在初始化動作完成之前,也有可能在初始化之後。 初始化 初始化時類加載的最後一步,前面的類加載過程,除了在加載階段使用者應用程式可以通過自定義類加載器參與之外,其餘動作完全由虛拟機主導和控制。到了初始化階段,才真正開始執行類中定義的Java程式代碼。
虛拟機把描述類的資料從Class檔案加載到記憶體,并對資料進行校驗,解析和初始化,最終形成可以被虛拟機直接使用的java類型。
當一個類收到了類加載請求時,不會自己先去加載這個類,而是将其委派給父類,由父類去加載,如果此時父類不能加載,回報給子類,由子類去完成類的加載。
實作通過類的權限定名擷取該類的二進制位元組流的代碼塊叫做類加載器。 主要有一下四種類加載器: 1. 啟動類加載器(Bootstrap ClassLoader)用來加載java核心類庫,無法被java程式直接引用。 2. 擴充類加載器(extensions class loader):它用來加載 Java 的擴充庫。Java 虛拟機的實作會提供一個擴充庫目錄。該類加載器在此目錄裡面查找并加載 Java 類。 3. 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader()來擷取它。 4. 使用者自定義類加載器,通過繼承 java.lang.ClassLoader類的方式實作。
對象優先在堆的Eden區配置設定。 大對象直接進入老年代. 長期存活的對象将直接進入老年代. 當Eden區沒有足夠的空間進行配置設定時,虛拟機會執行一次Minor GC.Minor Gc通常發生在新生代的Eden區,在這個區的對象生存期短,往往發生Gc的頻率較高,回收速度比較快;Full Gc/Major GC 發生在老年代,一般情況下,觸發老年代GC的時候不會觸發Minor GC,但是通過配置,可以在Full GC之前進行一次Minor GC這樣可以加快老年代的回收速度。 原文位址http://www.bieryun.com/551.html