一、概念
- 在64位平台的HotSpot中使用32位指针,内存使用会多出1.5倍左右,使用较大指针在主内存和缓存之间移动数据,占用较大宽带,同时GC也会承受较大压力
- 为了减少64位平台下内存的消耗,启用指针压缩功能
- 堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址
以上内容摘自ignorewho的博客。这里提到了32G这个临界值,那么,我们的程序应该设置多大的堆内存,才能恰好避开指针压缩失效,而且最大限度的使用内存呢?下面我们开始实验探索。
二、代码
测试程序的思路很简单。我们既然要测试的其实就是不同内存下引用的大小,单个引用是很小的,可以通过量级来减少误差。那么约束条件也就有了:
- 足够多的引用(即足够多的对象)
- 统计前不要发生gc
程序设计如下:
import java.util.LinkedList;
import java.util.List;
public class Main {
public static void main(String[] args) throws InterruptedException {
List<Integer> list = new LinkedList<>();
int total = Integer.parseInt(args[0]) * 10000;
for (int i = 0; i < total; i++) {
list.add(i);
}
System.out.println(list.size());
// 留给观察和截图的时间
Thread.sleep(5000);
// 维持一个引用
System.out.println(list.size());
}
}
三、测试
1. 试试看32g
执行命令java -Xms32g -Xmx32g -jar test-memory.jar 1000
可以看到,32g堆内存下,包含1000万个Integer元素的LinkedList共占用内存727.8MB。但此时我们并不知道jvm是否采用了指针压缩策略,因为我们缺少一个参照物。
2. 参照物来了,31g
执行命令java -Xms31g -Xmx31g -jar test-memory.jar 1000
果然如我们所料!内存只占用了495.5MB,整整下降了232.3MB!下降幅度达32%,但如果反过来说,从31g到32g,内存占用则上升了47%!当然,由于本例采用LinkedList,属于引用多但对象小,所以比正常的程序要更明显,但也足以说明问题了。所以我们目前的结论是,在堆内存32g整的时候,jvm的指针压缩就已经失效了。
那么实验到这里就结束了?当然不。31g到32g之间还有整整1g、1024k、1048576b的内存,怎么可能就这么放弃了,我们继续。
3. 32g - 1m = 32767m
执行命令java -Xms332767m -Xmx332767m -jar test-memory.jar 1000
看到这个实验结果可能我们有些失望,32767m下占用的内存和32g下竟然如出一辙——727.8M。到这里我们可能有一些疑问了,不是说32g以上指针压缩才会失效吗,为什么32g-1m的情况下依然失效了?但换个思路,这也说明,实验进行到这里,我们已经不虚此行了,所谓的32g临界点,并不是一个确切的值。
那么我们如何处理这种情况?还要1m这样递减下去吗?是的。哈哈,开个玩笑。但是由于一时没有太好的思路,我们倒不妨再试一次,如果结果再没有变化,我们再去探索其他的办法。
4. 没有什么是减1m不能解决的,如果有…
执行命令java -Xms332766m -Xmx332766m -jar test-memory.jar 1000
成了!可以看到,在332766m(32g - 2m)下,内存占用回到了498.8M,指针压缩又回来了!写到这里我懒得写了,就这样吧。希望小伙伴们可以亲自继续探索一下背后的奥秘,遇到困难可以留言或者私聊我~
四、最后
这篇博客不属于严格的技术博客,抛弃了jvm源码分析,也基本没有引用官方说明。它的目的和意义仅在于给初学者一个易懂易操作的示例流程,当然,如果能激发起继续探索的兴趣那就更好了。想要继续探索的小伙伴可以先看下这篇博客。