JVM垃圾收集器(Java Garbage Collection)。本教程均在JDK1.8+HotSpot爲例來講解的.
先來看看Java7的:
再來看看Jva8的
從上圖中我們可以看出,java8之後換成了元空間。那麼怎麼證明,堆區是有新生代、永久代、元空間三部分組成的呢?OOM這個錯誤我們都熟悉,那麼怎麼手動製造出一個OOM呢?如果16G的物理內存,JVM堆內存能夠分到多少G的內存空間呢?我們帶着這些疑問來一起學習吧
在Java8中,永久帶已經被移除了,被一個稱爲元空間的區域所取代。元空間的本質和永久帶類似。
元空間與永久帶之間最大的區別在於:
永久帶使用的是JVM的堆內存空間,但是java8以後的元空間並不是虛擬機中的空間,而是使用了本機的物理內存空間的。
因此,默認情況下,元空間大小僅受到本地內存大小的限制。類的元數據放入native memory,字符串常量池和靜態類變量存放在java堆區中。這樣可以加載多少類的元數據,就不在由MaxPermSize控制了,而是由系統的實際可用空間來控制。
Java默認堆區空間大小是物理內存的六十四分之一(1/64).默認最大堆空間是物理內存的1/4
想要對JVM調優的話,就先要知道自己的家底。默認情況下,當前服務的JVM最大和最小內存是多少呢?怎麼查看呢?
我們可以使用Runtime這個類來查看。具體代碼如下:
運行結果:
來看看凱哥本子上物理內存大小:
可以看到是24GB。
從打印的結果,我們看知道,凱哥本子上的JVM最大內存是5.4個G。也就是大約等於物理內存的1/4
JVM最小內存就是:368。大約是物理內存的1/64.
是不是證明了JVM默認堆內存最大值佔用物理內存的1/4,最小值佔用物理內存的1/64。沒有忽悠,沒有騙人吧。
看到了嗎?totoalMemory方法和maxMemory方法都是native的。在前面,我們講解JVM體系圖的時候,講解了native關鍵字修飾的方法,這裏就不贅述了。
代碼證明堆內存空間就是新生代、老年代、元空間三個區域:
在idea中通過VM options參數來操作
找到需要修改的類,然後在VM options,添加參數。如下圖:
輸入如下參數:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
堆內存調優參數說明:
命令 |
描述 |
-Xms |
設置初始分配大小,默認物理內存的1/64 |
-Xmx |
最大分配內存,默認爲物理內存的1/4 |
-XX:+PrintGCDetails |
輸出詳細的GC處理日誌 |
修改好了之後,重新運行程序,我們看看控制檯打印的信息:
修改後,我們發現堆內存的最大和最小的值是相等的。需要說明一點,在生產環境中,我們最好也把最大和最小值設置一樣。這樣可以減少空間差距切換從而影響了程序的穩定健壯性。
在上圖2部分區域,就是打印出了jvm的詳細信息。我們可以明顯的看到如下幾個數據:
PSYoungGen、ParOldGen、Metaspace這三個區域,正好就是我們之前文章說的,新生代、老年代、元空間這三個區域。這是邏輯上區分的。
在物理上區分是2個,分別是新生代和老年代,怎麼證明呢?
還記得我們參數設置的是1024m吧。把新生代和老年代的total相加,是不是就是打印出最大和最小堆內存的值?
再來看看新生代和老年代空間佔用比例:305664/699392是不是於等於1/2。
怎麼證明新生代是有伊甸園區、from區、to區三部分組成呢?三部分佔用比例怎麼證明是8/1/1呢?請看下圖:
是不是有三個區域。佔用空間分別是:26214/43520/43520.是不是就是8/1/1?
現在再回過頭,來看看堆內存,是不是更清晰了。
通過修改堆參數,模擬出OOM問題
思路:
寫個while(true)死循環,通過設置JVM的參數,設置小一點。比如8M,然後執行就會出現OOM。或者new一個字節數組,大於配置的參數就可以。比如設置的堆內存大小是8M,那麼byte[] bytes =new byte[10*1024*1024]; //10M的對象。一定會OOM
-Xms8m -Xmx8m -XX:+PrintGCDetails
運行後,查看控制檯打印信息.
是不是看到了熟悉的
[Full GC (Allocation Failure) Exception in thread "main" java.lang.OutOfMemoryError: Java heap space。
下一篇文章預告:GC收集日誌信息分析。歡迎大家和凱哥(凱哥java:kaigejava)一起繼續學習。