1. 程式人生 > >【面試總結】JVM虛擬機器

【面試總結】JVM虛擬機器

記憶體洩漏與記憶體溢位:

記憶體洩漏:指申請到的物件,用完之後沒有被回收,導致下次申請的時候這些記憶體空間無法訪問。

記憶體溢位:指申請記憶體的時候,沒有足夠的記憶體空間供其使用,超過了記憶體的最大限度,出現了out of memory。(記憶體溢位就是你要求分配的記憶體超出了系統能給你的,系統不能滿足需求,於是產生溢位)

記憶體洩漏最終會導致記憶體溢位。

記憶體溢位的原因:

  • 記憶體中載入的資料量過於龐大,例:一次性從資料庫中取出很多資料。
  • 靜態集合類使用完後,沒有釋放對變數的引用。如:HashMap ,vevtor中儲存了物件,但是物件引用為null,這些靜態集合類還引用這些物件,當申請過多時,會導致記憶體溢位。
  • 程式碼中出現死迴圈,或者產生過多的重複我物件實體。
  • 第三方軟體,過多的jar包
  • JVM啟動引數過小

常見out of memory: java.lang.OutOfMemoryError: Java heap space,java.lang.OutOfMemoryError: PermGen space。


解決步驟如下:

  1. 修改JVM啟動引數,直接增加記憶體,-Xms(初始記憶體),-Xmx最大記憶體。
  2. 列印GC日誌,檢視GC情況,-XX:-printGC Detail
  3. 程式碼走查,檢查是否用死迴圈或者重複申請物件的地方,找出可能溢位的地方。

例項:java.lang.OutOfMemoryError: Java heap space

  • 堆記憶體溢位。
  • 原因:一次性申請過多資料庫資料,當一次查詢10W條以上的資料時就會出現記憶體溢位
  • 做法:(1)增加JVM記憶體,-Xms -Xmx (2)檢視GC日誌,有沒發生GC (3)若年輕代或老年代空間不足,可以設定引數(4)還不行,使用分批查詢,將50W資料分別10批分別取出

java.lang.OutOfMemoryError: PermGen space異常處理

  • PermGen 為永久代,方法區,存放類資訊,靜態資訊,當載入過多的類時候,報出記憶體溢位。
  • 這塊記憶體主要是被JVM存放Class和Meta資訊的,Class在被Loader時就會被放到PermGen space中
  • 解決方法:-XX:PermSize=32M -XX:MaxPermSize=64M 

記憶體洩漏的原因:

  • 靜態集合類由於生命週期與應用程式一樣長,當其中存放物件時,物件釋放了,但是集合類還引用這物件,沒有釋放集合類,導致記憶體洩漏。解決方法:將集合類也釋放掉 vector=null;
  • 連線使用後為關閉,常用的資料庫連線,IO連線等未釋放也會導致記憶體洩漏.解決方法:關閉連線
  • 監聽器,釋放物件後沒有刪除監聽器  解決方法:刪除監聽器
  • 單例物件,由於是靜態物件,在整個生命週期都存在,若單例物件持有外部引用將導致記憶體洩漏。
  • 內部類和外部模組等的引用:  內部類的引用是比較容易遺忘的一種,而且一旦沒釋放可能導致一系列的後繼類物件沒有釋放。此外程式設計師還要小心外部模組不經意的引用,例如程式設計師A 負責A 模組,呼叫了B 模組的一個方法如: public void registerMsg(Object b); 這種呼叫就要非常小心了,傳入了一個物件,很可能模組B就保持了對該物件的引用,這時候就需要注意模組B 是否提供相應的操作去除引用。

JVM記憶體模型:

這裡寫圖片描述

  • 程式計數器:是JVM中一段較小的記憶體空間,作用可以看做是當前執行緒執行位元組碼的行號指示器。位元組碼直譯器的工作都要依靠修改這個計數器的值來完成,是執行緒隔離的空間,如果是native方法,計數器為0.也是唯一一個不會發生記憶體溢位的空間。
  • 虛擬機器棧:執行緒私有,宣告週期與執行緒的週期相同,表示的是java方法執行的記憶體模型:每一個方法入棧的同時都會建立棧幀,用於儲存區域性變量表,操作棧,動態連結和方法出口等資訊。方法的執行過程就對應了,棧幀入棧出棧的過程。區域性變量表中存放編譯期可知的基本資料型別和物件引用。

異常:當執行緒申請的棧深度超過最大深度,丟擲stackoverflowerror.若棧可以擴充套件,當無法擴充套件時,丟擲outofmemory。

  • 堆(heap):JVM中記憶體管理最大的一塊記憶體。是執行緒之間共享,用於存放建立的物件的例項。也是GC發生的主要區域。由於目前jvm採用分代收集演算法,java堆可以分為新生代和老年代。當無法申請記憶體時,丟擲outofmemory。
  • 老年代 : 三分之二的堆空間
  • 年輕代 : 三分之一的堆空間 
    • eden區: 8/10 的年輕代空間
    • survivor0 : 1/10 的年輕代空間
    • survivor1 : 1/10 的年輕代空間
  • 方法區:執行緒共享,主要用於存放編譯期載入的類資訊,常量以及靜態變數資料等。常量池--1.7之後,字串常量池在隊中。
  • 執行時常量池:也是方法區的一部分,用於存放編譯期生成的各種字面量和符號引用。

類載入過程:

載入-驗證-準備-解析-初始化

  • 載入:包括三個部分(1)通過類的全限定名獲取其二進位制位元組流.class檔案(2)通過二進位制檔案將類的靜態儲存結構轉化為方法區執行時的資料結構(3)生成一個class檔案作為該類的訪問入口
  • 驗證:保證位元組流檔案符合jvm的規範。 檔案格式驗證。。
  • 準備:為靜態變數分配記憶體空間,以及賦初始值。這些記憶體都將在方法區中進行分配。準備階段不分配類中的例項變數的記憶體,例項變數將會在物件例項化時隨著物件一起分配在Java堆中。
  • 解析:將類中的符號引用轉化為直接引用(儲存地址)
  • 初始化:為靜態變數賦正確的初始值,開始執行程式程式碼。

Minor GC和Full GC觸發條件總結

GC機制

要準確理解Java的垃圾回收機制,就要從:“什麼時候”,“對什麼東西”,“做了什麼”三個方面來具體分析。

第一:“什麼時候”即就是GC觸發的條件。GC觸發的條件有兩種。(1)程式呼叫System.gc時可以觸發;(2)系統自身來決定GC觸發的時機。

系統判斷GC觸發的依據:根據Eden區和From Space區的記憶體大小來決定。當記憶體大小不足時,則會啟動GC執行緒並停止應用執行緒。

第二:“對什麼東西”籠統的認為是Java物件並沒有錯。但是準確來講,GC操作的物件分為:通過可達性分析法無法搜尋到的物件和可以搜尋到的物件。對於搜尋不到的方法進行標記。

第三:“做了什麼”最淺顯的理解為釋放物件。但是從GC的底層機制可以看出,對於可以搜尋到的物件進行復制操作,對於搜尋不到的物件,呼叫finalize()方法進行釋放。

具體過程:當GC執行緒啟動時,會通過可達性分析法把Eden區和From Space區的存活物件複製到To Space區,然後把Eden Space和From Space區的物件釋放掉。當GC輪訓掃描To Space區一定次數後,把依然存活的物件複製到老年代,然後釋放To Space區的物件。

對於用可達性分析法搜尋不到的物件,GC並不一定會回收該物件。要完全回收一個物件,至少需要經過兩次標記的過程。

第一次標記:對於一個沒有其他引用的物件,篩選該物件是否有必要執行finalize()方法,如果沒有執行必要,則意味可直接回收。(篩選依據:是否複寫或執行過finalize()方法;因為finalize方法只能被執行一次)。

第二次標記:如果被篩選判定位有必要執行,則會放入FQueue佇列,並自動建立一個低優先順序的finalize執行緒來執行釋放操作。如果在一個物件釋放前被其他物件引用,則該物件會被移除FQueue佇列。、、

 Minor GC ,Full GC 觸發條件

Minor GC觸發條件:當Eden區滿時,觸發Minor GC。

Full GC觸發條件:

1、System.gc()方法的呼叫

此方法的呼叫是建議JVM進行Full GC,雖然只是建議而非一定,但很多情況下它會觸發 Full GC,從而增加Full GC的頻率,也即增加了間歇性停頓的次數。強烈影響系建議能不使用此方法就別使用,讓虛擬機器自己去管理它的記憶體,可通過通過-XX:+ DisableExplicitGC來禁止RMI呼叫System.gc。


2、老年代代空間不足


老年代空間只有在新生代物件轉入及建立為大物件、大陣列時才會出現不足的現象,當執行Full GC後空間仍然不足,則丟擲如下錯誤:
java.lang.OutOfMemoryError: Java heap space 
為避免以上兩種狀況引起的Full GC,調優時應儘量做到讓物件在Minor GC階段被回收、讓物件在新生代多存活一段時間及不要建立過大的物件及陣列。

3、永生區空間不足


JVM規範中執行時資料區域中的方法區,在HotSpot虛擬機器中又被習慣稱為永生代或者永生區,Permanet Generation中存放的為一些class的資訊、常量、靜態變數等資料,當系統中要載入的類、反射的類和呼叫的方法較多時,Permanet Generation可能會被佔滿,在未配置為採用CMS GC的情況下也會執行Full GC。如果經過Full GC仍然回收不了,那麼JVM會丟擲如下錯誤資訊:
java.lang.OutOfMemoryError: PermGen space 
為避免Perm Gen佔滿造成Full GC現象,可採用的方法為增大Perm Gen空間或轉為使用CMS GC。


4、CMS GC時出現promotion failed和concurrent mode failure


對於採用CMS進行老年代GC的程式而言,尤其要注意GC日誌中是否有promotion failed和concurrent mode failure兩種狀況,當這兩種狀況出現時可能

會觸發Full GC。
promotion failed是在進行Minor GC時,survivor space放不下、物件只能放入老年代,而此時老年代也放不下造成的;concurrent mode failure是在

執行CMS GC的過程中同時有物件要放入老年代,而此時老年代空間不足造成的(有時候“空間不足”是CMS GC時當前的浮動垃圾過多導致暫時性的空間不足觸發Full GC)。
對措施為:增大survivor space、老年代空間或調低觸發併發GC的比率,但在JDK 5.0+、6.0+的版本中有可能會由於JDK的bug29導致CMS在remark完畢

後很久才觸發sweeping動作。對於這種狀況,可通過設定-XX: CMSMaxAbortablePrecleanTime=5(單位為ms)來避免。


5、統計得到的Minor GC晉升到舊生代的平均大小大於老年代的剩餘空間


這是一個較為複雜的觸發情況,Hotspot為了避免由於新生代物件晉升到舊生代導致舊生代空間不足的現象,在進行Minor GC時,做了一個判斷,如果之

前統計所得到的Minor GC晉升到舊生代的平均大小大於舊生代的剩餘空間,那麼就直接觸發Full GC。
例如程式第一次觸發Minor GC後,有6MB的物件晉升到舊生代,那麼當下一次Minor GC發生時,首先檢查舊生代的剩餘空間是否大於6MB,如果小於6MB,

則執行Full GC。
當新生代採用PS GC時,方式稍有不同,PS GC是在Minor GC後也會檢查,例如上面的例子中第一次Minor GC後,PS GC會檢查此時舊生代的剩餘空間是否

大於6MB,如小於,則觸發對舊生代的回收。
除了以上4種狀況外,對於使用RMI來進行RPC或管理的Sun JDK應用而言,預設情況下會一小時執行一次Full GC。可通過在啟動時通過- java -

Dsun.rmi.dgc.client.gcInterval=3600000來設定Full GC執行的間隔時間或通過-XX:+ DisableExplicitGC來禁止RMI呼叫System.gc。

6、堆中分配很大的物件

所謂大物件,是指需要大量連續記憶體空間的java物件,例如很長的陣列,此種物件會直接進入老年代,而老年代雖然有很大的剩餘空間,但是無法找到足夠大的連續空間來分配給當前物件,此種情況就會觸發JVM進行Full GC。

為了解決這個問題,CMS垃圾收集器提供了一個可配置的引數,即-XX:+UseCMSCompactAtFullCollection開關引數,用於在“享受”完Full GC服務之後額外免費贈送一個碎片整理的過程,記憶體整理的過程無法併發的,空間碎片問題沒有了,但提頓時間不得不變長了,JVM設計者們還提供了另外一個引數 -XX:CMSFullGCsBeforeCompaction,這個引數用於設定在執行多少次不壓縮的Full GC後,跟著來一次帶壓縮的。