1. 程式人生 > >java面試總結之——JVM篇

java面試總結之——JVM篇

       下面對關於jvm的問題做一個總結和整理,分享給大家,方便大家一起學習。中間有說得不對的地方,還請大家多多批評指正。

1.什麼是JVM及其工作原理

答:JVM是用於執行java程式的虛擬機器,是java跨平臺特性的基礎,其工作原理是模擬處理器,堆疊,暫存器等,還有相應的系統指令.

2. 描述一下JVM 載入class檔案的原理機制?

答:由於Java的跨平臺性,經過編譯的Java源程式並不是一個可執行程式,而是一個或多個類檔案。當Java程式需要使用某個類時,JVM會確保這個類已經被載入、連線(驗證、準備和解析)和初始化。

(1)載入

這個是由類載入器執行的。改步驟將查詢位元組碼(通常在classpath所指定的路徑中查詢,但這並非是必需的),並從這些位元組碼中建立Class物件。

(2)連結

a) 驗證 主要驗證class檔案的格式是否正確,語法是否正確,位元組碼是否正確,二進位制是否相容

b) 準備 為類的靜態變數分配空間及設定預設初始值。

c) 解析:將class檔案中符號引用轉變成直接引用。

(3)初始化 

如果該類具有超類,則對其初始化,執行靜態初始化器和靜態初始化塊。

(參考《java程式設計思想》第14章 型別資訊)

java類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括:載入、驗證、準備、解析、初始化、使用、解除安裝七個階段。

3.JVM中的類載入器都有哪些?

答:類的載入是由類載入器完成的,類載入器包括:根載入器(BootStrap)、擴充套件載入器(Extension)、系統載入器(System)和使用者自定義類載入器(java.lang.ClassLoader的子類)。從JDK 1.2開始,類載入過程採取了父親委託機制

(PDM)。PDM更好的保證了Java平臺的安全性,在該機制中,JVM自帶的Bootstrap是根載入器,其他的載入器都有且僅有一個父類載入器。類的載入首先請求父類載入器載入,父類載入器無能為力時才由其子類載入器自行載入。JVM不會向Java程式提供對Bootstrap的引用。下面是關於幾個類載入器的說明:

 a)Bootstrap:一般用原生代碼實現,負責載入JVM基礎核心類庫(rt.jar);

 b)Extension:從java.ext.dirs系統屬性所指定的目錄中載入類庫,它的父載入器是Bootstrap;

 c)System:又叫應用類載入器,其父類是Extension。它是應用最廣泛的類載入器。它從環境變數classpath或者系統屬性java.class.path所指定的目錄中記載類,是使用者自定義載入器的預設父載入器。

4.解釋記憶體中的棧(stack)、堆(heap)和靜態儲存區的用法。

答:(1) 堆 用於存放通過new關鍵字和構造器建立的例項

(2) 棧 用於存放基本資料型別的變數,物件的引用以及函式的呼叫執行緒

(3) 靜態儲存區(方法區):用於存放常量,靜態變數,載入的類資訊,字面量,符號引用

除了這3個區域外,JVM記憶體區域還劃分了 計數器,有些虛擬機器甚至會將棧更具體的劃分成 本地方法棧和虛擬機器棧,而方法區還劃分執行常量池,用於存放編譯時生成的字面量和符號引用

 String str = new String(“hello”);

上面的語句中str放在棧上,用new創建出來的字串物件放在堆上,而“hello”這個字面量放在靜態儲存區。

補充:較新版本的Java中使用了一項叫“逃逸分析“的技術,可以將一些區域性物件放在棧上以提升物件的操作效能。

下面給大家看一下jvm的記憶體模型:

5.Heap 堆記憶體又可以細分成什麼?

一個JVM例項只存在一個堆記憶體,堆記憶體的大小是可以調節的。類載入器讀取了類檔案後,需要把類、方法、常變數放到堆記憶體中,儲存所有引用型別的真實資訊,以方便執行器執行,堆記憶體分為三部分:

Young Generation Space 新生區 Young

Tenure generation space 養老區 Old

Permanent Space 永久儲存區 Perm

下面給大家看一下堆記憶體示意圖:

新生區

    新生區是類的誕生、成長、消亡的區域,一個類在這裡產生,應用,最後被垃圾回收器收集,結束生命。新生區又分為兩部分: 伊甸區(Eden space)和倖存者區(Survivor pace) ,所有的類都是在伊甸區被new出來的。倖存區有兩個: 0區(Survivor 0 space)和1區(Survivor 1 space)。當伊甸園的空間用完時,程式又需要建立物件,JVM的垃圾回收器將對伊甸園區進行垃圾回收(Minor GC),將伊甸園區中的不再被其他物件所引用的物件進行銷燬。然後將伊甸園中的剩餘物件移動到倖存 0區。若倖存 0區也滿了,再對該區進行垃圾回收,然後移動到 1 區。那如果1 區也滿了呢?再移動到養老區。若養老區也滿了,那麼這個時候將產生Major GC(FullGC),進行養老區的記憶體清理。若養老區執行了Full GC之後發現依然無法進行物件的儲存,就會產生OOM異常“OutOfMemoryError”。

如果出現java.lang.OutOfMemoryError: Java heap space異常,說明Java虛擬機器的堆記憶體不夠。原因有二:

(1)Java虛擬機器的堆記憶體設定不夠,可以通過引數-Xms、-Xmx來調整。

(2)程式碼中建立了大量大物件,並且長時間不能被垃圾收集器收集(存在被引用)。

養老區

養老區用於儲存從新生區篩選出來的 JAVA 物件,一般池物件都在這個區域活躍。

永久區

永久儲存區是一個常駐記憶體區域,用於存放JDK自身所攜帶的 Class,Interface 的元資料,也就是說它儲存的是執行環境必須的類資訊,被裝載進此區域的資料是不會被垃圾回收器回收掉的,關閉 JVM 才會釋放此區域所佔用的記憶體。

如果出現java.lang.OutOfMemoryError: PermGen space,說明是Java虛擬機器對永久代Perm記憶體設定不夠。一般出現這種情況,都是程式啟動需要載入大量的第三方jar包。例如:在一個Tomcat下部署了太多的應用。或者大量動態反射生成的類不斷被載入,最終導致Perm區被佔滿。

Jdk1.6及之前: 常量池分配在永久代

Jdk1.7: 有,但已經逐步“去永久代”

Jdk1.8及之後: 將最初的永久代取消了,由元空間取代。目的是將HotSpot與JRockit兩個虛擬機器標準

6.GC 是什麼?為什麼要有GC?

答:GC是垃圾回收機制,是JVM對記憶體進行管理的方式。其基本原理就是對物件的引用進行監控,當發現物件不可達時, GC將負責回收所有"不可達"物件的記憶體空間。

GC是垃圾收集的意思,記憶體處理是程式設計人員容易出現問題的地方,忘記或者錯誤的記憶體回收會導致程式或系統的不穩定甚至崩潰,Java提供的GC功能可以自動監測物件是否超過作用域從而達到自動回收記憶體的目的,Java語言沒有提供釋放已分配記憶體的顯示操作方法。Java程式設計師不用擔心記憶體管理,因為垃圾收集器會自動進行管理。要請求垃圾收集,可以呼叫下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以遮蔽掉顯示的垃圾回收呼叫。

垃圾回收可以有效的防止記憶體洩露,有效的使用可以使用的記憶體。垃圾回收器通常是作為一個單獨的低優先順序的執行緒執行,不可預知的情況下對記憶體堆中已經死亡的或者長時間沒有使用的物件進行清除和回收,程式設計師不能實時的呼叫垃圾回收器對某個物件或所有物件進行垃圾回收。在Java誕生初期,垃圾回收是Java最大的亮點之一,因為伺服器端的程式設計需要有效的防止記憶體洩露問題,然而時過境遷,如今Java的垃圾回收機制已經成為被詬病的東西。移動智慧終端使用者通常覺得iOS的系統比Android系統有更好的使用者體驗,其中一個深層次的原因就在於Android系統中垃圾回收的不可預知性。

7.垃圾回收演算法都有哪些?

答:垃圾回收機制有很多種,包括:分代複製垃圾回收、標記垃圾回收、增量垃圾回收等方式。

1)標記清除演算法:首先標記處所有需要回收的物件,在標記完成後統一進行回收。

缺點:標記的過程效率不高、標記清除之後產生大量不連續的記憶體碎片,當需要申請大塊連續記憶體空間時,無法找到。

2)複製演算法:將記憶體按容量費為大小相等的兩塊區域,每次只使用其中的一塊,當一塊記憶體用完了,就將還存活的物件複製到另一塊記憶體上面,然後吧使用過的那塊記憶體統一清理掉。

缺點:每次只能使用總記憶體容量的一半。在物件存活較多的情況下會進行大量複製操作,效率底下。

3)標記整理演算法:和標記清除演算法一樣,先對死亡物件進行標記,然後將存活物件向一端移動,然後直接清理掉邊界以外的記憶體。

4)分代收集演算法:根據物件存活週期的不同,將記憶體劃分為新生代和老年代,新生代使用複製演算法,老年代使用標記清除或標記整理演算法進行垃圾收集。在垃圾收集過程中,可能會將物件移動到不同區域:

· 伊甸園(Eden):這是物件最初誕生的區域,並且對大多數物件來說,這裡是它們唯一存在過的區域。

· 倖存者樂園(Survivor):從伊甸園倖存下來的物件會被挪到這裡。

· 終身頤養園(Tenured):這是足夠老的倖存物件的歸宿。年輕代收集(Minor-GC)過程是不會觸及這個地方的。當年輕代收集不能把物件放進終身頤養園時,就會觸發一次完全收集(Major-GC),這裡可能還會牽扯到壓縮,以便為大物件騰出足夠的空間。

8. StackOverflowError和OutOfMemoryError,談談你的理解

答:如果當前執行緒請求的棧深度大於虛擬機器所允許的最大深度,將丟擲StackOverflowError異常。 如果虛擬機器在擴充套件棧時無法申請到足夠的記憶體空間,則丟擲OutOfMemoryError異常。

OutOfMemoryError表示堆溢位,StackOverFlowError表示棧溢位

9一般什麼時候會發生GC?如何處理?

答:Java中的GC會有兩種回收:年輕代的M inor GC,另外一個就是老年代的Full GC;新物件建立時如果伊甸園空間不足會觸發MinorGC,如果此時老年代的記憶體空間不足會觸發Full GC,如果空間都不足丟擲OutOfMemoryError。

10.jvm中新生代與老年代的區別?

答:新生代物件存在的生命週期較短,老年代物件生命週期長

採用的垃圾回收機制演算法不一樣,新生代採用的是複製演算法,老年代採用的是標記-清除演算法

11.JVM常見啟動引數都有哪些?

  • -Xms / -Xmx — 堆的初始大小 / 堆的最大大小
  • -Xmn — 堆中年輕代的大小
  • -XX:-DisableExplicitGC — 讓System.gc()不產生任何作用
  • -XX:+PrintGCDetails — 列印GC的細節
  • -XX:+PrintGCDateStamps — 列印GC操作的時間戳
  • -XX:NewSize / XX:MaxNewSize — 設定新生代大小/新生代最大大小
  • -XX:NewRatio — 可以設定老生代和新生代的比例
  • -XX:PrintTenuringDistribution — 設定每次新生代GC後輸出倖存者樂園中物件年齡的分佈
  • -XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:設定老年代閥值的初始值和最 大值
  • -XX:TargetSurvivorRatio:設定倖存區的目標使用率

12.Java 中會存在記憶體洩漏嗎,請簡單描述。

答:理論上Java因為有垃圾回收機制(GC)不會存在記憶體洩露問題(這也是Java被廣泛使用於伺服器端程式設計的一個重要原因);然而在實際開發中,可能會存在無用但可達的物件,這些物件不能被GC回收也會發生記憶體洩露。一個例子就是hibernate的Session(一級快取)中的物件屬於持久態,垃圾回收器是不會回收這些物件的,然而這些物件中可能存在無用的垃圾物件。

13.描述一種虛擬機器棧記憶體溢位的場景,有什麼有效的解決方法?

答:一種是當執行緒在執行時,呼叫了的次數過多,會導致執行緒棧變的很深,如果大於虛擬機器所允許的最大深度,就會發生StackOverflowError。第二種方法遞迴沒終止條件。

解決方法:每新建一個執行緒時,會分配給這個執行緒一個棧記憶體初始值,最大的大小可通過 -Xss 來設定。執行緒佔有的棧記憶體大小,通過不斷執行方法,生成區域性變數等操作,棧楨不斷增加,該執行緒的棧記憶體也不斷被使用。最終達到 -Xss 的值時,會丟擲StackOverFlowError。其實這裡就是執行緒的棧記憶體溢位,背後的概念與 OOME 是一樣的,只是jvm設計者取的名字不一樣而已。

小生不才,可能還有很多技術點沒有想到。希望大家多多批評指教,共同學習進步!