Keywords: Java | Memory Management | Object Size
Abstract: This article explores various methods to programmatically determine the memory size of objects in Java, focusing on the use of the java.lang.instrument package and comparing it with JOL tools and ObjectSizeCalculator. Through practical code examples, it demonstrates how to obtain shallow and deep sizes of objects, aiding developers in optimizing memory usage and preventing OutOfMemoryError. The article also details object header, member variables, and array memory layouts, offering practical optimization tips.
Introduction
In Java application development, memory management is critical, especially when handling large-scale data such as reading CSV files and converting them into objects. Improper memory usage can lead to OutOfMemoryError. Based on high-scoring Q&A from Stack Overflow and reference articles, this article systematically introduces how to programmatically determine the size of Java objects and provides multiple practical tools and methods.
Fundamentals of Java Object Memory
Java objects are allocated memory in the heap, with their size consisting of the object header, instance variables, and padding bytes. The object header includes a mark word and a klass pointer, typically 16 bytes in 64-bit architectures. The size of instance variables depends on their type: primitives (e.g., int is 4 bytes) or object references (usually 4 bytes, depending on JVM configuration). The JVM aligns objects to 8-byte boundaries, adding padding bytes for performance.
In memory analysis, we focus on three metrics: shallow size, deep size, and retained size. Shallow size includes only the object itself and its direct references; deep size includes all referenced objects; retained size represents the memory that can be freed by garbage collecting the object, excluding parts shared by other objects.
Using the java.lang.instrument Package
The java.lang.instrument package provides a standard method to obtain the shallow size of an object. Here are the implementation steps:
First, create an instrumentation class, compile it, and package it into a JAR file:
import java.lang.instrument.Instrumentation;
public class ObjectSizeFetcher {
private static Instrumentation instrumentation;
public static void premain(String args, Instrumentation inst) {
instrumentation = inst;
}
public static long getObjectSize(Object o) {
return instrumentation.getObjectSize(o);
}
}Add to MANIFEST.MF:
Premain-Class: ObjectSizeFetcherUsage example:
public class C {
private int x;
private int y;
public static void main(String[] args) {
System.out.println(ObjectSizeFetcher.getObjectSize(new C()));
}
}Run with:
java -javaagent:ObjectSizeFetcherAgent.jar CThis method is straightforward but returns only the shallow size, excluding memory of referenced objects.
In-Depth Analysis with JOL Tools
Java Object Layout (JOL), part of the OpenJDK project, provides detailed object layout analysis. It uses Unsafe, JVMTI, and Serviceability Agent (SA) to decode actual object layouts, offering higher accuracy than tools relying on heap dumps.
Obtain JVM details:
System.out.println(org.openjdk.jol.vm.VMSupport.vmDetails());Sample output:
Running 64-bit HotSpot VM.
Using compressed oop with 0-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]Analyze class layout:
System.out.println(org.openjdk.jol.info.ClassLayout.parseClass(Foo.class).toPrintable());JOL also supports deep size analysis:
System.out.println(org.openjdk.jol.info.GraphLayout.parseInstance(obj).toFootprint());For example, for Pattern.compile("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"), JOL reports a total footprint of 1840 bytes, with the Pattern instance itself being only 72 bytes.
Comparison with Other Tools
jdk.nashorn.internal.ir.debug.ObjectSizeCalculator is a built-in JDK class that is easy to use:
System.out.println(ObjectSizeCalculator.getObjectSize(new HashMap<String, Integer>(100000)));Sample output: 48 (bytes). However, this method is not a standard API and may be removed in future JDK versions.
Compared to java.lang.instrument, JOL offers more comprehensive analysis but with slightly more complex setup; ObjectSizeCalculator is user-friendly but implementation-dependent.
Practical Applications and Optimization Tips
When processing CSV data, if each row is converted to an object, dynamically control the number of rows read by calculating object size. For example, targeting 32MB of memory usage:
long maxMemory = 32 * 1024 * 1024; // 32MB in bytes
long objectSize = ObjectSizeFetcher.getObjectSize(new DataRow());
int maxRows = (int) (maxMemory / objectSize);Recommendations for optimizing memory usage:
- Prefer primitives over wrapper classes to reduce object header and reference overhead.
- Use Trove library for primitive collections.
- Enable
-XX:+UseCompressedOopsto compress pointers and reduce memory usage in 64-bit systems. - Regularly use JOL to analyze object layouts and identify memory waste.
Conclusion
Programmatically calculating Java object sizes is key to optimizing memory usage. java.lang.instrument provides standard shallow size measurement, JOL offers in-depth layout analysis, and ObjectSizeCalculator serves as a convenient alternative. Developers should choose the appropriate tool based on needs, combining knowledge of memory metrics to effectively prevent OutOfMemoryError and enhance application performance.