I tried to check the memory consumption of a program. During the check, I have noticed some interesting things.
I created a Load class, which contains some fields.
class Load {
String name;
String title;
long id;
}
I created 500000 Load objects and add them to an ArrayList. I have found, it took around 18 MB of memory.
Then, I modified the Load class and use reference type Long.
class Load {
String name;
String title;
Long id;
}
Again created 500000 Load objects and add them to ArrayList. Interestingly this time it took less memory than the previous one. It way 14 MB.
Run test changing os and JVM version. Found the following results.
OS: Windows 10 Pro 64 bit
JDK: 11 64bit
Object Created | Load Object | Memory | Load Object | Memory
------------------------------------------------------------------------------
1. 500000 | With primitive long | 18 MB | With reference Long | 14 MB
2. 900000 | | 32 MB | | 26 MB
3. 1500000 | | 53 MB | | 41 MB
OS: macOS Big Sur 64 bit
JDK: 8 64bit
Object Created | Load Object | Memory | Load Object | Memory
------------------------------------------------------------------------------
1. 500000 | With primitive long | 18 MB | With reference Long | 14 MB
2. 900000 | | 32 MB | | 26 MB
3. 1500000 | | 53 MB | | 41 MB
Surprisingly, in all of these test runs, Object contains primitive types long consume more memory than Object contains reference Long.
Memory Tester Code:
public class MemoryChecker {
private static final long MEGABYTE = 1024L * 1024L;
public static long bytesToMegabytes(long bytes) {
return bytes / MEGABYTE;
}
public static void main(String[] args) {
List<Load> list = new ArrayList<Load>();
for (int i = 0; i <= 500000
; i++) {
list.add(new Load("Jim", "Knopf", 11L));
}
// 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 megabytes: " + bytesToMegabytes(memory));
}
}
Complete code git repo.
For 32 Bit JVMs, but also for 64 Bit JVMs with the CompressedOOPs feature (which is supported by the HotSpot JVM and on by default), a reference consumes only 4 bytes, compared to the 8 bytes of a long.
Even when you initialize the reference with an actual object, it may consume less memory when the object is shared. This applies to autoboxing of constants:
If the value p being boxed is the result of evaluating a constant expression (§15.29) of type
boolean,byte,char,short,int, orlong, and the result istrue,false, a character in the range'\u0000'to'\u007f'inclusive, or an integer in the range-128to127inclusive, then letaandbbe the results of any two boxing conversions ofp. It is always the case thata == b.
but also to all operations ending up at Long.valueOf(long) in general.
This method will always cache values in the range -128 to 127, inclusive, and may cache other values outside of this range.
Of course, if you create a lot of unshared Long objects, they will consume far more memory than the primitive long. If you use a lot of distinct values, even a potential sharing of them wouldn’t help.
The largest primitive can hold 8 bytes.
every Object has at least 12 bytes (with default 64 bit VMs and relatively small heap) of headers. Automatically making it bigger than a primitive.
There is a single good library that I am aware of that does correctly, called jol. here is a related question.
It is rather easy to get jol set-up and run the samples to understand the actual numbers you are interested in.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With