Under the Hood: How Java Primitive and Object Types Consume RAM
We write `int age = 25;` and rarely think about what happens in memory. The JVM manages memory for us and gets larger memory regions from the OS when needed. It then allocates space for variables and objects within them. Understanding where data lives and how much memory it uses becomes important when building high-performance, memory-efficient systems.
1. Java 8 primitives and their memory footprint in RAM
Java has 8 fundamental built-in primitive datatypes and those are one of the building blocks in Java programming language. Basically they store raw values directly without object overhead.
Regardless of the platform the program runs (macOs, Windows, Linux) the memory occupied by a primitive in Java is fixed and platform independent.

Why boolean is special ? JVM specifications doesn't strictly defines the exact memory size of a boolean. In practice, a standard boolean field is treated as ~1 byte value.
2. Now, where does data live ? Stack Vs Heap
It is important to know that the memory location of the variable is based on the declaration location (class level or method level) of the variable.
Stack Memory: Stores short lived method stack frames specific to each thread, local variables (both primitive and object reference) created inside methods. Once the method execution completes, entire stack frame gets removed instantly.
Heap Memory: Stores all objects and instance fields (class level members). Data on the Heap is alive until the garbage collector determines it is no longer needed.

Note: When we declare a field inside a class (eg: private int age ;) it occupy 4 bytes inside the object payload on the Heap, not on the Stack.
3. Hidden Costs: Object headers, References
Storing an object on a 64-bit JVM incurs hidden overhead.
Object Headers (12 bytes of memory): Every object instance in java on the Heap includes Object Header consisting of:
Mark word (8 bytes): Stores identity hash code, GC metadata and locking flags.
Klass pointer (4 bytes): A pointer to the class metadata in the Meta space (where the class definition is stored).
Reference Variables (Pointers): When a class contains a reference to another object (like String, List or Custom object) the field does not store the object itself, it stores the pointer also. Which generally takes 4 bytes based on the JVM.
8-byte Padding: If an object's total size in bytes is not a multiple of 8, the JVM adds blank space called padding.
Example: A User class containing int age (4 bytes) and boolean active (1 byte): 12 bytes (header) + 4 bytes + 1 byte = 17 bytes ---> 24 bytes in RAM after 7 bytes of padding.

4. Primitives vs. Wrapper Classes: The 5x Memory Penalty: Consider allocating 1,000,000 integers
Java offers wrapper classes for all primitives (`Integer` for int Double for double, etc.) to work with collections like ArrayList. However, switching from primitives to wrapper classes introduces significant memory amplification.
// Option A: Primitive Array
int[] primitiveArray = new int[1_000_000];
// Option B: Wrapper Object Array
Integer[] wrapperArray = new Integer[1_000_000];Breakdown of memory occupancy in RAM for the above primitive and wrapper types.
primitive int [] array only occupy ~ 4 MB of memory in the RAM, why ? primitive array store raw values sequentially right next to each other in memory with zero objects created.
primitive array header: 16 bytes (8 bytes Mark word + 4 bytes Klass pointer + 4 bytes length field).
primitive data: 1,000,000 * 4 bytes = 4,000,000 bytes ~ 4 MB.
Total: ~4 MB stored in a single contiguous memory block with zero object overhead.
Wrapper Integer[] array occupy ~ 20 MB of memory in the RAM, why? Integer [] itself is an object on the heap that stores the memory addresses (pointers) pointing to Integer objects.
array header: 16 bytes (Mark word 8 bytes + Klass pointer 4 bytes + array length 4 bytes).
references: 1,000,000 * 4 bytes = 4,000,000 bytes ~ 4MB (pointing to heap addresses).
Every element in the array points to an independent Integer instance on the Heap, so each single object occupies
Object header: 12 bytes (8 bytes Mark word + 4 bytes Klass pointer).
int value: 4 bytes.
padding: 0 bytes (12+4 = 16 bytes, already a multiple of 8 so 0 bytes for padding).
Total each Integer object memory footprint is 12 + 4 = 16 bytes, for 1,000,000 object it is 16,000,000 bytes ~ 16MB.
Total: ~20 MB (4 MB references + 16 MB objects).
So, finally for the wrapper type Integer array (Integer[]) of having 1,000,000 Integer objects occupy ~20MB (4 + 16) while the primitive type int array (int[]) having same number of primitive int's occupy ~ 4MB.
5. So, what we should do for memory conscious java code
Prefer primitives (
int,long,double) for internal models and entity fields unless representingnullis a strict domain requirement.Avoid unexpected wrapper conversions in high-throughput operations or loops.
For primitive collections, use specialized libraries like fastutil to avoid boxing penalties.