Java Object Size: Estimating, Measuring, and Verifying via Profiling
How to calculate Java Objects, Arrays, Primitives, and Complex Classes. Checking calculated size correctness using JProfiler.
Join the DZone community and get the full member experience.
Join For FreeIn this article, we will learn how to estimate all possible Java objects or primitives. This knowledge is critically important, especially for a production application. You might think that now most of the servers have enough memory that cover all possible application's needs. Well, in some sort you are right — hardware, it's pretty cheap compared to a developer's salary. But still, it's pretty easy to meet very consumable situations, e.g.:
- Caches, especially with long strings
- Structures with a large number of records (e.g., a tree with nodes built from the huge XML file)
- Any structures that replicate data from the database
In the next step, we begin estimating Java objects from primitive to more complex structures.
Java Primitive
Sizes of Java Primitives are well known and provided from the box:
Minimal Memory Word for 32 and 64-Bit Systems
The minimal size of the memory word for 32-bit and 64-bit is 4 and 8 bytes respectively. Any smaller length is rounded by 8. During the calculation, we will consider both cases.
Due to the nature of memory (word size) structure, any memory is multiple of 8 and if it's not the system will automatically add additional bytes (but the minimal size is still 4 and 8 bytes for 32/64 systems)
Java Object
Java object has no fields inside and according to specification, it has only metadata called header. The Header contains two parts: Mark Word and Klass pointer.
Functional purpose | Size 32bit OS | Size 64 bit | |
Mark word | Lock (Synchronization), Garbage Collector Info, Hash Code (comes from native call) | 4 bytes | 8 bytes |
Klass pointer | Block pointer, Array length (if object is an array) | 4 bytes | 4 bytes |
Total | 8 bytes (0 bytes offset) | 16 bytes (4 bytes offset) |
And how it looks like in Java Memory:
Java Primitive Wrappers
In Java, everything is an Object except primitive and references (the last one is hidden). So all wrapper classes just wrap the corresponding primitive type. So wrapper size, in general, = object header object + internal primitive field size + memory gap. The sizes of all primitive wrappers are shown in the next table:
Type | Internal Primitive Size | Header 32 Bit | Header 64 Bit | Total size 32 bit | Total size 64 bit | Total size 32-bit with gap | Total size 64 bit with gap |
Byte | 1 | 8 | 12 | 9 | 13 | 16 | 16 |
Boolean | 1 | 8 | 12 | 9 | 13 | 16 | 16 |
Integer | 4 | 8 | 12 | 12 | 16 | 16 | 16 |
Float | 4 | 8 | 12 | 12 | 16 | 16 | 16 |
Short | 2 | 8 | 12 | 10 | 14 | 16 | 16 |
Char | 2 | 8 | 12 | 10 | 14 | 16 | 16 |
Long | 8 | 8 | 12 | 16 | 20 | 16 | 24 |
Double | 8 | 8 | 12 | 16 | 20 | 16 | 24 |
Java Array
Java Array is pretty similar to objects — they also differ for primitive and object values. The array contains headers, array length, and its cells (to primitive) or reference to its cells (for objects). For clarification let's draw an array of primitive integers and big integers (wrappers).
Array of Primitives (Integer in Our Case)
Array of Objects (Bit Integer in Our Case)
So as you can see the key difference between primitive and object arrays — an additional layer with references. In this very example the reason for most memory loss — the usage of an Integer wrapper which adds 12 extra bytes (3 times more than primitive!).
Java Class
Now we know how to calculate Java Object, Java Primitive, and Java Primitive Wrapper and Arrays. Any class in Java is nothing but an object with a mix of all mentioned components:
- header (8 or 12 bytes for 32/64 bit os).
- primitive (type bytes depending on the primitive type).
- object/class/array (4 bytes reference size).
Java String
Java string it's a good example of the class, so besides header and hash it encapsulates a char array inside, so for a long string with length 500 we have:
String | Encapsulate char array | ||
header | 8-12 bytes (32/64 bit os) | header | 8-12 bytes (32/64 bit os) |
hash | 4 bytes | array length | 4 bytes |
char[] (reference) | 4 bytes | 500 chars | 500 * 2 bytes = 1000 bytes |
String size | 16 or 24 bytes | Total Array size | 16 (considering gap)+ 1000 bytes = 1016 bytes |
Total size | (16 or 24) + 1016 = 1032 or 1040 bytes (for 32 and 64 bit os) |
But we have to consider that the Java String class has different implementations, but in general, the main size is held by a char array.
How to Calculate Programmatically
Checking Size Using Runtime freeMemory
The easiest way, but not reliable is to compare the difference between total and free memory after and before memory initialization:
long beforeUsedMem=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
Object[] myObjArray = new Object[100_000];
long afterUsedMem=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
Using Jol Library
The best way is to use Jol library written by Aleksey Shipilev. This solution will pleasantly surprise you with how easily we can investigate any object/primitive/array. In order to do it you need to add the next Maven dependency:
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.16</version> </dependency>
and feed to ClassLayout.parseInstance
anything you want to estimate:
int primitive = 3; // put here any class/object/primitive/array etc
System.out.println(VM.current().details());
System.out.println(ClassLayout.parseInstance(primitive).toPrintable());
as output you will see:
# 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]
java.lang.Integer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x200021de
12 4 int Integer.value 3
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Using Profiler
As an option, you can use profilers (JProfiler, VM Visualizer, JConsole, etc.) in order to observe how much memory is consumed by this or another structure. But this solution is rather about profiling memory and not object structure. In the next paragraph, we will use JProfiler to confirm that our calculation is correct.
Making Database Cache Class and Calculating Its Size
As a realistic example, we create classes that represent data from some database table with 5 columns and 1.000.000 records in each of them.
public class UserCache{
public static void main(String[] args){
User [] cachedUsers = new User[1_000_000];
while(true){}
}
private static class User{
Long id;
String name; //assume 36 characters long
Integer salary;
Double account;
Boolean isActive;
}
}
So now we created 1M users, right? Well, doesn't matter what it's inside the User class — we just created 1M references. Memory usage: 1M * 4 bytes = 4000 KB or 4MB. Not even started, but paid 4MB.
Profiling Java Memory for 64-bit Systems
In order to confirm our calculation, we execute our code and attach JProfile to it. As an alternative, you can use any other profiler eg VisualVM (it's free). If you never profiled your application, you can check this article. Here is an example of how the profile screen looks like in JProfiler (it's just an example not related to our implementation).
Tip: when you profile an app you can run GC from time to time to clean up unused objects. So the results of profiling: we have User[]
reference points to 4M records and a size of 4000KB. When we profile
As the next step, we initialize objects and add them to our array (the name is unique UUID 36 length size):
for(int i = 0;i<1_000_000;i++){
User tempUser = new User();
tempUser.id = (long)i;
tempUser.name = UUID.randomUUID().toString();
tempUser.salary = (int)i;
tempUser.account = (double) i;
tempUser.isActive = Boolean.FALSE;
cachedUsers[i] = tempUser;
}
Now let's profile this app and confirm our expectations. You might mention that some values are not precise, e.g., Strings are 24.224 sizes instead of 24.000 but we count all String including internal JVM strings and the same related to Boolean.FALSE
object (estimated to 16 bytes, but in profile, it's 32 obviously because Boolean.TRUE
is also used by JVM internally).
For 1M records, we spend 212MB and it's only 5 fields and all string lengths are limited by 36 chars. So as you can see, objects are pretty greedy. Let's improve the User object and replace all objects with primitives (well, except String).
Just by changing fields to primitives, we saved 56MB (about 25% of used memory). But also we improved performance by removing additional references between the user and the primitive.
How to Reduce Memory Consumption
Let's list some simple ways to save your memory consumption:
Compressed OOPs
For 64-bit systems, you can execute JVM with compressed oop param. It's a pretty big subject and you can read this article compressed oop params.
Extract Data From A Child Object to Parent
If the design allows moving fields from child to parent class it might save some memory:
Collections With Primitives
From previous examples, we saw how primitives wrappers waste a lot of memory. Primitive arrays are quite are not user friendly as the Java Collection interface. But there is an alternative: Trove, FastUtils, Eclipse Collection, etc. Let's compare memory usage of simple ArrayList<Double>
and TDoubleArrayList
from the Trove library.
TDoubleArrayList arrayList = new TDoubleArrayList(1_000_000);
List<Double> doubles = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
arrayList.add(i);
doubles.add((double) i);
}
Generally, the key difference is hidden in Double Primitive Wrapper objects, not in ArrayList or TDoubleArrayList structure. So simplifying the difference for 1M records:
And JProfiler confirms it:
So just by changing the collection we easily reduce consumption in 3 times.
Conclusion
This article described basic things about the Java Memory structure. For more information, I recommend checking Alexey Shipilev's articles. If you have any questions, don't hesitate to ask them in the comments.
Opinions expressed by DZone contributors are their own.
Comments