Answers:
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 ++情况进行比较,在这种情况下,相同的语法不会增加任何存储开销。
String
:String
的内存增长跟踪其内部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字节。
这取决于体系结构/ 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>
每个对象及其关联的监视器和类型信息以及字段本身都有一定的开销。除此之外,尽管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个字节。
无论哪种方式,“单个大对象”至少将与多个小对象一样有效-在这种简单情况下。
可以通过以下方式在程序中获取程序的已使用/可用内存总量:
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));
}
}
看起来每个对象在32位系统上的开销为16字节(在64位系统上的开销为24字节)。
http://algs4.cs.princeton.edu/14analysis/是很好的信息来源。以下是许多好例子中的一个例子。
http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficiency-java-tutorial.pdf也非常有用,例如:
具有100个属性的一个对象所消耗的存储空间是否与每个具有一个属性的100个对象所消耗的存储空间相同?
没有。
为一个对象分配多少内存?
添加属性时会使用多少额外空间?
这个问题将是一个非常广泛的问题。
它取决于类变量,或者您可以在Java中将其称为状态内存使用情况。
它还对标头和引用有一些额外的内存要求。
Java对象使用的堆内存包括
根据原始字段的大小存储原始字段(请参阅下面的原始类型的大小);
用于参考字段的存储器(每个4字节);
对象标头,由几个字节的“内务”信息组成;
Java中的对象还需要一些“内务处理”信息,例如记录对象的类,ID和状态标志,例如该对象当前是否可访问,当前同步锁定等。
Java对象标头的大小在32位和64位jvm上有所不同。
尽管这些是主要的内存使用者,jvm有时也需要其他字段,例如代码对齐等。
基本类型的大小
布尔值和字节 -1
字符和短 -2
整数和浮点数 -4
长双 -8
我从另一个答案中提到的java.lang.instrument.Instrumentation方法获得了很好的结果。有关其用法的良好示例,请参阅JavaSpecialists的新闻稿中的Instrumentation Memory Counter条目和SourceForge上的java.sizeOf库。
如果它对任何人都有用,则可以从我的网站上下载一个小型Java代理,以查询对象的内存使用情况。它还可以让您查询“深度”内存使用情况。
(String, Integer)
Guava缓存每个元素使用多少内存非常有用。谢谢!