【问题标题】:Size of a byte in memory - Java内存中字节的大小 - Java
【发布时间】:2010-09-18 19:07:06
【问题描述】:

对于一个字节在 java 程序中占用的内存量,我听到了不同的意见。

我知道你可以在一个 java 字节中存储不超过 +127,documentation 说一个字节只有 8 位,但here 我被告知它实际上占用了与一个 int,因此只是一种有助于代码理解而不是效率的类型。

任何人都可以解决这个问题吗?这会是特定于实现的问题吗?

【问题讨论】:

  • 单个字节占用 4/8 个字节,具体取决于 cpu 架构,byte[] 中的一个字节恰好占用一个字节 + 对象头(+尾随对齐)
  • "我知道你在一个 java 字节中最多可以存储 +127" -- 从某种意义上说,这不是真的。您可以在一个字节中存储 256 个不同的值,因此您可以在其中存储超过 127 个:如果从 0 开始,最多可以存储 255 个。这完全取决于您如何处理这 8 个位。只是为了迂腐:P

标签: java performance memory


【解决方案1】:

好的,讨论很多,代码不多:)

这是一个快速基准测试。当涉及到这种事情时,它有一些正常的警告——由于 JITting 等原因,测试内存有一些奇怪的地方,但如果数量足够大,它还是很有用的。它有两种类型,每种类型有 80 个成员——LotsOfBytes 有 80 个字节,LotsOfInts 有 80 个整数。我们构建了很多,确保它们没有被 GC,并检查内存使用情况:

class LotsOfBytes
{
    byte a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af;
    byte b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf;
    byte c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf;
    byte d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, da, db, dc, dd, de, df;
    byte e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, ea, eb, ec, ed, ee, ef;
}

class LotsOfInts
{
    int a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af;
    int b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf;
    int c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf;
    int d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, da, db, dc, dd, de, df;
    int e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, ea, eb, ec, ed, ee, ef;
}


public class Test
{
    private static final int SIZE = 1000000;

    public static void main(String[] args) throws Exception
    {        
        LotsOfBytes[] first = new LotsOfBytes[SIZE];
        LotsOfInts[] second = new LotsOfInts[SIZE];

        System.gc();
        long startMem = getMemory();

        for (int i=0; i < SIZE; i++)
        {
            first[i] = new LotsOfBytes();
        }

        System.gc();
        long endMem = getMemory();

        System.out.println ("Size for LotsOfBytes: " + (endMem-startMem));
        System.out.println ("Average size: " + ((endMem-startMem) / ((double)SIZE)));

        System.gc();
        startMem = getMemory();
        for (int i=0; i < SIZE; i++)
        {
            second[i] = new LotsOfInts();
        }
        System.gc();
        endMem = getMemory();

        System.out.println ("Size for LotsOfInts: " + (endMem-startMem));
        System.out.println ("Average size: " + ((endMem-startMem) / ((double)SIZE)));

        // Make sure nothing gets collected
        long total = 0;
        for (int i=0; i < SIZE; i++)
        {
            total += first[i].a0 + second[i].a0;
        }
        System.out.println(total);
    }

    private static long getMemory()
    {
        Runtime runtime = Runtime.getRuntime();
        return runtime.totalMemory() - runtime.freeMemory();
    }
}

我的盒子上的输出:

Size for LotsOfBytes: 88811688
Average size: 88.811688
Size for LotsOfInts: 327076360
Average size: 327.07636
0

所以显然有一些开销 - 看起来有 8 个字节,尽管不知何故只有 7 个字节用于 lotofints(?就像我说的,这里有一些奇怪的地方) - 但关键是字节字段似乎是为lotsofbytes 打包的这样它(在去除开销之后)只需要LotsOfInts 的四分之一的内存。

【讨论】:

  • 这取决于 JVM。 Sun 对齐到 8 字节边界
  • @kohlerm:那是使用 Sun JVM。
  • 不错的测试,但是如果您使用class LotsOfBytes { byte a0; } class LotsOfInts { int a0; } 进行测试,则不会有任何显着差异
  • 请解释一下我的输出:Size for LotsOfBytes: -914712 Average size: -914.712 Size for LotsOfInts: 336000 Average size: 336.0 0
  • @mini-me:不知道 - 我需要了解更多上下文(您如何运行它等)。听起来你有一些东西正在被单独收集......
【解决方案2】:

是的,Java 中的字节变量实际上是内存中的 4 个字节。但是,这不适用于数组。一个 20 字节的字节数组的存储实际上只有 20 字节在内存中。

这是因为 Java 字节码语言只知道两种整数类型:int 和 long。所以它必须在内部将所有数字作为任一类型处理,这些类型在内存中分别为 4 和 8 个字节。

然而,Java 知道所有整数格式的数组。所以短数组的存储实际上是每个条目两个字节,字节数组每个条目一个字节。

我一直说“存储”的原因是,数组也是 Java 中的一个对象,每个对象都需要多个字节的存储空间,而不管实例变量的存储空间或数组存储空间,以防万一数组需要。

【讨论】:

  • 哦,是的,我忘了那个不那么小的细节!
  • 不要忘记字节数组也有作为对象的正常开销和长度。哦,你的变量就是一个引用(4 或 8 个字节)。因此,假设没有别名,实际上有 20 个字节可用和有用将需要 36 个字节。我会坚持使用 20 字节字段:)
  • @Jon @Mecki 你能给出或多或少精确的公式来计算int[] 数组的大小吗?会是4[=length] + 4[=int_size]*length(array) + 8_byte_align吗?
  • @dma_k:没有公式,因为它完全取决于虚拟机。数组或多或少是 Java 中的一个对象。一个对象可能有 20 个内部变量,仅用于 VM 管理,也可能没有这些变量。这个星球上不仅有 Sun 的 VM(更多)。一个 int[] 数组肯定至少是“4 * length(array)”并且有一些静态开销。开销可以是任何东西,从 4 字节到 xxx 字节;开销不取决于数组大小(int[1] 与 int[10000000] 具有相同的静态开销);因此开销对于大数组来说是微不足道的。
  • @Mecki 我在另一个线程中找到了这个链接;它满足了我的好奇心:kohlerm.blogspot.com/2008/12/…
【解决方案3】:

Java 从来都不是特定于实现或平台的(至少就primitive type sizes 而言)。无论您在什么平台上,它们的原始类型始终保证保持不变。这不同于(并且被认为是对)C 和 C++ 的改进,其中一些原始类型是特定于平台的。

由于底层操作系统一次处理四个(或 8 个,在 64 位系统中)字节更快,JVM 可能会分配更多字节来存储原始字节,但您仍然只能存储来自-128 到 127。

【讨论】:

  • 即使它使用 4 个字节来存储一个字节,也可能会打包一个字节数组。如果一个 byte[4] 使用 16 个字节而不是 4 个字节,我会感到惊讶。
  • 可能。这是特定于实现的。老实说,我不知道哪种方法会更快。
  • 文章是正确的,但评论是错误的。单个字节变量消耗 1 个字节 + 对齐。例如,Sun JVM 上的 8 字节变量需要 8 个字节
【解决方案4】:

一个有启发性的练习是在一些用字节和整数做简单事情的代码上运行javap。您将看到期望 int 参数对字节进行操作的字节码,以及插入的字节码以相互强制。

请注意,尽管字节数组不存储为 4 字节值的数组,因此 1024 长度的字节数组将使用 1k 内存(忽略任何开销)。

【讨论】:

    【解决方案5】:

    我使用http://code.google.com/p/memory-measurer/ 进行了测试 请注意,我使用的是 64 位 Oracle/Sun Java 6,没有对引用等进行任何压缩。

    每个对象都占用一些空间,加上JVM需要知道那个对象的地址,而“地址”本身就是8个字节。

    使用原语,看起来原语被转换为 64 位以获得更好的性能(当然!):

    byte: 16 bytes,
     int: 16 bytes,
    long: 24 bytes.
    

    使用数组:

    byte[1]: 24 bytes
     int[1]: 24 bytes
    long[1]: 24 bytes
    
    byte[2]: 24 bytes
     int[2]: 24 bytes
    long[2]: 32 bytes
    
    byte[4]: 24 bytes
     int[4]: 32 bytes
    long[4]: 48 bytes
    
    byte[8]: 24 bytes => 8 bytes, "start" address, "end" address => 8 + 8 + 8 bytes
     int[8]: 48 bytes => 8 integers (4 bytes each), "start" address, "end" address => 8*4 + 8 + 8 bytes
    long[8]: 80 bytes => 8 longs (8 bytes each), "start" address, "end" address => 8x8 + 8 + 8 bytes
    

    现在猜猜是什么......

        byte[8]: 24 bytes
     byte[1][8]: 48 bytes
       byte[64]: 80 bytes
     byte[8][8]: 240 bytes
    

    附: Oracle Java 6,最新最好的,64 位,1.6.0_37,MacOS X

    【讨论】:

      【解决方案6】:

      这取决于 JVM 如何应用填充等。字节数组(在任何健全的系统中)将被打包成每个元素 1 个字节,但是具有四个字节字段的类可以紧密打包或填充到单词边界 - 它依赖于实现。

      【讨论】:

      • 这是否意味着单独使用一个字节不会节省内存,但如果我要使用多个字节变量(或一个字节数组),我可以节省大量内存。 (即 byte[10][10] 可能/应该比 int[10][10] 占用更少的内存)
      • 可能 :) (当然我希望字节数组比 int 数组占用更少的空间 - 但是四个字节变量与四个 int 变量?不知道。)
      • (请参阅我的其他答案以获取至少一些 JVM 会打包的证据。)
      【解决方案7】:

      你被告知的是完全正确的。 Java字节码规范只有4字节类型和8字节类型。

      byte、char、int、short、boolean、float 都存储在每个 4 个字节中。

      double 和 long 存储在 8 个字节中。

      但是字节码只是故事的一半。还有 JVM,它是特定于实现的。 Java 字节码中有足够的信息来确定变量被声明为字节。 JVM 实现者可能决定只使用一个字节,尽管我认为这不太可能。

      【讨论】:

      • Hmm... 这似乎与java.sun.com/docs/books/jvms/second_edition/html/… 背道而驰:“Java 虚拟机的整数类型的值与 Java 编程语言的整数类型的值相同(第 2.4 节) .1)"(现在正在寻找字节码的东西......)
      • 其实它也有数组,字节数组其实就是字节数组,每个字节都是一个字节
      • 是的。但是 Java 堆栈被定义为一系列 4 字节的槽。压入堆栈总是使用一个(对于 4 字节类型)或两个(对于 8 字节类型)元素。 bipush 将使用一个插槽。
      • JVM当然知道什么时候字段是字节字段而不是int字段,不是吗?它可能会选择不将它们紧紧地打包,但这肯定是一个实施决定。
      • 即使 Java stack 是基于 int 的,但这并不意味着它的对象布局必须如此。我正在制定一个基准...
      【解决方案8】:

      您始终可以使用 long 并将数据打包在自己的内部以提高效率。然后你总是可以保证你将使用所有 4 个字节。

      【讨论】:

      • 或者甚至全部 8 个字节,在一个很长的 :) 中
      • 如果您实际上正在考虑这种类型的内存管理,我认为您可能应该使用 C++ 或其他可以让您自己进行内存管理的语言。在 JVM 的开销上损失的比在 Java 中通过这样的技巧所节省的要多得多。
      • 啊。在 32 位系统上的 C/C++ 中,int 和 long 都是 32 位或 4 字节;我忘记了 long 在其他系统上实际上是 long ——当他们添加“longlong”来表示 8 字节长时总是让我发笑……嗯。
      • 您可以获得性能,因为您可以使用 int 一次处理 4 个字节,而不是因为您节省内存(通常会丢失)您不需要打包 byte[]。您需要避免对象中的单字节字段,因为对齐会增加内存开销
      【解决方案9】:

      byte = 8bit = Java 规范定义的一个字节。

      字节数组需要多少内存没有由规范定义,也没有定义复杂对象需要多少。

      对于 Sun JVM,我记录了规则:https://www.sdn.sap.com/irj/sdn/weblogs?blog=/pub/wlg/5163

      【讨论】:

        【解决方案10】:

        在我的网站 (www.csd.uoc.gr/~andreou) 上查看我的 MonitoringTools

        X类{ 字节 b1, b2, b3 ...; } long memoryUsed = MemoryMeasurer.measure(new X());

        (也可以用于更复杂的对象/对象图)

        在 Sun 的 1.6 JDK 中,似乎一个字节确实需要一个字节(在旧版本中,int ~ byte 就内存而言)。但请注意,即使在旧版本中,byte[] 也被打包为每个条目一个字节。

        无论如何,关键是不需要像上面 Jon Skeet 那样的复杂测试,它只会给出估计。我们可以直接测量物体的大小!

        【讨论】:

          【解决方案11】:

          看了以上的cmets,看来我的结论会出乎很多人的意料(对我来说也是个意外),所以值得重复一下:

          • 变量的旧 size(int) == size(byte) 不再适用,至少在 Sun 的 Java 6 中如此。

          相反,大小(字节)== 1 字节(!!)

          【讨论】:

            【解决方案12】:

            只是想指出声明

            你可以在一个 java 字节中存储不超过 +127

            并不真正正确。

            您始终可以在一个字节中存储 256 个不同的值,因此您可以轻松地将 0..255 范围视为“无符号”字节。

            这完全取决于您如何处理这 8 位。

            例子:

            byte B=(byte)200;//B contains 200
            System.out.println((B+256)%256);//Prints 200
            System.out.println(B&0xFF);//Prints 200
            

            【讨论】:

              【解决方案13】:

              似乎答案可能取决于您的 JVM 版本,也可能取决于您运行的 CPU 架构。 Intel 系列 CPU 高效地执行字节操作(由于其 8 位 CPU 历史)。一些 RISC 芯片需要对许多操作进行字(4 字节)对齐。对于堆栈上的变量、类中的字段和数组中的变量,内存分配可能不同。

              【讨论】:

                猜你喜欢
                • 2012-04-20
                • 2020-02-13
                • 2019-09-24
                • 2016-04-24
                • 1970-01-01
                • 2016-07-03
                • 1970-01-01
                • 2013-05-19
                • 2019-09-18
                相关资源
                最近更新 更多