看elasticsearch官方文檔時(shí)赔蒲,提到的一個(gè)觀點(diǎn):Don’t Cross 32 GB。是因?yàn)楫?dāng)JVM堆少于32G時(shí)良漱,HotSpot虛擬機(jī)會(huì)啟用一個(gè)壓縮對(duì)象指針舞虱。而如果超過32G,這個(gè)壓縮對(duì)象指針就會(huì)失效母市。那么矾兜,究竟這個(gè)臨界值的精確值是多大呢?開啟壓縮指針相比沒有開啟窒篱,能節(jié)省多少內(nèi)存呢焕刮?讓我們一探究竟舶沿!
Don’t Cross 32 GB!
在Java的世界里墙杯,絕大部分對(duì)象分配在堆里,并且被一個(gè)指針引用(Student stu = new Student()括荡,new的這個(gè)Student對(duì)象就是分配在堆里高镐,stu就是持有這個(gè)對(duì)象的應(yīng)用)。
32位的操作系統(tǒng)畸冲,最大只支持4G內(nèi)存(即2^32)嫉髓。當(dāng)然,對(duì)于當(dāng)下來說邑闲,32位服務(wù)器應(yīng)該是絕種了算行,所以本文討論的是64位操作系統(tǒng)。對(duì)于64位操作系統(tǒng)來說苫耸,理論上分配的堆可以非常非常大州邢。但是,64位指針的開銷就意味著有更多的浪費(fèi)空間褪子,這僅僅是因?yàn)橹羔樃罅刻省1壤速M(fèi)空間更糟糕的是骗村,64位指針在主內(nèi)存和多級(jí)緩存之間移動(dòng)數(shù)據(jù)的時(shí)候,還會(huì)消費(fèi)更多的帶寬呀枢。
Java用"compressed oops"術(shù)解決了這個(gè)問題胚股,指針不再是指向內(nèi)存中精確位置,而是對(duì)象的偏移量(原文: Instead of pointing at exact byte locations in memory, the pointers reference object offsets)裙秋。這就意味著琅拌,32位指針能引用2^32 個(gè)對(duì)象(大約43億個(gè)對(duì)象),而不是引用總計(jì)2^32 個(gè)字節(jié)大小對(duì)象残吩。所以财忽,堆大小直到32G左右還能保持32位指針。
一旦你越過這個(gè)32G--
一個(gè)具有魔法般的數(shù)值泣侮。指針將切回到普通的對(duì)象指針即彪。每個(gè)指針變大,意味著需要更多的CPU活尊,內(nèi)存和帶寬隶校,真正用來保持對(duì)象的內(nèi)存就會(huì)更少。這就可能導(dǎo)致一種奇怪的現(xiàn)象蛹锰,使用compressed oops的32G的堆和40~50G沒有使用compressed oops的堆保存的對(duì)象數(shù)量是一樣的深胳。
這個(gè)事實(shí)告訴我們:即使你有多余的內(nèi)存,也應(yīng)該盡量避免超過32G這個(gè)界限铜犬。它會(huì)浪費(fèi)內(nèi)存舞终,降低CPU性能,并且大堆情況下GC表現(xiàn)也一般般癣猾。更麻煩的是敛劝,那么大的堆,DUMP分析將是一件極其痛苦纷宇,極其麻煩的事情夸盟。相信我,你一定不想碰到那種局面像捶。
應(yīng)該設(shè)置多大上陕?
32G是個(gè)近似值,這個(gè)臨界值跟JVM和平臺(tái)有關(guān)拓春。如果不想精確設(shè)置的話释簿,31G是個(gè)決定安全的數(shù)值,31G肯定默認(rèn)開啟compressed oops硼莽。我們可以通過增加JVM參數(shù)-XX:+PrintFlagsFinal
庶溶,驗(yàn)證UseCompressedOops
的值,從而得知,到底是不是真的開啟了壓縮指針渐尿,還是壓縮指針失效醉途!
實(shí)踐才是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)!JUST DO IT砖茸!讓我們動(dòng)手驗(yàn)證這個(gè)臨界值吧隘擎!
驗(yàn)證情況--
JDK8前提下,32760m的堆是開啟壓縮指針的凉夯,32770m的堆壓縮指針已經(jīng)關(guān)閉:
[afei@afei ~]$ java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
[afei@afei ~]$ java -Xmx32760m -XX:+PrintFlagsFinal 2> /dev/null | grep UseCompressedOops
bool UseCompressedOops := true {lp64_product}
[afei@afei ~]$ java -Xmx32770m -XX:+PrintFlagsFinal 2> /dev/null | grep UseCompressedOops
bool UseCompressedOops = false {lp64_product}
32G货葬,即32*1024=32768M,剛好在范圍[32760, 32770]中劲够。
土豪我有1T內(nèi)存
JVM大堆的缺點(diǎn)太多了震桶;
- 超過32G壓縮指針失效;
- DUMP分析將是災(zāi)難征绎;
- 堆越大蹲姐,GC表現(xiàn)越差;
總之人柿,不要嘗試吃這個(gè)螃蟹柴墩!那如果我是在一家有錢任性的公司,服務(wù)器牛逼的不要不要的凫岖,都是128G起步江咳!畢竟不要讓貧窮限制了想象!
這種情況下哥放,我有小建議:開多個(gè)32G的JVM實(shí)例歼指。4個(gè)32G的JVM實(shí)例絕對(duì)比1個(gè)128G實(shí)例表現(xiàn)要好。
簡(jiǎn)單測(cè)試驗(yàn)證
筆者做了一個(gè)簡(jiǎn)單測(cè)試甥雕,驗(yàn)證一下這個(gè)問題:分配設(shè)置Xmx為Xmx32760m
和Xmx32770m
踩身,Xmn都是100M(S0:S1:Eden默認(rèn)1:1:8)∠總計(jì)分配一個(gè)包含8個(gè)對(duì)象類型和8個(gè)原子類型以及String惰赋,總計(jì)17個(gè)類型屬性的對(duì)象1kw次宰掉『巧冢看它們分別觸發(fā)了多少YGC,結(jié)論如下表所示:
實(shí)驗(yàn)次數(shù) | 開啟壓縮指針YGC次數(shù) | 關(guān)閉壓縮指針YGC次數(shù) |
---|---|---|
1 | 17.53 | 21.91 |
2 | 17.54 | 21.92 |
3 | 17.52 | 21.94 |
由執(zhí)行結(jié)果可知轨奄,Young區(qū)完全一樣的情況下孟害,開啟壓縮指針相比關(guān)閉壓縮指針,能節(jié)省20%多的內(nèi)存挪拟。由此可知挨务,32G還真是一個(gè)奇妙的魔法數(shù)值!另外需要說明的是YGC次數(shù)有小數(shù),是表示Eden區(qū)占用比例谎柄,比如17.52次YGC表示發(fā)生了17次YGC并且Eden還占了52%丁侄。
執(zhí)行的命令:
java -verbose:gc -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xmx32770m -Xms32770m -Xmn100m -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:CMSInitiatingOccupancyFraction=60 -XX:+UseCMSInitiatingOccupancyOnly Test
說明:-Xmx32760m -Xms32760m
即表示開啟壓縮指針;-Xmx32770m -Xms32770m
即表示關(guān)閉壓縮指針朝巫。
- 附:測(cè)試源碼
/**
* @author wangzhenfei
* @date 2018-09-05
* @since 1.0.0
*/
class Student{
private byte a;
private short b;
private int c;
private long d;
private float e;
private double f;
private boolean g;
private char h;
private Byte i;
private Short j;
private Integer k;
private Long l;
private Float m;
private Double n;
private Boolean o;
private Character p;
private String q;
// 省略帶參數(shù)構(gòu)造方法
}
public class Test {
public static void main(String[] args) throws Exception{
for (int i = 0; i < 10000000; i++) {
Student stu = new Student(
(byte)(i%128), (short)(i%256), i, i, i, i, i%2==0?true:false, 'a',
(byte)(i%128), (short)(i%256), i, (long)i, (float)i, (double)i,
i%2==0?Boolean.TRUE:Boolean.FALSE, 'a', String.valueOf(i));
if(i>0 && i%100000==0){
System.out.println("i="+i);
}
}
// 留點(diǎn)時(shí)間采集GC信息
Thread.sleep(20000);
}
}
參考:https://www.elastic.co/guide/en/elasticsearch/guide/current/heap-sizing.html