繁体   English   中英

Java中对象的内存消耗是多少?

[英]What is the memory consumption of an object in Java?

一个有100个属性的对象消耗的内存空间和100个对象每个有一个属性消耗的内存空间是一样的吗?

为一个对象分配了多少内存?
添加属性时使用了多少额外空间?

Mindprod指出,这不是一个可以直接回答的问题:

JVM 可以自由地在内部以任何它喜欢的方式存储数据,大端或小端,具有任意数量的填充或开销,但原语必须表现得好像它们具有官方大小一样。
例如,JVM 或本机编译器可能决定将boolean[]存储在 64 位长块中,如BitSet 它不必告诉您,只要程序给出相同的答案即可。

  • 它可能会在堆栈上分配一些临时对象。
  • 它可能会优化一些完全不存在的变量或方法调用,用常量替换它们。
  • 它可能会版本方法或循环,即编译一个方法的两个版本,每个版本针对特定情况进行优化,然后预先决定调用哪个版本。

当然,硬件和操作系统有多层缓存,片上缓存、SRAM 缓存、DRAM 缓存、普通 RAM 工作集和磁盘后备存储。 您的数据可能会在每个缓存级别重复。 所有这些复杂性意味着您只能非常粗略地预测 RAM 消耗。

测量方法

您可以使用Instrumentation.getObjectSize()获取对象消耗的存储空间的估计值。

要可视化实际的对象布局、封装和引用,您可以使用JOL(Java 对象布局)工具

对象头和对象引用

在现代 64 位 JDK 中,对象具有 12 字节的标头,填充为 8 字节的倍数,因此最小对象大小为 16 字节。 对于 32 位 JVM,开销为 8 字节,填充为 4 字节的倍数。 (从梅德Spikhalskiy的回答Jayen的回答,和JavaWorld的。)

通常,引用在 32 位平台或 64 位平台上为 4 个字节,最高可达-Xmx32G 和 8 字节以上 32Gb ( -Xmx32G )。 (请参阅 压缩对象引用。)

因此,64 位 JVM 通常需要多 30-50% 的堆空间。 我应该使用 32 位还是 64 位 JVM? ,2012,JDK 1.7)

盒装类型、数组和字符串

与原始类型(来自JavaWorld )相比,盒装包装器具有开销:

  • Integer :16 字节的结果比我预期的要差一点,因为int值只能容纳 4 个额外的字节。 与可以将值存储为原始类型相比,使用Integer花费了 300% 的内存开销

  • Long :16 字节也:显然,堆上的实际对象大小受制于由特定 CPU 类型的特定 JVM 实现完成的低级内存对齐。 看起来Long是 8 个字节的 Object 开销,加上实际 long 值的 8 个字节。 相比之下, Integer有一个未使用的 4 字节空洞,很可能是因为我使用的 JVM 强制在 8 字节字边界上对齐对象。

其他容器也很昂贵:

  • 多维数组:它提供了另一个惊喜。
    开发人员通常在数值和科学计算中使用像int[dim1][dim2]

    int[dim1][dim2]数组实例中,每个嵌套的int[dim2]数组本身就是一个Object 每个都会增加通常的 16 字节数组开销。 当我不需要三角形或参差不齐的数组时,这表示纯粹的开销。 当数组维度差异很大时,影响会增加。

    例如,一个int[128][2]实例占用 3,600 字节。 int[256]实例使用的 1,040 字节(具有相同容量)相比,3,600 字节代表 246% 的开销。 byte[256][1]的极端情况下,开销因子几乎是 19! 将其与相同语法不增加任何存储开销的 C/C++ 情况进行比较。

  • StringString的内存增长跟踪其内部字符数组的增长。 但是, String类又增加了 24 个字节的开销。

    对于大小为 10 个字符或更少的非空String ,相对于有用负载(每个字符 2 个字节加上长度为 4 个字节)的额外开销成本在 100% 到 400% 之间。

结盟

考虑这个示例对象

class X {                      // 8 bytes for reference to the class definition
   int a;                      // 4 bytes
   byte b;                     // 1 byte
   Integer c = new Integer();  // 4 bytes for a reference
}

一个简单的总和表明X的实例将使用 17 个字节。 但是,由于对齐(也称为填充),JVM 以 8 字节的倍数分配内存,因此它会分配 24 字节而不是 17 字节。

这取决于架构/jdk。 对于现代 JDK 和 64 位体系结构,对象具有 12 字节的标头和 8 字节的填充 - 因此最小对象大小为 16 字节。 您可以使用称为Java 对象布局的工具来确定大小并获取有关任何实体的对象布局和内部结构的详细信息,或者通过类引用来猜测此信息。 我的环境中 Integer 的输出示例:

Running 64-bit HotSpot VM.
Using compressed oop with 3-bit shift.
Using compressed klass with 3-bit shift.
Objects are 8 bytes aligned.
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

java.lang.Integer object internals:
 OFFSET  SIZE  TYPE DESCRIPTION                    VALUE
      0    12       (object header)                N/A
     12     4   int Integer.value                  N/A
Instance size: 16 bytes (estimated, the sample instance is not available)
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

因此,对于 Integer,实例大小为 16 字节,因为 4 字节 int 紧接在标头之后和填充边界之前压缩到位。

代码示例:

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.util.VMSupport;

public static void main(String[] args) {
    System.out.println(VMSupport.vmDetails());
    System.out.println(ClassLayout.parseClass(Integer.class).toPrintable());
}

如果您使用 maven,要获得 JOL:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.3.2</version>
</dependency>

每个对象对其关联的监视器和类型信息以及字段本身都有一定的开销。 除此之外,几乎可以按照 JVM 认为合适的方式布置字段(我相信) - 但如另一个答案所示,至少一些JVM 会打包得相当紧。 考虑这样一个类:

public class SingleByte
{
    private byte b;
}

对比

public class OneHundredBytes
{
    private byte b00, b01, ..., b99;
}

在 32 位 JVM 上,我希望SingleByte 100 个实例占用 1200 个字节(由于填充/对齐,8 个字节的开销 + 4 个字段用于该字段)。 我希望OneHundredBytes一个实例占用 108 个字节 - 开销,然后是 100 个字节,打包。 不过它肯定会因 JVM 而异——一种实现可能决定不将字段打包在OneHundredBytes ,导致它占用 408 字节(= 8 字节开销 + 4 * 100 对齐/填充字节)。 在 64 位 JVM 上,开销也可能更大(不确定)。

编辑:请参阅下面的评论; 显然 HotSpot SingleByte到 8 个字节的边界而不是 32 个,因此SingleByte每个实例将占用 16 个字节。

无论哪种方式,“单个大对象”至少与多个小对象一样有效 - 对于像这样的简单情况。

程序的总已用/空闲内存可以通过以下方式在程序中获得

java.lang.Runtime.getRuntime();

运行时有几个与内存相关的方法。 下面的编码示例演示了它的用法。

 public class PerformanceTest {
     private static final long MEGABYTE = 1024L * 1024L;

     public static long bytesToMegabytes(long bytes) {
         return bytes / MEGABYTE;
     }

     public static void main(String[] args) {
         // I assume you will know how to create an object Person yourself...
         List <Person> list = new ArrayList <Person> ();
         for (int i = 0; i <= 100_000; i++) {
             list.add(new Person("Jim", "Knopf"));
         }

         // Get the Java runtime
         Runtime runtime = Runtime.getRuntime();

         // Run the garbage collector
         runtime.gc();

         // Calculate the used memory
         long memory = runtime.totalMemory() - runtime.freeMemory();
         System.out.println("Used memory is bytes: " + memory);
         System.out.println("Used memory is megabytes: " + bytesToMegabytes(memory));
     }
 }

似乎每个对象在 32 位系统上都有 16 字节的开销(在 64 位系统上是 24 字节)。

http://algs4.cs.princeton.edu/14analysis/是一个很好的信息来源。 以下是众多优秀示例中的一个示例。

在此处输入图片说明

http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf也非常有用,例如:

在此处输入图片说明

一个有100个属性的对象消耗的内存空间和100个对象每个有一个属性消耗的内存空间是一样的吗?

不。

为一个对象分配了多少内存?

  • 开销是32位8字节,64位12字节; 然后四舍五入为 4 字节(32 位)或 8 字节(64 位)的倍数。

添加属性时使用了多少额外空间?

  • 属性的范围从1个字节(字节)到8个字节(长/双),但引用或者取决于-Xmx无论它是32位或64位,而不论是<32Gb的或> = 32Gb的4个字节或8个字节:典型64 -bit JVM 有一个称为“-UseCompressedOops”的优化,如果堆低于 32Gb,它会将引用压缩到 4 个字节。

这个问题将是一个非常广泛的问题。

这取决于类变量,或者您可以在 java 中调用作为状态内存使用情况。

它还对标头和引用有一些额外的内存要求。

Java 对象使用的堆内存包括

  • 原始字段的内存,根据它们的大小(参见下面的原始类型的大小);

  • 用于参考字段的内存(每个 4 个字节);

  • 一个对象头,由几个字节的“内务处理”信息组成;

Java 中的对象还需要一些“内务处理”信息,例如记录对象的类、ID 和状态标志,例如对象当前是否可达、当前是否同步锁定等。

Java 对象标头大小在 32 位和 64 位 jvm 上有所不同。

虽然这些是主要的内存消费者,但 jvm 有时也需要额外的字段,例如代码对齐等

原始类型的大小

布尔值和字节-- 1

字符和短- 2

整数和浮点数-- 4

长双倍-- 8

不,注册一个对象也需要一些内存。 具有 1 个属性的 100 个对象将占用更多内存。

我从另一个答案中提到的java.lang.instrument.Instrumentation方法中获得了非常好的结果。 有关其使用的良好示例,请参阅 JavaSpecialists' Newsletter 中的条目Instrumentation Memory Counter和 SourceForge 上的java.sizeOf库。

如果它对任何人有用,您可以从我的网站下载一个用于查询对象内存使用情况的小型Java 代理 它还可以让您查询“深度”内存使用情况。

不,100 个小物体比一个大物体需要更多的信息(内存)。

有关消耗多少内存的规则取决于 JVM 实现和 CPU 架构(例如 32 位与 64 位)。

SUN JVM的详细规则查看我的旧博客

问候,马库斯

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM