使用未初始化的内存是什么意思_他们都说JVM能实际使用的内存比-Xmx指定的少?这是为什么呢...
這確實是個挺奇怪的問題,特別是當最常出現的幾種解釋理由都被排除后,看來JVM并沒有耍一些明顯的小花招:
- -Xmx和-Xms是相等的,因此檢測結果并不會因為堆內存增加而在運行時有所變化。
- 通過關閉自適應調整策略(-XX:-UseAdaptiveSizePolicy),JVM已經事先被禁止動態調整內存池的大小。
重現差異檢測結果
要弄清楚這個問題的第一步就是要明白這些工具的實現原理。通過標準APIs,我們可以用以下簡單語句得到可使用的內存信息。
System.out.println("Runtime.getRuntime().maxMemory()="+Runtime.getRuntime().maxMemory());而且確實,現有檢測工具底層也是用這個語句來進行檢測。要解決這個問題,首先我們需要一個可重復使用的測試用例。因此,我寫了下面這段代碼:
package?eu.plumbr.test;//imports?skipped?for?brevitypublic?class?HeapSizeDifferences?{??static?Collection?objects?=?new?ArrayList();??static?long?lastMaxMemory?=?0;??public?static?void?main(String[]?args)?{????try?{??????List?inputArguments?=?ManagementFactory.getRuntimeMXBean().getInputArguments();??????System.out.println("Running?with:?"?+?inputArguments);??????while?(true)?{????????printMaxMemory();????????consumeSpace();??????}????}?catch?(OutOfMemoryError?e)?{??????freeSpace();??????printMaxMemory();????}??}??static?void?printMaxMemory()?{????long?currentMaxMemory?=?Runtime.getRuntime().maxMemory();????if?(currentMaxMemory?!=?lastMaxMemory)?{??????lastMaxMemory?=?currentMaxMemory;??????System.out.format("Runtime.getRuntime().maxMemory():?%,dK.%n",?currentMaxMemory?/?1024);????}??}??static?void?consumeSpace()?{????objects.add(new?int[1_000_000]);??}??static?void?freeSpace()?{????objects.clear();??}}這段代碼通過將new int[1_000_000]置于一個循環中來不斷分配內存給程序,然后監測JVM運行期的當前可用內存。當程序監測到可用內存大小發生變化時,通過打印出Runtime.getRuntime().maxMemory()返回值來得到當前可用內存尺寸,輸出類似下面語句:
Running?with:?[-Xms2048M,?-Xmx2048M]Runtime.getRuntime().maxMemory():?2,010,112K.實際情況也確實如預估的那樣,盡管我已經給JVM預先指定分配了2G對內存,在不知道為什么在運行期有85M內存不見了。你大可以把 Runtime.getRuntime().maxMemory()的返回值2,010,112K 除以1024來轉換成MB,那樣你將得到1,963M,正好和2048M差85M。
找到根本原因
在成功重現了這個問題之后,我嘗試用使用不同的GC算法,果然檢測結果也不盡相同。
除了G1算法剛好完整使用了我預指定分配的2G之外,其余每種GC算法似乎都不同程度地丟失了一些內存。
現在我們就該看看在JVM的源代碼中有沒有關于這個問題的解釋了。我在CollectedHeap這個類的源代碼中找到了如下的解釋:
??Running?with:?[-Xms2048M,?-Xmx2048M]??//?Support?for?java.lang.Runtime.maxMemory():??return?the?maximum?amount?of??//?memory?that?the?vm?could?make?available?for?storing?'normal'?java?objects.??//?This?is?based?on?the?reserved?address?space,?but?should?not?include?space??//?that?the?vm?uses?internally?for?bookkeeping?or?temporary?storage??//?(e.g.,?in?the?case?of?the?young?gen,?one?of?the?survivor??//?spaces).??virtual?size_t?max_capacity()?const?=?0;我不得不說這個答案藏得有點深,但是只要你有足夠的好奇心,還是不難發現的:有時候,有一塊Survivor區是不被計算到可用內存中的。
明白這一點之后問題就好解決了。打開并查看GC logging 信息之后我們發現,在Serial,Parallel以及CMS算法回收過程中丟失的那些內存,尺寸剛好等于JVM從2G堆內存中劃分給Survivor區內存的尺寸。例如,在上面的ParallelGC算法運行時,GC logging信息如下:
Running?with:?[-Xms2g,?-Xmx2g,?-XX:+UseParallelGC,?-XX:+PrintGCDetails]Runtime.getRuntime().maxMemory():?2,010,112K....?rest?of?the?GC?log?skipped?for?brevity?...?PSYoungGen??????total?611840K,?used?524800K?[0x0000000795580000,?0x00000007c0000000,?0x00000007c0000000)??eden?space?524800K,?100%?used?[0x0000000795580000,0x00000007b5600000,0x00000007b5600000)??from?space?87040K,?0%?used?[0x00000007bab00000,0x00000007bab00000,0x00000007c0000000)??to???space?87040K,?0%?used?[0x00000007b5600000,0x00000007b5600000,0x00000007bab00000)?ParOldGen???????total?1398272K,?used?1394966K?[0x0000000740000000,?0x0000000795580000,?0x0000000795580000)由上面的信息可以看出,Eden區被分配了524,800K,兩個Survivor區都被分配到了87,040K,老年代(Old space)則被分配了1,398,272K。把Eden區、老年代以及一個Survivor區的尺寸求和,剛好等于2,010,112K,說明丟失的那85M(87,040K)確實就是剩下的那個Survivor區。
總結
讀完這篇帖子的你現在應該對如何探索Java API的實現原理有了一些新的想法。下次當你用某個可視化工具查看可用堆內存發現所得的結果略少于-Xmx指定分配的大小時,你就知道這兩者之間的差值是一塊Survivor區的大小。
私信回復 資料 領取一線大廠Java面試題總結+阿里巴巴泰山手冊+各知識點學習思維導+一份300頁pdf文檔的Java核心知識點總結!
這些資料的內容都是面試時面試官必問的知識點,篇章包括了很多知識點,其中包括了有基礎知識、Java集合、JVM、多線程并發、spring原理、微服務、Netty 與RPC 、Kafka、日記、設計模式、Java算法、數據庫、Zookeeper、分布式緩存、數據結構等等。
我必須承認這個知識點在日常編程中并不是特別常用,但這并不是這篇帖子的重點。我寫下這篇帖子是為了描述一種特質,一種我經常在優秀的程序員身上尋找的特質-好奇心。好的程序員們會經常試著去了解一些事物工作的機理以及原因。有時問題的答案并不會那么顯而易見,但是希望你能堅持尋找下去,最終在尋找過程中的所累積的知識總會讓你獲益匪淺。
總結
以上是生活随笔為你收集整理的使用未初始化的内存是什么意思_他们都说JVM能实际使用的内存比-Xmx指定的少?这是为什么呢...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 企业年报在哪里查
- 下一篇: 什么股东具有优先认股权