DEV Community

Cover image for How Java Stores Data in Memory: Writing Efficient Code with Primitives and Non-Primitives
Nishanthan K
Nishanthan K

Posted on

How Java Stores Data in Memory: Writing Efficient Code with Primitives and Non-Primitives

How Java Stores Data in Memory — Primitives vs Non-Primitives

Java is a statically typed language, which means every variable’s type must be known at compile time. This allows the JVM (Java Virtual Machine) to allocate memory efficiently and enforce type safety during execution.

Primitive Types in Java

Primitive types are the building blocks of data in Java. They have fixed memory sizes and are stored in the stack. The value of a primitive can change, but its size does not.

Type Size Description
byte 1 byte Smallest integer (–128 to 127)
short 2 bytes Short integer
int 4 bytes Default integer type
long 8 bytes Large-range integer
float 4 bytes Single-precision float
double 8 bytes Double-precision float
char 2 bytes Unicode character
boolean 1 bit* True or False (handled as 1 byte internally)

Note: Although boolean is conceptually 1 bit, it is usually stored as **1 byte* in memory due to alignment and practical implementation constraints.*

Non-Primitive (Reference) Types

Non-primitives, also called reference types, are more complex and stored in the heap, while their references (pointers) are kept in the stack. These types include:

Type Description
String Sequence of characters (immutable)
Arrays Ordered collection of elements (of any type, including primitives)
Class Custom-defined objects
Interface Abstract types that other classes implement
Enum A fixed set of constants (internally class-like)
Wrapper Object representations of primitive types (e.g., Integer, Double)
Collections Data structures like List, Set, Map, etc. (part of Java Collections Framework)

How Java Stores Data in Memory

Java abstracts memory management with automatic garbage collection, but here’s how it works under the hood:

Stack Memory

  • Stores method calls, local variables, and primitive types.
  • Fast access due to LIFO structure.
  • Memory is freed automatically once a method exits.

Heap Memory

  • Stores objects and non-primitive values.
  • Accessed via references stored in the stack.
  • Managed by Garbage Collector (GC).
  • Larger and slower than stack but can store dynamic and complex structures.

How Primitive Types Are Accessed

  • Stored directly in the stack.
  • Access and modification are fast and straightforward.
  • Since they are fixed-size, the JVM knows exactly how much memory to allocate and retrieve.

How Non-Primitives Are Accessed

  1. When a non-primitive is created, the reference (memory address) is stored in the stack.
  2. The actual object is stored in the heap.
  3. Accessing the object involves following the reference from stack to heap.

Example:

String name = "John";
Enter fullscreen mode Exit fullscreen mode
  • name (stack) → points to → "John" (heap)

If we change name = "Jack";, now name points to a new "Jack" in heap (because String is immutable).

Memory Alignment and JVM Optimization

Java is designed with the principle: “Write once, run anywhere”, which means JVM must work on both 32-bit and 64-bit CPUs.

JVM Alignment Strategy:

  • Regardless of declared type, JVM aligns memory into 4-byte or 8-byte chunks depending on CPU.
  • Operations on smaller types (byte, short) are converted to int internally.

Why is that?

  • byte takes 25% of a 4-byte block; short takes 50%.
  • CPU needs extra steps to calculate offsets and align the memory block.
  • int consumes the entire 4-byte block, making storage and retrieval faster.

This is why:

JVM promotes byte and short to int during calculations and requires explicit casting to store results back.

Misconception: Using byte/short Saves Memory?

Why byte and short are less efficient:

  • JVM treats them as int internally.
  • Adds extra overhead during arithmetic and access.
  • Introduces hidden conversions (widening and narrowing).
  • Slower due to memory misalignment and CPU inefficiency.

For performance and alignment, use int as default for integers.

What About Wrapper Classes?

Wrapper classes (Integer, Double, etc.) are object versions of primitive types.

  • Stored in heap like any object.
  • Used in Collections (e.g., List<Integer>, not List<int>)
  • Include utility methods like parseInt, compareTo, etc.

Note: Auto-boxing and unboxing happen automatically:

int a = 10;
Integer b = a; // auto-boxing
int c = b;     // unboxing
Enter fullscreen mode Exit fullscreen mode

Boxing adds memory and performance overhead, so avoid unnecessary boxing in critical code paths.

Key Takeaways

  • Primitive types are fixed-size and fast, stored in stack.
  • Non-primitives are stored in heap with reference in stack.
  • JVM uses int as the baseline for all smaller integral operations.
  • Avoid using byte and short for optimization, it backfires due to hidden processing.
  • Wrapper classes add object overhead; prefer primitives for performance-critical paths.
  • Java abstracts memory handling, but understanding this helps you write more efficient code.

Top comments (0)