Java堆內存介紹及簡單性能調優

Java底層最重要的一部分就是jvm堆內存,它影響着Java的性能。
這篇博客主要介紹Java堆內存的分區及簡單的Java調優。

一、Java堆內存

首先看這張圖:
在這裏插入圖片描述

  • 堆中的分區
    Java堆內存分爲兩部分:年輕代、老年代
    其中,年輕代分兩個部分:Eden、Survivor

  • 內存分配
    老年代的內存佔堆內總內存的2/3
    年輕代佔1/3,在年輕代中,Eden分8/10,From和To都是1/10
    例:堆中總共300M空間,那麼老年代有200M,年輕代有100M

  • 運行時到底是什麼個情況呢?
    當我們new一個對象之後,首先,這個對象就會被放入Eden區,直到Eden區內存被佔滿

  • Eden滿了之後:
    Eden滿了後,就會觸發垃圾回收機制gc,但是呢,在這個地方是minor gc。minor gc會回收Eden區的垃圾對象(沒有任何指針引用程序的對象)
    JVM虛擬機內部構造圖

  • 垃圾對象?
    可以理解爲:當main函數結束時,棧幀區域會被銷燬掉,然後局部變量也就被釋放掉,那麼指向堆中對象的指針也就被幹掉了,最後,堆中的對象就是個垃圾對象。這裏我們要注意一個點:查找垃圾對象的起始是從棧幀中開始的,然後查到的時一個局部變量

  • 可達性分析算法?GC Root?
    在垃圾對象裏面說了,堆中的對象是由局部變量指出的,那麼我們就可以利用它來判斷是不是垃圾對象,這裏的局部變量就可以理解爲GC Root。可達性分析算法就是以GC Root爲起點,往下搜索,找到的對象就不是垃圾對象,找不到的就是垃圾對象。

  • 接着minor gc
    上面已經說了,minor gc只是針對Eden區的。然後,當進行一次minor gc後,Eden中的垃圾對象全部被幹掉,剩下的非垃圾對象,要進入Survivor區中的From中,這時候,這些非垃圾對象的分代年齡就會加一。

  • 分代年齡?對象頭?
    分代年齡存放在對象的對象頭中,每經歷一輪的垃圾回收,分代年齡就會加一
    對象頭:(https://blog.csdn.net/lkforce/article/details/81128115#1%EF%BC%8CMark%20Word

    1. Mark Word
    2. 指向類的指針
    3. 數組長度(只有數組對象纔有)
  • 然後Eden區再次滿的時候,又會觸發垃圾回收,再次將垃圾對象(包括剛剛放入from中變成新垃圾的對象)幹掉,把Eden和From中非垃圾對象放入To中,並將年齡加一。
    當下次Eden再滿的時候,又會把Eden和To中的非垃圾對象放入From中,年齡加一,以此不斷循環……

  • 所以年齡有什麼卵用?
    當進行的次數多了,直到年齡達到了15(可以改這個參數),這時候就會被移到老年代

  • 上面已經說過,老年代也有一定的大小,那麼如果老年代滿的時候怎麼辦呢?
    這時候就會發生full gc

下面我們以代碼爲例,查看運行時的各區情況:
利用上篇微信紅包中的main函數,使它進入死循環
(對微信紅包算法感興趣的可以看:https://blog.csdn.net/qq_44357371/article/details/103115263

public static void main(String[] args) {
		while (true) {
			test.thirdMethod(5, 20);
		}
	}

在這裏插入圖片描述
old就是代表老年區,我們可以看到Eden區是最快的,然後就發生垃圾回收……,和上面所述相符。
在這裏插入圖片描述
說實話,我想看old區被塞滿的情況,可惜它增長的太慢了。。。

  • 接着full gc。STW?
    當發生full gc時,就會產生STW(stop world,,毀掉整個世界),(哈哈哈,沒這麼牛逼)。這時候呢,就會暫停所有的線程,讓垃圾回收機制專心的回收垃圾。這樣的話,用戶端會卡掉。
  • 爲什麼要把線程停掉呢?
    考慮一種情況,如果在找垃圾時,剛好已經在某個鏈條上面了,這時候,如果線程把這條鏈給幹掉了,那麼後面的本來應該全部是垃圾的,但是gc沒有把它們給找出來,所以停掉線程。

二、性能調優

  • Jvm性能調優到底調的是什麼?
    我們上面已經知道,minor gc發生時,對性能的影響不大,但是full gc發生時,對性能的影響是巨大的。所以調優就是要減少full gc發生的次數,減少STW出現次數;還有就是在發生了Full gc時,所有的線程停掉等待垃圾回收,所以,減少垃圾回收時間。

下面舉一個調優例子:
在這裏插入圖片描述

首先分析:
我們設置了堆的大小爲3G,老年區就有2G,eden有800M,from和to各佔100M
線程在運行時,每秒產生60M的對象,用後直接幹掉變垃圾,這樣大概每13秒就會把Eden佔滿,觸發minor gc。
但是一個問題,在第13s產生的進程,會被直接移到survivor區,但是它的大小超過了survivor中一個區的一半,這時候觸發對象動態年齡判斷機制,直接進入老年代。
但是着60M的對象,在下一秒就又變成了垃圾。。。這樣算下來,大概5、6分鐘就會使老年代觸發full gc,這樣的效率是極其低下的。
所以呢,我們可以對這個系統進行一個調優:
把這些參數改一下,我們可以儘量讓垃圾在年輕代就被幹掉。
總共3G,把old區設置爲1G(因爲沒那麼多要放的),這樣,新生代就有了2G,這樣,Eden分了1.6G,from和to分別200M
這時候,每秒60M的對象過來,最後那一秒在Eden區中傳入survivor中,但是根據對象動態年齡判斷機制,60M小於from或to的一半,不會被傳進老年代,變成垃圾,直接被minor gc乾死,所以基本上,老年代就不可能被放滿,所以就極大的改善了性能!!!

今晚收穫巨大。jvm內容還多,路途且長,繼續奮鬥!
圖片來源:諸葛老師

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