当为(例如)一个集合创建支持数组时,您实际上并不在乎所创建数组的确切大小,它只需要至少与您计算的大小一样大即可。
但是由于有了内存分配和VM的数组标头,在某些情况下就可以创建更大的数组而不消耗更多的内存-对于Oracle 32位VM(至少就是互联网上的几个来源所声称的),内存粒度为8(意味着任何内存分配都将舍入到下一个8字节边界),并且数组头的开销为12字节。
这意味着在分配Object [2]时,应该消耗20个字节(12 + 2 * 4),但是由于粒度的原因,实际上将占用24个字节。以相同的内存成本创建Object [3]是可能的,这意味着一个集合将不得不稍后再调整其支持数组的大小。相同的原理可以应用于原始数组,例如,用于I / O缓冲区的byte [],字符串生成器中的char []等。
尽管这样的优化不会真正产生明显的效果,除非在最极端的情况下,调用静态方法来“优化”数组大小不会有太大的麻烦。
问题是,在JDK中没有这样的“到内存粒度为止的舍入数组大小”。我自己编写这种方法将需要确定VM的一些关键参数:内存粒度,数组标头开销以及每种类型的大小(主要是引用问题,因为它们的大小会随体系结构和VM选项而变化)。
那么,有没有一种方法可以确定这些参数,或者通过其他方式实现所需的“舍入”?
有趣的主意。我认为确定此问题的更便捷的方法是实际衡量使用情况。示例程序:
public class FindMemoryUsage { public static void main(String[] args) { for (int i=0; i<50; i+=2) { long actual = getActualUsageForN(i); System.out.println(i + " = " + actual); long theoretical = getTheoreticalUsageForN(i); if (theoretical != actual) { throw new RuntimeException("Uh oh! Mismatch!"); } } } private static long getTheoreticalUsageForN(long count) { long optimal = (Unsafe.ARRAY_BYTE_BASE_OFFSET + Unsafe.ARRAY_BYTE_INDEX_SCALE * count); return ((optimal - 1) & ~7) + 8; } private static long getActualUsageForN(int count) { System.gc(); byte[][] arrays = new byte[3000000][]; long begin = usedMemory(); for (int i=0; i<arrays.length; i++) { arrays[i] = new byte[count]; } long end = usedMemory(); return Math.round((end - begin) / (double) arrays.length); } private static long usedMemory() { return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); } }
该程序为您提供此信息:
0 = 16 2 = 16 4 = 16 6 = 24 8 = 24 10 = 24 12 = 24 14 = 32 16 = 32 18 = 32 20 = 32 22 = 40 24 = 40 26 = 40 28 = 40 30 = 48 32 = 48 34 = 48 36 = 48 38 = 56 40 = 56 42 = 56 44 = 56 46 = 64 48 = 64
该数据来自使用率的实际计算和基于sun.misc.Unsafe的常数和8字节舍入的理论使用率。这意味着您可以像建议的那样使用这些常量来“四舍五入”:
sun.misc.Unsafe
private static int roundSizeUp(int from) { long size = (Unsafe.ARRAY_BYTE_BASE_OFFSET + Unsafe.ARRAY_BYTE_INDEX_SCALE * from); long actual = ((size - 1) & ~7) + 8; return (int) (actual - Unsafe.ARRAY_BYTE_BASE_OFFSET) / Unsafe.ARRAY_BYTE_INDEX_SCALE; }
这是特定于VM的代码,但是getActualUsageForN如果需要更多的可移植性,您可能可以根据该策略找到如何执行此操作。
getActualUsageForN
请注意,这不是生产质量的代码:您需要仔细考虑溢出,并将Unsafe引用更改为实际上适用于所使用数组类型的常量。
Unsafe