1. Overview
1.概述
Unlike C/C++ where we can use sizeof() method to get an object size in bytes, there’s no true equivalent of such method in Java.
与C/C++不同,我们可以使用sizeof()方法来获取对象的字节大小,但在Java中没有真正意义上的对应方法。
In this article, we’ll demonstrate how we can still get the size of a particular object.
在这篇文章中,我们将演示如何仍然可以获得某个特定对象的大小。
2. Memory Consumption in Java
2.Java中的内存消耗
Although there is no sizeof operator in Java, we actually don’t need one. All primitive types have a standard size, and there are typically no pad or alignment bytes. Still, this isn’t always straightforward.
虽然Java中没有sizeof操作符,但实际上我们不需要。所有的原始类型都有一个标准的大小,而且通常没有焊盘或对齐字节。但是,这并不总是简单明了的。
Although primitives must behave as if they have the official sizes, a JVM can store data in any way it pleases internally, with any amount of padding or overhead. It can choose to store a boolean[] in 64-bit long chunks like BitSet, allocate some temporary Objects on the stack or optimize some variables or method calls totally out of existence replacing them with constants, etc… But, as long as the program gives the same result, it’s perfectly fine.
尽管基元必须表现得像它们具有官方尺寸一样,但JVM可以在内部以任何方式存储数据,并使用任何数量的填充或开销。它可以选择像BitSet那样将boolean[]存储为64位长块,在堆栈中分配一些临时Objects,或者将一些变量或方法调用完全优化为常量,等等…但是,只要程序给出同样的结果,就完全没有问题。
Taking also into the account hardware and OS caches impact (our data could be duplicated on every cache level), it means that we can only roughly predict RAM consumption.
考虑到硬件和操作系统缓存的影响(我们的数据可能在每个缓存级别上都是重复的),这意味着我们只能大致预测RAM的消耗量。
2.1. Objects, References and Wrapper Classes
2.1.对象、引用和封装类
Minimum object size is 16 bytes for modern 64-bit JDK since the object has 12-byte header, padded to a multiple of 8 bytes. In 32-bit JDK, the overhead is 8 bytes, padded to a multiple of 4 bytes.
现代64位JDK的最小对象大小为16字节,因为对象有12字节的头,填充到8字节的倍数。在32位JDK中,开销是8字节,填充为4字节的倍数。
References have a typical size of 4 bytes on 32-bit platforms and on 64-bits platforms with heap boundary less than 32Gb (-Xmx32G), and 8 bytes for this boundary above 32Gb.
在32位平台和64位平台上,堆的边界小于32Gb(-Xmx32G)时,参考文献的典型大小为4字节,而在32Gb以上的边界则为8字节。
This means that a 64-bit JVM usually requires 30-50% more heap space.
这意味着64位JVM通常需要多出30-50%的堆空间。
Especially relevant is to note that boxed types, arrays, Strings and other containers like multidimensional arrays are memory costly since they add certain overhead. For example, when we compare int primitive (which consumes only 4 bytes) to the Integer object which takes 16 bytes, we see that there is 300% memory overhead.
特别相关的是要注意盒式类型、数组、Strings和其他容器(如多维数组)的内存成本很高,因为它们会增加一定的开销。例如,当我们将int基元(仅消耗4个字节)与Integer对象(需要16个字节)进行比较时,我们发现有300%的内存开销。
3. Estimating Object Size Using Instrumentation
3.使用仪器估计物体大小
One way to get an estimate of an object’s size in Java is to use getObjectSize(Object) method of the Instrumentation interface introduced in Java 5.
在Java中获得对象大小的估计的一种方法是使用Java 5中引入的Instrumentation接口的getObjectSize(Object)方法。
As we could see in Javadoc documentation, the method provides “implementation-specific approximation” of the specified object’s size. It’s noteworthy that a potential inclusion of overhead in the size exists and values can be different during single JVM invocation.
正如我们在Javadoc文档中看到的,该方法提供了指定对象大小的 “特定实现的近似值”。值得注意的是,尺寸中可能包含了开销,而且在单次JVM调用时,数值可能是不同的。
This approach only supports size estimation of the considered object itself and not the sizes of objects it references. To estimate a total size of the object, we would need a code that would go over those references and calculate the estimated size.
这种方法只支持对所考虑的对象本身的大小进行估计,而不支持它所引用的对象的大小。为了估计对象的总尺寸,我们需要一个代码,来检查这些引用并计算估计的尺寸。
3.1. Creating Instrumentation Agent
3.1.创建仪器仪表代理
In order to call Instrumentation.getObjectSize(Object) to get object’s size, we need to be able to access the instance of Instrumentation first. We need to use the instrumentation agent and there are two ways to do it, as described in the documentation for the java.lang.instrument package.
为了调用 Instrumentation.getObjectSize(Object) 来获取对象的大小,我们需要首先能够访问 Instrumentation 的实例。我们需要使用仪器代理,有两种方法,如java.lang.instrument包的文档中所述。
Instrumentation agent can be specified via the command-line or we can use it with an already running JVM. We’ll focus on the first one.
仪表代理可以通过命令行指定,或者我们可以用已经运行的JVM来使用它。我们将专注于第一种情况。
To specify the instrumentation agent via the command-line, we’ll need the implementation of the overloaded premain method that will be first invoked by the JVM when using instrumentation. Besides that, we need to expose a static method to be able to access Instrumentation.getObjectSize(Object).
为了通过命令行指定仪表代理,我们需要实现重载的premain方法,该方法将在使用仪表时被JVM首先调用。除此之外,我们需要公开一个静态方法,以便能够访问Instrumentation.getObjectSize(Object)。
Let’s now create the InstrumentationAgent class:
现在我们来创建InstrumentationAgent类。
public class InstrumentationAgent {
private static volatile Instrumentation globalInstrumentation;
public static void premain(final String agentArgs, final Instrumentation inst) {
globalInstrumentation = inst;
}
public static long getObjectSize(final Object object) {
if (globalInstrumentation == null) {
throw new IllegalStateException("Agent not initialized.");
}
return globalInstrumentation.getObjectSize(object);
}
}
Before we create a JAR for this agent, we need to make sure that a simple metafile, MANIFEST.MF is included in it:
在我们为这个代理创建JAR之前,我们需要确保其中包含一个简单的元文件,MANIFEST.MF。
Premain-class: com.baeldung.objectsize.InstrumentationAgent
Now we can make an Agent JAR with the MANIFEST.MF file included. One way is via command-line:
现在我们可以制作一个包含MANIFEST.MF文件的Agent JAR。一种方法是通过命令行。
javac InstrumentationAgent.java
jar cmf MANIFEST.MF InstrumentationAgent.jar InstrumentationAgent.class
3.2. Example Class
3.2.示例类
Let’s see this in action by creating a class with sample objects that will make use of our agent class:
让我们通过创建一个带有样本对象的类来看看这个动作,这些对象将利用我们的代理类。
public class InstrumentationExample {
public static void printObjectSize(Object object) {
System.out.println("Object type: " + object.getClass() +
", size: " + InstrumentationAgent.getObjectSize(object) + " bytes");
}
public static void main(String[] arguments) {
String emptyString = "";
String string = "Estimating Object Size Using Instrumentation";
String[] stringArray = { emptyString, string, "com.baeldung" };
String[] anotherStringArray = new String[100];
List<String> stringList = new ArrayList<>();
StringBuilder stringBuilder = new StringBuilder(100);
int maxIntPrimitive = Integer.MAX_VALUE;
int minIntPrimitive = Integer.MIN_VALUE;
Integer maxInteger = Integer.MAX_VALUE;
Integer minInteger = Integer.MIN_VALUE;
long zeroLong = 0L;
double zeroDouble = 0.0;
boolean falseBoolean = false;
Object object = new Object();
class EmptyClass {
}
EmptyClass emptyClass = new EmptyClass();
class StringClass {
public String s;
}
StringClass stringClass = new StringClass();
printObjectSize(emptyString);
printObjectSize(string);
printObjectSize(stringArray);
printObjectSize(anotherStringArray);
printObjectSize(stringList);
printObjectSize(stringBuilder);
printObjectSize(maxIntPrimitive);
printObjectSize(minIntPrimitive);
printObjectSize(maxInteger);
printObjectSize(minInteger);
printObjectSize(zeroLong);
printObjectSize(zeroDouble);
printObjectSize(falseBoolean);
printObjectSize(Day.TUESDAY);
printObjectSize(object);
printObjectSize(emptyClass);
printObjectSize(stringClass);
}
public enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
}
For this to work, we need to include –javaagent option with the path to agent JAR when running our application:
为了使其发挥作用,我们需要在运行应用程序时包括-javaagent选项和代理JAR的路径。
VM Options: -javaagent:"path_to_agent_directory\InstrumentationAgent.jar"
The output of running our class will show us estimated object sizes:
运行我们的类的输出将显示我们估计的对象大小。
Object type: class java.lang.String, size: 24 bytes
Object type: class java.lang.String, size: 24 bytes
Object type: class [Ljava.lang.String;, size: 32 bytes
Object type: class [Ljava.lang.String;, size: 416 bytes
Object type: class java.util.ArrayList, size: 24 bytes
Object type: class java.lang.StringBuilder, size: 24 bytes
Object type: class java.lang.Integer, size: 16 bytes
Object type: class java.lang.Integer, size: 16 bytes
Object type: class java.lang.Integer, size: 16 bytes
Object type: class java.lang.Integer, size: 16 bytes
Object type: class java.lang.Long, size: 24 bytes
Object type: class java.lang.Double, size: 24 bytes
Object type: class java.lang.Boolean, size: 16 bytes
Object type: class com.baeldung.objectsize.InstrumentationExample$Day, size: 24 bytes
Object type: class java.lang.Object, size: 16 bytes
Object type: class com.baeldung.objectsize.InstrumentationExample$1EmptyClass, size: 16 bytes
Object type: class com.baeldung.objectsize.InstrumentationExample$1StringClass, size: 16 bytes
4. Conclusion
4.结论
In this article, we described how the memory is used by particular types in Java, how JVM stores data and emphasized things that can impact total memory consumption. We then demonstrated how we can in practice get the estimated size of Java objects.
在这篇文章中,我们描述了Java中特定类型的内存是如何被使用的,JVM是如何存储数据的,并强调了可能影响总内存消耗的事情。然后我们演示了在实践中如何获得Java对象的估计大小。
As always, the complete code related to this article can be found in the GitHub project.
一如既往,与本文相关的完整代码可以在GitHub项目中找到。