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


216

具有100个属性的一个对象所消耗的存储空间是否与每个具有一个属性的100个对象所消耗的存储空间相同?

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

Answers:


180

Mindprod指出,这不是一个容易回答的问题:

JVM可以自由地以内部或大端或小端的任何方式存储数据,并具有任意填充量或开销,尽管基元必须表现得好像它们具有官方大小一样。
例如,JVM或本机编译器可能决定将a存储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位平台(不超过-Xmx32G;)上为4个字节。和32Gb(-Xmx32G)以上的8个字节。 (请参阅压缩的对象引用。)

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

装箱的类型,数组和字符串

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

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

  • Long:也为16个字节:显然,堆上的实际对象大小受特定JVM实现针对特定CPU类型的低级内存对齐的约束。看起来Long是8个字节的对象开销,再加上8个字节的实际long值。相反,Integer有一个未使用的4字节漏洞,这很可能是因为我使用的JVM在8字节字边界上强制了对象对齐。

其他容器也很昂贵:

  • 多维数组:它提供了另一个惊喜。
    开发人员通常采用int[dim1][dim2]数字和科学计算中的构造。

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

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

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

    对于String大小为10个字符或更少的非空字符,相对于有用的有效负载而言,增加的开销成本(每个字符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字节。


int [128] [6]:128个数组,共6个整数-总共768个整数,数据3072字节+ 2064字节对象开销=总计5166字节。int [256]:总共256个int,因此不可比。int [768]:3072字节的数据+ 16 byes的开销-约为2D数组空间的3/5-开销不是246%!
JeeBee

嗯,原始文章使用的是int [128] [2]而不是int [128] [6]-想知道它是如何改变的。还表明极端的例子可以讲一个不同的故事。
JeeBee

2
在64位JVM中,开销为16个字节。
蒂姆·库珀

3
@AlexWien:某些垃圾收集方案可能会强加一个最小的对象大小,该大小与填充是分开的。在垃圾回收期间,一旦将对象从旧位置复制到新位置,旧位置可能不再需要保存该对象的数据,但是它将需要保留对新位置的引用;它可能还需要存储对发现了第一个参考的对象的旧位置的引用以及该参考在旧对象中的偏移量(因为旧对象可能仍包含尚未处理的引用)。
supercat 2014年

2
@AlexWien:在对象的旧位置使用内存来保存垃圾收集器的簿记信息避免了为此目的分配其他内存的需要,但是可能会强加一个最小的对象大小,该大小大于否则需要的大小。我认为至少有一个.NET垃圾收集器版本使用该方法。某些Java垃圾收集器当然也有可能这样做。
supercat 2014年

34

这取决于体系结构/ jdk。对于现代JDK和64位体系结构,对象具有12字节的标头和8字节的填充-因此最小对象大小为16字节。您可以使用一个名为Java Object Layout的工具来确定大小并获取有关任何实体的对象布局和内部结构的详细信息,或者通过类引用猜测此信息。我的环境中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>

28

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

public class SingleByte
{
    private byte b;
}

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

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

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

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


9
其实,单字节的一个实例将采取在Sun JVM,即8个字节的开销,4个字节场地16个字节,然后4物体填充字节,因为热点编译回合一切为8的倍数
保罗Wagland

6

可以通过以下方式在程序中获取程序的已使用/可用内存总量:

java.lang.Runtime.getRuntime();

运行时有几种与内存有关的方法。下面的编码示例演示其用法。

package test;

 import java.util.ArrayList;
 import java.util.List;

 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 a object Person yourself...
         List < Person > list = new ArrayList < Person > ();
         for (int i = 0; i <= 100000; 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));
     }
 }

6

看起来每个对象在32位系统上的开销为16字节(在64位系统上的开销为24字节)。

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

在此处输入图片说明

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

在此处输入图片说明


“看来,每个对象在32位系统上的开销为16字节(在64位系统上的开销为24字节)。” 至少对于当前的JDK,这是不正确的。看看我对整数示例的回答。对于64位系统和现代JDK,标头的对象开销至少为12个字节。可能由于填充而更多,取决于对象中字段的实际布局。
德米特里·史皮卡斯基

内存效率高的Java教程的第二个链接似乎已失效–我收到了“禁止访问”。
tsleyson

6

具有100个属性的一个对象所消耗的存储空间是否与每个具有一个属性的100个对象所消耗的存储空间相同?

没有。

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

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

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

  • 属性的范围从1个字节(字节)到8个字节(长/双),但是参考文献是要么根据4个字节或8个字节上-Xmx它是否是32位或64位,而不论是<32Gb的或> = 32Gb的:典型64位JVM的优化称为“ -UseCompressedOops”,如果堆低于32Gb,该引用会将引用压缩为4个字节。

1
字符是16位,而不是8位。
comonad

你是对的。有人似乎已经编辑了我的原始答案
Jayen

5

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


4

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

它取决于类变量,或者您可以在Java中将其称为状态内存使用情况。

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

Java对象使用的堆内存包括

  • 根据原始字段的大小存储原始字段(请参阅下面的原始类型的大小);

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

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

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

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

尽管这些是主要的内存使用者,jvm有时也需要其他字段,例如代码对齐等。

基本类型的大小

布尔值和字节 -1

字符和短 -2

整数和浮点数 -4

长双 -8


:读者也可能发现本文中很有启发cs.virginia.edu/kim/publicity/pldi09tutorials/...
quellish



1

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


0

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

有关SUN JVM的详细规则,请查看我的旧博客。

问候, 马库斯


我很确定Sun Java 1.6 64位,需要12个字节用于纯对象+ 4个填充= 16;一个对象+一个整数字段= 12 + 4 = 16
AlexWien

您是否关闭了博客?
JohanBoulé

并非如此,不确定SAP博客是否有所改变。它的大部分可以在这里找到kohlerm.blogspot.com
kohlerm
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.