Don’t Cross 32 GB!

看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)橹羔樃?。比浪費(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為Xmx32760mXmx32770m,Xmn都是100M(S0:S1:Eden默認(rèn)1:1:8)??傆?jì)分配一個(gè)包含8個(gè)對(duì)象類型和8個(gè)原子類型以及String,總計(jì)17個(gè)類型屬性的對(duì)象1kw次??此鼈兎謩e觸發(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容