JVM:32G以上的堆會發生什麼

這篇短文主要是想告訴你若是給Oracle JVM配置超過32G的堆會發生什麼事情。默認狀況下,堆大小在32G如下的話JVM中的引用會佔用4個字節。這是JVM在啓動的時候就已經決定了的。若是你去掉了-XX:-UseCompressedOops選項的話,固然也能夠在較小的堆上使用8字節的引用(但在生產系統中這麼作是毫無心義的!)。 性能

一旦堆超過了32G,你就進入到64位的世界裏了,所以對象引用就只能是8字節而非4字節了。正如Scott Oaks在他的Java性能:終極指南一書中所說的(234到236頁,這裏有我對該書的一個評價),Java程序的堆中平均會有20%的空間是被對象引用佔據了。也就是說,若是堆的配置是介於Xmx32G到Xmx37G——Xmx38G之間的話,其實是減小了應用程序的可用堆的大小(固然了,具體的數字要取決於你的程序)。對於不少人而言,增長了額外的內存是爲了能讓程序能夠多處理一些數據,而這樣的結果會令他感到意外。 測試

測試——生成LinkedList

我決定測試一下最壞的場景——生成一個值遞增的LinkedList。這個測試很是有意思:看一下將2億個Integer插入到LinkedList中須要多大的堆空間。這個工做就留給讀者來完成了:-) spa

測試的代碼很是簡單:
public class Mem32Test {
public static void main(String[] args) {
List lst = new LinkedList<>();
int i = 0;
while ( true )
{
lst.add( new Integer( i++ ) );
if ( ( i & 0xFFFF ) == 0 )
System.out.println( i ); //shows where you are 
if ( i == System.currentTimeMillis() )
break; //otherwise will not compile
}
System.out.println( lst.size() ); //needed to avoid dead code optimizations
}
}
你能夠結合Xmx以及verbose:gc(或者是-XX:+PrintGCDetails)選項來運行這個程序。同時還得查看下GC的日誌來確認下內存什麼時候會被用完(距離真正拋出OOM還須要至關長的一段時間)。
日誌

首先,我發現JVM切換到64位引用的一個確切的臨界點是——Xmx32767M(很奇怪,正比如32G要少1M)。除此以外我還發現應用程序的可用內存與堆的變化並非線性增加的。而是階段性的增加(你能夠看下Xmx49200M和49500M之間所發生的現象)——這點我想再深刻地探討一下。 code

測試結果

||LinkedList中元素的個數||堆大小|| ||666,697,728 ||Xmx32700M|| ||667,287,552 ||Xmx32730M|| ||667,680,768 ||Xmx32750M|| ||667,877,376 ||Xmx32760M|| ||668,008,448 ||Xmx32764M|| ||668,139,520 ||Xmx32765M|| ||668,008,448 ||Xmx32766M|| ||422,510,592 ||Xmx32767M|| ||429,391,872 ||Xmx33700M|| ||535,166,976 ||Xmx42000M|| ||639,041,536 ||Xmx48700M|| ||643,039,232 ||Xmx49200M|| ||731,578,368 ||Xmx49500M|| ||734,658,560 ||Xmx49700M|| ||1,442,119,680 ||Xmx110000M|| 對象

不難看出,列表元素的個數在Xmx32767M也就是切換到64位引用的時候開始戲劇性地從6億降低到了4億。 生命週期

咱們來看下爲何插入到LinkedList中的元素的數量會發生銳減。JDK中的LinkedList是一個雙向鏈表。所以,每一個Node對象除了包含數據之外(固然了,只是個引用),還有prev及next引用。 內存

在32位模式下,每一個Java對象會包含12字節的對象頭,而後纔是對象自己的字段。每一個對象所佔用的內存還會按8個字節來對齊。所以,32位模式下一個Node對象會佔用12+4*3=24個字節。一個Integer對象須要12+4=16字節(這兩種狀況都不須要補齊填充)。 字符串

可是,一旦切換到了64位以後,LinkedList中的每一個元素的大小會從40字節漲到64字節。 it

內存調優的一些技巧

正如我前面所說的,JVM使用超過32G堆的話就意味着有一個不小的性能損耗。除了增長應用程序的內存使用量之外,JVM的垃圾回收器還要去回收這些對象(你能夠添加-XX:+PrintGCDetails選項來看下對程序的GC所形成的影響)。

對於那些沒有調優過的應用程序,我這裏列舉出了一些簡單的技巧能夠用來減小它的內存使用量(千萬不要以爲這些建議沒什麼,某些狀況下能夠節省的內存至關可觀):

一、應用程序中可能包含許多內容同樣的字符串對象。若是使用的是Java 7及更新的版本,能夠考慮下字符串內聯——這是消除冗餘字符串的終極武器,但必須得謹慎使用——只有那些生命週期適中或較長的字符串才應該進行內聯,由於它們更有可能會出現冗餘。若是你用的是Java 8 update 20之後的版本,能夠嘗試使用字符串去重——JVM會本身去處理字符串冗餘的問題(使用這個特性必須得啓用G1垃圾回收器)。

二、若是堆中有大量的數值型包裝類譬如Integer或者Double的話,最好是將它們存儲在集合裏。如今都已是2015年了,沒有什麼理由拒絕使用原始集合(Primitive Collection)了。最近我寫了篇文章介紹了下不一樣的原始集合庫實現的哈希表的概況。你還能夠看一下我以前關於Trove的一篇文章

三、最後,能夠看一下我以前寫過的有關內存消耗和節省內存的一系列文章(第1篇第2篇第3篇第4篇)。

總結

一、 若是應用程序的堆超過32G的話須要謹慎對待(從低於32G升級到32G以上)——JVM會切換到64位的對象引用,也就是說應用程序的可用堆的空間會減少。解決方法就是不要從32G加起,而是直接加到37~38G以上。這個灰色區域具體是多少取決於你的應用程序——對象的平均大小較大的話,損耗就要少一些。

二、 更明智的作法或許就是不要使用太大的堆,而是將使用的內存限制在32G之內。看一下個人這幾篇文章:字符串內聯字符串去重哈希表及其它原始集合Trove)。

相關文章
相關標籤/搜索