1. The Hook (The "Byte-Sized" Intro)
- In a Nutshell: Memory optimization = reduce footprint, improve performance.
- Techniques: (1) Object pooling (reuse expensive objects), (2) Immutability (share instances), (3) Lazy initialization (create on-demand), (4) Primitives over wrappers (int vs Integer, no boxing), (5) Collection sizing (set initial capacity, avoid resizing), (6) StringBuilder (avoid String concat in loops).
- Trade-off: Complexity vs memory savings.
- Golden rule: Profile first, optimize bottlenecks, avoid premature optimization!
Think of minimalist living. Object pooling = share car (Uber) vs owning. Immutability = library books (share). Lazy init = buy furniture when needed (not all at once). Primitives = cash (efficient) vs credit card (overhead). StringBuilder = reusable notepad vs new paper for each note!
2. Conceptual Clarity (The "Simple" Tier)
š” The Analogy
- Object pooling: Car rental (reuse vs buy)
- Immutability: Shared resources (library)
- Lazy init: Just-in-time delivery
- Primitives: Direct transaction (no middleman)
3. Technical Mastery (The "Deep Dive")
Optimization Techniques
| Technique | Memory Saved | Complexity | When to Use |
|---|---|---|---|
| Object pooling | High | Medium | Expensive creation |
| Immutability | Medium | Low | Shareable data |
| Lazy init | Medium | Medium | Rarely used objects |
| Primitives | Low per object | Low | Hot paths |
| Collection sizing | Medium | Low | Known size |
4. Interactive & Applied Code
java
import java.util.*;
public class MemoryOptimizationDemo {
public static void main(String[] args) {
demonstrateObjectPooling();
demonstrateImmutability();
demonstrateLazyInit();
demonstratePrimitivesVsWrappers();
demonstrateCollectionSizing();
demonstrateStringBuilder();
}
// 1. Object Pooling
static class ConnectionPool {
private Queue<Connection> pool = new LinkedList<>();
ConnectionPool(int size) {
for (int i = 0; i < size; i++) {
pool.offer(new Connection());
}
}
Connection acquire() {
return pool.poll();
}
void release(Connection conn) {
pool.offer(conn);
}
}
static class Connection {
// Expensive to create
}
static void demonstrateObjectPooling() {
System.out.println("=== OBJECT POOLING ===");
ConnectionPool pool = new ConnectionPool(10);
// ā
Reuse from pool (efficient)
Connection conn = pool.acquire();
// Use connection
pool.release(conn);
// ā vs creating new each time (wasteful)
// Connection conn = new Connection();
}
// 2. Immutability (sharing)
static void demonstrateImmutability() {
System.out.println("\n=== IMMUTABILITY ===");
// Immutable objects can be shared
String s1 = "Hello";
String s2 = "Hello"; // ā
Shares same object (String pool)
System.out.println("s1 == s2: " + (s1 == s2)); // true
// Mutable = can't share (safety)
StringBuilder sb1 = new StringBuilder("Hello");
StringBuilder sb2 = new StringBuilder("Hello"); // ā Separate objects
}
// 3. Lazy Initialization
static class LazyResource {
private static ExpensiveObject instance; // Not created yet!
static ExpensiveObject getInstance() {
if (instance == null) {
instance = new ExpensiveObject(); // Create on-demand
}
return instance;
}
}
static class ExpensiveObject {
// Large object
}
static void demonstrateLazyInit() {
System.out.println("\n=== LAZY INITIALIZATION ===");
// ā
Lazy: Created only if needed
ExpensiveObject lazy = LazyResource.getInstance();
// ā Eager: Created immediately (wasteful if unused)
// ExpensiveObject eager = new ExpensiveObject();
}
// 4. Primitives vs Wrappers
static void demonstratePrimitivesVsWrappers() {
System.out.println("\n=== PRIMITIVES VS WRAPPERS ===");
// ā BAD: Wrappers (boxing overhead)
Long start1 = System.nanoTime();
Integer sum1 = 0;
for (int i = 0; i < 1_000_000; i++) {
sum1 += i; // Auto-boxing each iteration!
}
System.out.println("Wrappers: " + (System.nanoTime() - start1) + " ns");
// ā
GOOD: Primitives (no boxing)
long start2 = System.nanoTime();
int sum2 = 0;
for (int i = 0; i < 1_000_000; i++) {
sum2 += i; // Direct primitive operation
}
System.out.println("Primitives: " + (System.nanoTime() - start2) + " ns");
}
// 5. Collection Sizing
static void demonstrateCollectionSizing() {
System.out.println("\n=== COLLECTION SIZING ===");
// ā BAD: Default size (resizes multiple times)
List<Integer> list1 = new ArrayList<>(); // Default: 10
for (int i = 0; i < 1000; i++) {
list1.add(i); // Resizes at 10, 20, 40, 80, 160...
}
// ā
GOOD: Pre-sized (no resizing)
List<Integer> list2 = new ArrayList<>(1000); // Initial: 1000
for (int i = 0; i < 1000; i++) {
list2.add(i); // No resizing!
}
}
// 6. StringBuilder (avoid String concat)
static void demonstrateStringBuilder() {
System.out.println("\n=== STRINGBUILDER ===");
// ā BAD: String concatenation (creates many objects)
long start1 = System.nanoTime();
String result1 = "";
for (int i = 0; i < 1000; i++) {
result1 += i; // Creates new String each iteration!
}
System.out.println("String concat: " + (System.nanoTime() - start1) + " ns");
// ā
GOOD: StringBuilder (single mutable object)
long start2 = System.nanoTime();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i); // Modifies same object
}
String result2 = sb.toString();
System.out.println("StringBuilder: " + (System.nanoTime() - start2) + " ns");
}
}5. The Comparison & Decision Layer
| Optimization | Memory Saved | Complexity | Trade-off |
|---|---|---|---|
| Object pooling | High | Medium | Complexity vs savings |
| Primitives | Low per use | Low | Always use when possible |
| Collection sizing | Medium | Low | Always do if size known |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "Primitives vs Wrappers memory overhead?" Answer:
- int: 4 bytes
- Integer: 16 bytes (12 header + 4 value)
- 4x overhead!
java
// ā Memory wasteful
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add(i); // Each int wrapped in 16-byte Integer!
}
// Total: 1000 Ć 16 = 16KB (vs 4KB for primitives)
// ā
Efficient (if possible)
int[] array = new int[1000]; // 4KBPro-Tip: When to pool objects:
java
ā
POOL:
- Expensive creation (DB connections, threads)
- Heavy objects (large buffers)
- Frequent allocation/deallocation
ā DON'T POOL:
- Cheap objects (String, Integer)
- Short-lived objects (GC handles well)
- Simple POJOs