JVM之壓縮指針(CompressedOops)

收藏待读

https://juejin.im/post/5c4c8ad9f265da6179752b03

對於32位機器,進程能使用的最大內存是4G。如果進程需要使用更多的內存,需要使用64位機器。

對於Java進程,在oop只有32位時,只能引用4G內存。因此,如果需要使用更大的堆內存,需要部署64位JVM。這樣,oop為64位,可引用的堆內存就更大了。

註:oop(ordinary object pointer),即普通對象指針,是JVM中用於代表引用對象的句柄。

在堆中,32位的對象引用佔4個位元組,而64位的對象引用佔8個位元組。也就是說,64位的對象引用大小是32位的2倍。

64位JVM在支持更大堆的同時,由於對象引用變大卻帶來了性能問題:

  1. 增加了GC開銷

64位對象引用需要佔用更多的堆空間,留給其他數據的空間將會減少,從而加快了GC的發生,更頻繁的進行GC。

  1. 降低CPU緩存命中率

64位對象引用增大了,CPU能緩存的oop將會更少,從而降低了CPU緩存的效率。

為了能夠保持32位的性能,oop必須保留32位。那麼,如何用32位oop來引用更大的堆內存呢?

答案是壓縮指針(CompressedOops)。

JVM的實現方式是,不再保存所有引用,而是每隔8個位元組保存一個引用。例如,原來保存每個引用0、1、2…,現在只保存0、8、16…。因此,指針壓縮後,並不是所有引用都保存在堆中,而是以8個位元組為間隔保存引用。

在實現上,堆中的引用其實還是按照0x0、0x1、0x2…進行存儲。只不過當引用被存入64位的寄存器時,JVM將其左移3位(相當於末尾添加3個0),例如0x0、0x1、0x2…分別被轉換為0x0、0x8、0x10。而當從寄存器讀出時,JVM又可以右移3位,丟棄末尾的0。(oop在堆中是32位,在寄存器中是35位,2的35次方=32G。也就是說,使用32位,來達到35位oop所能引用的堆內存空間)

JVM之壓縮指針(CompressedOops)

在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 參數來確認壓縮指針的工作模式。

JDK 7

壓縮指針默認開啟:

$ 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)
複製代碼

JDK 8

壓縮指針默認開啟:

$ 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 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);
	}
}
複製代碼

使用Eclipse Memory Analyzer查看Integer對象數量與大小

先運行程序IntegerApplication,再通過mat查看對象分配情況。

JVM之壓縮指針(CompressedOops)
JVM之壓縮指針(CompressedOops)

在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 參數來確認壓縮指針的工作模式。

JDK 7

壓縮指針默認開啟:

$ 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)
複製代碼

JDK 8

壓縮指針默認開啟:

$ 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 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);
	}
}
複製代碼

使用Eclipse Memory Analyzer查看Integer對象數量與大小

先運行程序IntegerApplication,再通過mat查看對象分配情況。

JVM之壓縮指針(CompressedOops)
JVM之壓縮指針(CompressedOops)
JVM之壓縮指針(CompressedOops)

開啟壓縮指針

壓縮指針默認開啟(-XX:+UseCompressedOops)。

$ java IntegerApplication
application is running...
複製代碼
JVM之壓縮指針(CompressedOops)

每個Integer大小為:

64(Mark Word)+32(Compressed oops)+32(int)=128bits=16bytes

所有Integer總大小為:

2000256*16=32004096bytes

關閉壓縮指針

設置參數-XX:-UseCompressedOops,關閉壓縮指針。

開啟壓縮指針

壓縮指針默認開啟(-XX:+UseCompressedOops)。

$ java IntegerApplication
application is running...
複製代碼
JVM之壓縮指針(CompressedOops)

每個Integer大小為:

64(Mark Word)+32(Compressed oops)+32(int)=128bits=16bytes

所有Integer總大小為:

2000256*16=32004096bytes

關閉壓縮指針

設置參數-XX:-UseCompressedOops,關閉壓縮指針。

$ java -XX:-UseCompressedOops IntegerApplication
application is running...
複製代碼
JVM之壓縮指針(CompressedOops)

每個Integer大小為:

64(Mark Word)+64(Compressed oops)+32(int)=160bits=20bytes

由於JVM內存分配需要根據字寬進行對齊,對於64位JVM,字寬為8個位元組。因此,一個Integer實際佔用24bytes,即192bits。

所有Integer總大小為:

2000256*24=48006144bytes

通過上面的實例可以看到,在開啟壓縮指針之後,oop大小確實是變成了32位,並且實際測試結果與理論分析是一致的。

相關閱讀

免责声明:本文内容来源于稀土掘金,已注明原文出处和链接,文章观点不代表立场,如若侵犯到您的权益,或涉不实谣言,敬请向我们提出检举。