JVM常見問題彙總

什麼是JVM?

jvm全稱是java virtual Machine,是java跨平臺特性的保障,java程序被編譯成爲字節碼文件之後都會放到虛擬機上面來執行,同時還提供了對java程序的內存管理功能。

那java虛擬機裏面內存區域是怎麼劃分的?

在java中,內存區域會被分爲 堆,棧,方法區三個大塊以及程序計數器,在堆裏面又會分爲 年輕代【Eden區,s0區,s1區】老年代,棧又被分爲虛擬機棧和本地方法棧。
用一個圖來表示就是這樣:
在這裏插入圖片描述
年輕代:
java虛擬機在進行內存分配的時候會首先在Eden區進行內存分配,當Eden區內存不足的時候就會進行一次mirrorGC,存活的對象就會放在s0區,然而,當s0區內存不足的時候就會使用複製算法進行一次垃圾回收,把S0的存活的對象複製到S1,然後清空S0,那麼當一個對象在S0或S1裏面存活到一定時間後就會被轉移到老年代。如果再Eden區創建的一個對象是一個比較大的對象,那麼會直接分配到老年代
老年代:
老年代裏面所存儲的對象一般都是存活時間比較長的或者是佔用空間比較大的對象
方法區:
在方法區裏面存在常量池,用於存儲我們在程序裏面創建的常量,同時還存儲了所有類的信息,以及方法的信息,還有靜態變量等等,一般在這裏面進行垃圾回收是回收不了什麼空間的
本地方法棧:
各個虛擬機自由實現,調用本地方法的時候會使用
虛擬機棧:
用於存儲方法執行的時候的棧幀(包含方法信息,局部變量表,操作數棧,動態鏈接,方法出口信息等等)
程序計數器:
用於記錄當前執行到了字節碼的那一行

上面提到了垃圾,那麼java裏面什麼樣的對象被稱爲垃圾?

在Java程序裏面,如果一個對象沒有被任何其他對象所引用了,那麼這個對象就可以稱之爲垃圾。

那麼又是如何找到這些垃圾的呢?

要找到垃圾,那麼就需要判斷這些對象有沒有被其它對象所引用,有如下兩種方法:
1、引用計數法
也就是每一個對象都會維護一個值來記錄自己被其它對象引用了多少次,每當被引用一次那麼這個值就會+1,這種做法實現起來固然簡單,但是卻存在一個缺陷,即循環引用的問題。
例如有如下代碼:

 Object a = new Object(); // 對象a 的引用爲1
 Object b = new Object(); // 對象b 的引用爲1
 a = b;//對象b的引用+1變爲2
 b = a;//對象a的引用+1變爲2
 a = null;//對象a的引用-1變爲1;
 b = null;//對象b的引用-1變爲1

在上面的代碼裏面我們可以看到引用a 和 b 孫然都已經等於null了,但是對象a和對象b的引用數還不爲0,所以對象a和對象b就永遠不會被回收,因此,在java裏面並沒有採取這個方法來進行垃圾的標記。
2、可達性分析算法
由於引用計數法的缺陷,所以在java裏面採用了可達性分析算法來進行垃圾標記。
java中的可達性分析算法是基於一系列GCRoots 對象拉進行的,當一個對象自下而上能追溯到GCRoots對象的話那麼這個對象就應該是一個存活的對象。

那麼什麼樣的對象被稱爲是GCRoots對象呢?

1、方法區裏面的常量池裏面的引用對象
2、方法區裏面的靜態屬性引用對象
3、棧裏面的引用對象
4、存活的線程對象

如果一個對象標記爲不可達了一定會被回收嗎?

這個是不一定的,因爲在對象被標記爲不可達之後,虛擬機還會判斷他是否需要執行finalize方法,如果對象重寫了finalize方法,呢麼他就會被加入到一個隊列裏面,交給虛擬機創建的一個低優先級的隊列裏面去執行,這是候如果在finalize方法對象重新被GCroots對象引用了,那麼他就不會被回收了。

前面多次提到了引用,那麼java裏面都有哪些引用呢?

在java裏面一共有四種引用:

  • 強引用
    當我們使用 Object o = new Object(); 創建一個對象的時候這裏的o就是一個強引用,在垃圾回收的時候,被強引用引用的對象是不會被回收的。

  • 軟引用
    在java裏面可以使用 SoftRefrence s = new SoftRefrence()來使用軟引用,使用軟引用引用的對象會在垃圾回收空間不夠的時候被回收

  • 弱引用
    weakRefrence w = new WeakRefrence();被弱引用引用的對象會在下一次垃圾回收的時候被回收。在ThreadLocal裏面的Entry與ThreadLocal對象的引用就是使用的弱引用

  • 虛引用
    虛引用又稱爲幽靈引用,PhatomRenfrence p = new PhatomRenfrence();
    虛引用是最弱的一中引用,被虛引用引用的對象會在被回收的時候收到一個通知

java虛擬機是如何進行垃圾回收的呢?

java虛擬機進行垃圾回收的時候有如下幾種算法:

  • 標記清除算法
    標記清除方法會對標記了需要回收的對象進行回收,但是使用標記回收算法進行垃圾回收會很容易產生內存碎片
  • 複製算法
    複製算法是爲了解決上面的內存碎片的問題,把內存區域分爲兩塊,每次只有一塊可用,在一塊內存區域不夠的時候會把標記爲存活的對象連續複製對另一塊內存區域(這時候不會進行垃圾回收),然後把當前的內存區域清空,不過這種算法帶來的問題就是可使用的內存區域會變小
  • 標記整理算法
    標記整理算法是爲了解決賦值算法的缺陷,先試用標記清除把垃圾回收,然後再對內存進行整理,使其連續,不過這種做法效率比較低下,一般會用於老年代的垃圾回收。
  • 分代收集
    分代收集是現代垃圾回收器主流採用的垃圾回收算法,具體來說是對上面的幾種算法的綜合使用。jvm把堆內存分爲Eden區,S0區,S1區,Old區,在各個區使用不同的垃圾回收算法。由於java裏面的大部分對象都是朝生夕死的,所以Eden區的垃圾回收一定要快,因此Eden一般會使用標記清除法,然後把存活的對象給到S0區,若S0空間不夠了就使用複製算法進行垃圾回收。當一個對象經歷過N次MirrorGC之後仍然存活,那麼就會直接進入老年代

那麼java中的垃圾回收器又有哪些呢?

java裏面的垃圾回收器目前有七種分別如下:

  • serial
    最古老的垃圾收集器,串行運行的,適用於新生代使用,使用複製算法進行垃圾回收
  • serialOld
    與Serial搭配使用的老年代的串行垃圾回收器使用了標記整理算法,回收時間長
  • parNew
    新生代的垃圾回收器,使用了多線程技術,也是採用複製算法,不過線程暫停的時間會短一些,不過只能配合CMS使用
  • parallelOld
    使用了多線程技術的老年代垃圾回收器,也還是使用標記整理算法
  • parallelScavenge
    並行執行的新生代垃圾回收器,也還是使用了複製算法,不過不會暫停用戶線程,可以控制吞吐量
  • CMS
    併發的老年代收集器,使用了標記清除法,效率要比標記整理算法高
    步驟分爲初始標記,併發標記,重新標記,併發清除。
    由於使用了而標記清除算法,所以容易產生內存碎片,導致分配空間因空間不夠出發fullGC。同時還會佔用更多的CPU資源,並且還會產生浮動垃圾(在併發清除的時候產生的新的垃圾)
  • G1
    大堆垃圾回收器,不在區分新生代和老年代,會把大堆劃分爲不同的小的Region

各個垃圾回收器如何配合使用?

如圖,其中CMS與SerialOld爲什麼會相連呢?
是因爲CMS在進行垃圾回收的時候在併發清除會產生浮動的垃圾,也就是清除垃圾用戶線程又產生了垃圾,所以需要預留一部分的空間來裝浮動垃圾,如果預留的內存空間還不夠裝,那麼就會使用SerialOld回收器觸發FullGC
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章