Skip to content
Robin Drew edited this page Sep 17, 2020 · 13 revisions

There is no built-in functionality in Java for programmatically measuring the memory usage of an object. There are a number of tools for debugging the Java heap at runtime, but only a handful of libraries to resolve this specific problem. This project is one of those solutions, and is now over 8 years old. It provides a quick way of calculating the size of an object using reflection and shortcuts where possible to perform particularly fast programmatic calculation.

Getting Started

To calculate the size of an object, simply create a MemoryCalculator then call the sizeOf method.

long bytes = new MemoryCalculator().sizeOf(object);

Architecture

The IArchitecture contains the basic rules for determining the size of any object or primitive. Currently three architectures are supplied:

  • The Architecture32 which provides the rules for full 32 bit Java.
  • The Architecture64 which provides the rules for full 64 bit Java.
  • The Architecture64Compressed which provides the rules for 64 bit Java with compressed pointers enabled (the default for most JVMs).

Class Definitions

The IClassDefinitionMap contains the rules for determining the size of any object based on its class. You can configure the default map by writing your own ClassDefinition objects to further optimize the size calculations or circumvent the default rules. There are a few convenience methods to ignore classes and fields.

IArchitecture arch = new Architecture64Compressed();
IClassDefinitionMap map = new ClassDefinitionMap();

map.ignoreType(class1); // Ignore this class please
map.ignoreType(class2); // And this one ...
...

map.ignoreField(field1); // Ignore this field please
map.ignoreField(field2); // And this one ...
...

Set<Object> set = new IdentityHashSet();

IMemoryCalculator calculator = new MemoryCalculator(arch, map, set);
long bytes = calculator.sizeOf(object);

Identity Set

The identity set is a Set containing all objects to ignore if encountered while traversing the hierarchy. The calculation adds objects to this set during its progress through the hierarchy to prevent it attempting to find the size of the same instance more than once. There will often be objects (perhaps static factory classes or singletons) that are referenced by some object in the hierarchy but you do not wish to be included in the calculation. To ignore them, simply create the set and add the instances to it before calling sizeof.

Set<Object> set = new IdentityHashSet();

set.add(object1); // Ignore this object please
set.add(object2); // And this one ...

IArchitecture arch = new Architecture64Compressed();
IClassDefinitionMap map = new ClassDefinitionMap();

IMemoryCalculator calculator = new MemoryCalculator(arch, map, set);
long bytes = calculator.sizeOf(object);

Notes

String can be cached in memory (see String.intern), it is not possible to programmatically determine whether a string has been cached, so all strings are treated as if not cached. A common problem with traversing an object hierarchy is when encountering long linked lists (for example a LinkedList, LinkedHashMap or LinkedHashSet) a StackOverflowError can occur. To handle this issue special ClassDefinitions exist to deal with these particular objects. However if you have a custom linked list in code, it may require a custom ClassDefinition to handle it. There are no problems if a linked list is short, or empty, only if its length will cause the stack to fill.

Calculations

How to calculate the size of a Java object

Primitives

Any Java book will include the relative sizes of each of the basic types in one of its early chapters. The various instances of IArchitecture includes these as constants:

/** The number of bytes used to represent a byte. */
SIZE_OF_BYTE = 1;
/** The number of bytes used to represent a short. */
SIZE_OF_SHORT = 2;
/** The number of bytes used to represent an integer. */
SIZE_OF_INT = 4;
/** The number of bytes used to represent a long. */
SIZE_OF_LONG = 8;

/** The number of bytes used to represent a boolean. */
SIZE_OF_BOOLEAN = 1;
/** The number of bytes used to represent a char. */
SIZE_OF_CHAR = 2;
/** The number of bytes used to represent a float. */
SIZE_OF_FLOAT = 4;
/** The number of bytes used to represent a double. */
SIZE_OF_DOUBLE = 8;

Objects

Knowing the size of primitives is essential to understanding the size of any instance of an object. There are also two other key sizes associated with objects, the base size of any object (including arrays), and the size of a null pointer (field or object array element):

/** The number of bytes used to represent a pointer field (unassigned/null). */
SIZE_OF_NULL_POINTER = 4;
/** The number of bytes used to represent an object (with no fields). */
SIZE_OF_OBJECT_INSTANCE = 8;

Rules

Object Rules:

  • The base size of any object is 8 bytes.
  • The total size of any object is always a multiple of 8 bytes (rounded up).

Field Rules:

  • Each primitive field occupies the size of the primitive value it contains (1, 2, 4 or 8 bytes).
  • Each object field occupies 4 bytes (if it is not null, the value should be calculated as a separate object).

Array Rules:

  • A primitive array occupies the base of 8 bytes, + 4 bytes for the length of the array, + the length multiplied by the size of the primitive.
  • An object array occupies the base of 8 bytes, + 4 bytes for the length of the array, + the length multiplied by 4 bytes (the size of the null pointer).

Compressed Pointers

It is helpful to note that from 32-bit to 64-bit, object references (for fields and arrays) double from 4 to 8 bytes, and the base object size doubles from 8 to 16 bytes. What is interesting however is how 64-bit compressed pointers reduce object references back to 4 bytes. They also reduce the base object size from 16 to 12 bytes. Overall compressed pointers provide a very significant reduction in memory usage compared to full 64-bit pointers.

Basic Types

Object 32-bit 64-bit** 64-bit
Object Field 4 bytes 4 bytes 8 bytes
Object Array Element 4 bytes 4 bytes 8 bytes
Object 8 bytes 16 bytes 16 bytes
Boolean 16 bytes 16 bytes 24 bytes
Byte 16 bytes 16 bytes 24 bytes
Short 16 bytes 16 bytes 24 bytes
Integer 16 bytes 16 bytes 24 bytes
Long 16 bytes 24 bytes 24 bytes
Float 16 bytes 16 bytes 24 bytes
Double 16 bytes 24 bytes 24 bytes
Currency 16 bytes
Date > 24 bytes
BigInteger > 56 bytes
BigDecimal > 144 bytes
Thread > 176 bytes
GregorianCalendar > 460 bytes

Collections

Object 32-bit 64-bit** 64-bit
ArrayList 40(+4) bytes 40(+4) bytes 64 (+8) bytes
LinkedList 48 (+24) bytes 48 (+24) bytes 80(+40) bytes
HashMap 120 (+32) bytes
HashSet 136 (+30) bytes
LinkedHashMap 160 (+32) bytes
LinkedHashSet 176 (+30) bytes
TreeMap 40 (+32) bytes
TreeSet 64 (+32) bytes
ConcurrentHashMap 1272 (+33) bytes
ConcurrentSkipListMap 104 (+36) bytes
CopyOnWriteArrayList 72 (+4) bytes
CopyOnWriteArraySet 88 (+4) bytes
ArrayBlockingQueue 144 (+4) bytes
LinkedBlockingQueue 200 (+216) bytes