對於32位機器,進程能使用的最大內存是4G。若是進程須要使用更多的內存,須要使用64位機器。html
對於Java進程,在oop只有32位時,只能引用4G內存。所以,若是須要使用更大的堆內存,須要部署64位JVM。這樣,oop爲64位,可引用的堆內存就更大了。java
注:oop(ordinary object pointer),即普通對象指針,是JVM中用於表明引用對象的句柄。git
在堆中,32位的對象引用佔4個字節,而64位的對象引用佔8個字節。也就是說,64位的對象引用大小是32位的2倍。github
64位JVM在支持更大堆的同時,因爲對象引用變大卻帶來了性能問題:c#
64位對象引用須要佔用更多的堆空間,留給其餘數據的空間將會減小,從而加快了GC的發生,更頻繁的進行GC。緩存
64位對象引用增大了,CPU能緩存的oop將會更少,從而下降了CPU緩存的效率。bash
爲了可以保持32位的性能,oop必須保留32位。那麼,如何用32位oop來引用更大的堆內存呢?app
答案是壓縮指針(CompressedOops)。oop
JVM的實現方式是,再也不保存全部引用,而是每隔8個字節保存一個引用。例如,原來保存每一個引用0、一、2...,如今只保存0、八、16...。所以,指針壓縮後,並非全部引用都保存在堆中,而是以8個字節爲間隔保存引用。性能
在實現上,堆中的引用其實仍是按照0x0、0x一、0x2...進行存儲。只不過當引用被存入64位的寄存器時,JVM將其左移3位(至關於末尾添加3個0),例如0x0、0x一、0x2...分別被轉換爲0x0、0x八、0x10。而當從寄存器讀出時,JVM又能夠右移3位,丟棄末尾的0。(oop在堆中是32位,在寄存器中是35位,2的35次方=32G。也就是說,使用32位,來達到35位oop所能引用的堆內存空間)
在JVM中(無論是32位仍是64位),對象已經按8字節邊界對齊了。對於大部分處理器,這種對齊方案都是最優的。因此,使用壓縮的oop並不會帶來什麼損失,反而提高了性能。
Oracle JDK從6 update 23開始在64位系統上會默認開啓壓縮指針。
32位HotSpot VM是不支持UseCompressedOops參數的,只有64位HotSpot VM才支持。
對於大小在4G和32G之間的堆,應該使用壓縮的oop。
在VM啓動的時候,能夠設置 -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode 參數來確認壓縮指針的工做模式。
壓縮指針默認開啓:
$ java -server -Xms2G -Xmx2G -XX:+UseConcMarkSweepGC -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode -version
heap address: 0x000000077ae00000, size: 2130 MB, zero based Compressed Oops
java version "1.7.0_79"
Java(TM) SE Runtime Environment (build 1.7.0_79-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.79-b02, mixed mode)
複製代碼
壓縮指針默認開啓:
$ java -server -Xms2G -Xmx2G -XX:+UseConcMarkSweepGC -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode -version
heap address: 0x0000000080000000, size: 2048 MB, Compressed Oops mode: 32-bit
Narrow klass base: 0x0000000000000000, Narrow klass shift: 3
Compressed class space size: 1073741824 Address: 0x000000013fe20000 Req Addr: 0x0000000100000000
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
複製代碼
關閉壓縮指針:
$ java -server -Xms2G -Xmx2G -XX:+UseConcMarkSweepGC -XX:-UseCompressedOops -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode -version
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
複製代碼
測試環境:JDK 1.8.0_121
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
public class IntegerApplication {
public static void main(String[] args) {
List<Integer> intList = new LinkedList<>();
for (int i = 0; i < 2000000; i++) {
Integer number = new Integer(1);
intList.add(number);
}
Scanner scanner = new Scanner(System.in);
System.out.println("application is running...");
String tmp = scanner.nextLine();
System.exit(0);
}
}
複製代碼
先運行程序IntegerApplication,再經過mat查看對象分配狀況。
壓縮指針默認開啓(-XX:+UseCompressedOops)。
$ java IntegerApplication
application is running...
複製代碼
每一個Integer大小爲:
64(Mark Word)+32(Compressed oops)+32(int)=128bits=16bytes
全部Integer總大小爲:
2000256*16=32004096bytes
設置參數-XX:-UseCompressedOops,關閉壓縮指針。
$ java -XX:-UseCompressedOops IntegerApplication
application is running...
複製代碼
每一個Integer大小爲:
64(Mark Word)+64(Compressed oops)+32(int)=160bits=20bytes
因爲JVM內存分配須要根據字寬進行對齊,對於64位JVM,字寬爲8個字節。所以,一個Integer實際佔用24bytes,即192bits。
全部Integer總大小爲:
2000256*24=48006144bytes
經過上面的實例能夠看到,在開啓壓縮指針以後,oop大小確實是變成了32位,而且實際測試結果與理論分析是一致的。
參考
《Java性能權威指南》Scott Oaks
www.javacodegeeks.com/2016/05/com…
rednaxelafx.iteye.com/blog/101007…
更多文章,請關注公衆號:二進制之路