根据JVM的内存粒度确定数组的最佳大小

为(例如)集合创建支持数组时,您并不真正关心所创建数组的确切大小,它只需要至少与您计算的一样大。

但是由于内存分配和VM的数组头,在某些情况下可以创建一个更大的arrays而不消耗更多的内存 – 对于Oracle 32位VM(至少这是互联网上的几个来源声称),内存粒度为8(意味着任何内存分配向上舍入到下一个8字节边界),并且数组头开销为12个字节。

这意味着在分配Object [2]时,它应该消耗20个字节(12 + 2 * 4),但由于粒度,它实际上需要24个字节。 可以为相同的内存成本创建一个Object [3],这意味着集合必须稍后调整其后备arrays的大小。 相同的原理可以应用于primitve数组,例如用于I / O缓冲区的byte [],字符串生成器中的char []等。

虽然这种优化不会产生明显的效果,但在最极端的情况下,调用静态方法来“优化”数组大小并不会太麻烦。

问题是,JDK中没有这样的“圆形arrays大小直至内存粒度”。 并且自己编写这样的方法需要确定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 

该程序为您提供以下信息:

 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字节舍入的理论用法。 这意味着你可以像你建议的那样使用这些常量“向上舍入”:

 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策略执行此操作。

请注意,这不是生产质量的代码:您需要仔细考虑溢出并将Unsafe引用更改为实际应用于您正在使用的数组类型的常量。

当动态大小的集合增加其后备arrays的大小时,它们不会向其大小添加少量,它们会按比例增加。 加倍是一种常见的选择。 他们这样做是因为它提供了更好的性能。 你建议的微小调整是不值得的。