Java memory management is the JVM's way of allocating, using, and automatically reclaiming memory, mainly through areas such as Heap, Method Area, Stack, and the Program Counter, with Garbage Collection handling objects that are no longer reachable.
Every Java program needs memory to store things such as:
- Variables
- Objects
- Class metadata
- Method execution information
- Temporary data
The JVM manages this memory for us.
For example:
int x = 4;
Student s1 = new Student();The JVM needs to allocate memory for these values and objects.
The handwritten diagram on page 1 summarizes the basic memory-management responsibilities as:
Memory
↓
Allocate
↓
Use / Update
↓
Deallocate / Free
When the program needs memory, the JVM allocates it.
Example:
int x = 4;and:
Student s1 = new Student();The first creates a variable, while the second creates an object.
Conceptually:
Program
↓
Needs memory
↓
JVM allocates memory
After memory is allocated, the program can use or modify it.
Example:
int x = 4;
x = x + 1;The value stored for x changes from:
4 → 5
The page 1 notes represent this as:
Memory use / update
↓
x = x + 1
Eventually, memory that is no longer needed should be reclaimed.
In languages such as C/C++, developers can explicitly manage memory using mechanisms such as:
free()
delete
Java takes a different approach.
Java uses an automatic Garbage Collector (GC).
Unused object
↓
Garbage Collector
↓
Memory can be reclaimed
The first page of the uploaded notes contrasts manual memory management in C/C++ with Java's Garbage Collection.
The programmer has more direct responsibility for memory management.
Conceptually:
Allocate
↓
Use
↓
free/delete
If memory is not properly released, problems can occur.
Java provides automatic garbage collection:
Allocate
↓
Use
↓
Object becomes unreachable
↓
Garbage Collector
↓
Memory reclaimed
This makes memory management easier, but it does not mean developers can completely ignore memory usage.
A common misconception is:
"Java has Garbage Collection, so memory management is no longer my problem."
That is wrong.
You can still write memory-unfriendly programs.
For example:
List<Student> list = new ArrayList<>();If the program keeps adding unnecessary objects and references:
list
↓
Student
Student
Student
Student
...
those objects remain reachable and cannot simply be reclaimed.
The notes on page 2 specifically connect unnecessary object retention with memory leaks and performance degradation.
A memory leak happens when memory that is no longer logically needed remains occupied because something still keeps a reference to it.
Conceptually:
Object no longer needed
↓
But still referenced
↓
GC considers it reachable
↓
Cannot reclaim it
↓
Memory keeps being occupied
So Garbage Collection cannot magically know your business intent.
It works based on reachability.
Suppose:
List<Student> students = new ArrayList<>();and the list keeps growing:
students
↓
Student 1
Student 2
Student 3
Student 4
...
If those objects are no longer useful but the list still references them, they remain reachable.
Therefore:
Still referenced
↓
Not eligible for GC
This is why writing memory-friendly code is still important even in Java.
Poor memory usage can affect performance.
For example:
Too many unnecessary objects
↓
More memory consumption
↓
More GC work / memory pressure
↓
Potential performance degradation
The notes on page 2 explicitly mention that poor memory management can cause performance degradation.
The JVM divides memory into different areas because different kinds of information have different lifetimes and purposes.
The notes show this structure on page 2:
JVM Memory
│
┌────────────────┼─────────────────┐
↓ ↓ ↓
Heap Method Area Stack
│
↓
Thread-specific
+
Program Counter
The main areas covered by the source are:
- Heap
- Method Area / Metaspace
- Stack
- Program Counter (PC Register)
A useful mental model is:
JVM Memory
│
├── Heap
│ └── Objects, arrays, instance data
│
├── Method Area / Metaspace
│ └── Class metadata, static data, bytecode-related information
│
├── Stack
│ └── Method frames, local variables, execution information
│
└── Program Counter
└── Current instruction for a thread
An important distinction from the page 2 diagram is:
Thread-shared
├── Heap
└── Method Area
Thread-specific
├── Stack
└── Program Counter
This distinction is important when thinking about multiple threads.
The Heap is the memory area primarily associated with dynamically created objects.
Examples:
Student s1 = new Student();
int[] numbers = new int[10];The objects/arrays created dynamically are stored in the Heap.
The source describes Heap memory as storing:
- Objects
- Arrays
- Instance variables
Consider:
class Student {
String name;
int age;
}Then:
Student s1 = new Student();Conceptually:
Stack
┌─────────────┐
│ s1 ─────────┼──────────────┐
└─────────────┘ │
↓
Heap
┌──────────────┐
│ Student │
│ name │
│ age │
└──────────────┘
The reference variable and the object itself should not be mentally treated as the same thing.
The object lives in the Heap, while the reference used to reach it is associated with the executing context.
The page 6 notes explicitly list:
Heap
├── Objects
├── Instance variables
├── Arrays
└── Strings
Examples:
Student s1 = new Student();class Student {
String name;
int age;
}The name and age belong to each Student object.
int[] numbers = new int[5];String objects are also part of the object-based memory model and are represented in the source's Heap discussion.
The notes describe Heap memory as shared across threads.
Conceptually:
Heap
│
┌──────────┼──────────┐
↓ ↓ ↓
Thread 1 Thread 2 Thread 3
│ │ │
└──────────┼──────────┘
↓
shared objects
This is important when learning multithreading because multiple threads can access objects stored in shared memory.
The notes describe Heap memory as dynamic in nature.
They mention:
-Xms → minimum space
-Xmx → maximum space
These are JVM heap-related configuration options.
Conceptually:
Heap
│
├── Minimum size → -Xms
└── Maximum size → -Xmx
The page 6 diagram explicitly labels -Xms as minimum space and -Xmx as maximum space.
The Heap is the main area associated with Garbage Collection.
Conceptually:
Heap
│
├── Reachable objects
│ ↓
│ still needed
│
└── Unreachable objects
↓
eligible
↓
GC
↓
reclaimed
The page 5 notes explicitly show Garbage Collection associated with Heap memory.
An object becomes eligible for garbage collection when it is no longer reachable from active references.
Example:
Student s1 = new Student();
s1 = null;Initially:
s1 ─────→ Student object
After:
s1 = null;the reference becomes:
s1 ─────→ null
If there are no other references to the object:
Student object
↑
no references
it becomes eligible for GC.
The page 7 diagram illustrates this exact idea: a Student object becomes eligible for GC after the reference is set to null.
Example:
Student s1 = new Student();
s1 = null;Before:
Stack/reference
│
↓
Student
object
After:
s1 → null
Student object
↓
no reachable reference
↓
eligible for GC
Important:
s1 = nulldoes not directly delete the object.
It only removes that particular reference.
The Garbage Collector decides when the object's memory is actually reclaimed.
This becomes more interesting when multiple references point to the same object.
Suppose:
Student s1 = new Student();
Student s2 = s1;Conceptually:
s1 ─────┐
├────→ Student object
s2 ─────┘
Now:
s1 = null;gives:
s1 → null
s2 ─────→ Student object
The object is still reachable through s2.
Therefore:
Not eligible for GC
The source describes Heap memory as behaving conceptually like a graph because objects can contain references to other objects.
Example:
class A {
B b;
}and:
A a = new A();
a.b = new B();Conceptually:
Heap
[A object]
│
│ b
↓
[B object]
With more objects:
┌────→ Object B
│
Object A
│
└────→ Object C
│
↓
Object D
This graph-like view is useful for understanding reachability and Garbage Collection.
The Method Area is used for information associated with loaded classes.
The source describes it as the class-level blueprint / metadata area.
The page 3 diagram shows:
Method Area
↓
Class-level blueprint / metadata
Suppose we have:
class Student {
String name;
int age;
void display() {
// ...
}
}The JVM needs information about this class:
Student
├── class information
├── fields
├── methods
└── bytecode-related information
This class-level information is associated with the Method Area.
Think of a class as a blueprint.
Class definition
↓
Student
├── name
├── age
└── display()
When the JVM loads the class, it needs to know this structure.
The Method Area serves as a repository for this class-level information.
The source describes it as a place where the JVM stores the class definition/metadata so it knows the blueprint of objects it can create.
The source identifies information such as:
- Class metadata
- Class definitions
- Static variables
- Methods
- Bytecode-related information
- Constants
The page 3 notes specifically associate methods such as m1() and m2() with return types and bytecode, and identify static variables as class-level information.
Java source code:
class Demo {
public static void main(String[] args) {
int x = 5;
}
}is compiled into bytecode.
Conceptually:
Demo.java
↓
javac
↓
Demo.class
↓
Bytecode
↓
JVM
The Method Area is associated with the class information and bytecode needed by the JVM.
Static variables belong to the class rather than to a particular object.
Example:
class Student {
static String college = "ABC College";
}Conceptually:
Student class
│
└── static college
The source associates static variables with the Method Area/class-level information.
This distinction is important.
Think:
"How is the class defined?"
Think:
"Where are the actual objects?"
Example:
Student s1 = new Student();Conceptually:
Method Area
┌───────────────────┐
│ Student class │
│ fields │
│ methods │
│ metadata │
└───────────────────┘
Heap
┌───────────────────┐
│ Student object │
│ name = "Raquib" │
│ age = 20 │
└───────────────────┘
The Stack is associated with method execution.
The source describes it as:
- LIFO
- Execution memory
- Thread-specific
The page 3 diagram explicitly labels Stack Memory as LIFO and Execution Memory.
LIFO means:
Last In, First Out
Think of a stack of plates:
┌───────┐
│ C │ ← added last
├───────┤
│ B │
├───────┤
│ A │ ← added first
└───────┘
C comes out first.
The Java execution stack follows the same basic model.
This is one of the most important concepts.
Suppose:
public static void main(String[] args) {
int a = 5;
int b = 7;
}When main() executes, a stack frame is created.
Conceptually:
Stack
┌─────────────────┐
│ main() frame │
│ a = 5 │
│ b = 7 │
└─────────────────┘
The page 3 notes explicitly connect method execution with stack frames.
A stack frame represents the execution context of a method.
The source notes associate it with information such as:
- Local variables
- Method parameters
- Reference variables
- Return address
Think:
Stack Frame
├── Local variables
├── Parameters
├── References
└── Return information
The page 4 notes explicitly list these four categories under Stack Memory in Depth.
Consider:
void main() {
int a = 10;
int result = add(10, 20);
}
int add(int x, int y) {
int sum = x + y;
return sum;
}Execution:
main()
↓
add()
The stack becomes:
┌────────────────────┐
│ add() frame │
│ x = 10 │
│ y = 20 │
│ sum = 30 │
├────────────────────┤
│ main() frame │
│ a = 10 │
│ result │
└────────────────────┘
The add() frame is on top because it was called later.
When add() returns:
add()
↓
return
↓
frame removed
The stack becomes:
┌────────────────────┐
│ main() frame │
│ a = 10 │
│ result = 30 │
└────────────────────┘
The page 5 diagram visually shows the add() frame appearing above main() and then being removed when the method completes.
The source makes an important observation:
Stack memory is automatically cleaned as methods return.
There is no need for Garbage Collection to remove a completed stack frame.
Why?
Because the stack follows method execution.
Method starts
↓
Frame created
↓
Method executes
↓
Method returns
↓
Frame popped
Stack operations are generally very efficient because the structure is simple and follows LIFO.
Conceptually:
Push frame
↓
Execute method
↓
Pop frame
There is no need to search through arbitrary locations to remove a method frame.
The source describes Stack Memory as very fast and LIFO-based.
Each thread has its own execution stack.
Conceptually:
Thread 1
↓
Stack 1
Thread 2
↓
Stack 2
Thread 3
↓
Stack 3
This is different from Heap memory, which is shared across threads.
The page 2 diagram explicitly groups Stack Memory and the Program Counter as thread-specific, while Heap and Method Area are shown as thread-shared.
| Stack | Heap |
|---|---|
| Thread-specific | Shared across threads |
| Method execution | Objects/data |
| LIFO | Dynamic object storage |
| Contains stack frames | Contains objects/arrays |
| Local variables and parameters | Instance variables |
| Automatically cleaned as frames pop | Managed by GC |
| Very fast | Generally slower than Stack |
| Limited size | Dynamically managed |
Stack memory has a limited size.
If too many stack frames are created, the stack can become full.
This results in:
StackOverflowError
The most common example is infinite recursion.
Consider:
void f() {
f();
}There is no base case.
Execution becomes:
f()
↓
f()
↓
f()
↓
f()
↓
f()
↓
...
Each call creates another stack frame:
Stack
┌──────────┐
│ f() │
├──────────┤
│ f() │
├──────────┤
│ f() │
├──────────┤
│ f() │
├──────────┤
│ ... │
└──────────┘
Eventually the stack limit is reached.
Stack full
↓
StackOverflowError
The page 5 notes explicitly identify recursion without a base case as a common cause.
The Program Counter tracks the current instruction being executed for a thread.
Think:
Program instructions:
1. load value
2. calculate
3. call method
4. return
5. print result
The PC conceptually tracks where execution currently is:
1 → 2 → 3 → 4 → 5
↑
PC
The page 3 notes label the Program Counter as the current instruction and show it as one of the JVM memory areas.
Each thread has its own execution position.
Conceptually:
Thread 1 → PC → instruction 10
Thread 2 → PC → instruction 25
Thread 3 → PC → instruction 7
This is why the PC belongs to the thread-specific side of the memory model.
Based on the diagrams in the uploaded notes:
JVM MEMORY
│
┌──────────────────┼──────────────────┐
│ │ │
↓ ↓ ↓
HEAP METHOD AREA STACK
│ │ │
Objects Class metadata Frames
Arrays Methods Local vars
Instance vars Static data Parameters
Strings Bytecode References
│ │ │
│ │ Thread-specific
│ │
Shared Shared
│ │
└──────────┐ ┌────┘
│ │
↓ ↓
Program Counter
│
Thread-specific
The exact page 2 sketch groups Heap and Method Area as shared, and Stack and Program Counter as thread-specific.
Consider:
public class Demo {
public static void main(String[] args) {
int x = 5;
Student s1 = new Student();
s1.name = "Raquib";
}
}The source's page 4 example uses a similar Demo class with:
int x = 5;
Student s1 = new Student();
s1.name = "..."A simplified memory model is:
Method Area
┌─────────────────────────┐
│ Demo class │
│ Student class │
│ main() bytecode │
│ class metadata │
└─────────────────────────┘
Stack
┌─────────────────────────┐
│ main() frame │
│ x = 5 │
│ s1 ─────────────────────┼─────┐
└─────────────────────────┘ │
↓
Heap Student object
┌──────────────────────────────────────┐
│ name = "Raquib" │
│ age = ... │
└──────────────────────────────────────┘
PC
┌─────────────────────────┐
│ current instruction │
└─────────────────────────┘
This is a simplified conceptual model, but it is useful for understanding the roles of the different memory areas.
The page 4 notes describe a simplified startup sequence:
1. JVM starts
↓
2. Class loading
↓
3. Class loader
↓
Read .class file
↓
Read bytecode
↓
Create Method Area entry
↓
4. main() method gets executed
↓
Stack + PC involved
When the Java program starts, the JVM begins preparing the runtime environment.
The JVM needs the class definition before executing it.
The Class Loader loads the required class.
Conceptually:
Demo.class
↓
Class Loader
↓
Class information
↓
Method Area
After the necessary class information is available, main() can execute.
This requires:
main()
↓
Stack frame
+
Program Counter
The page 4 diagram explicitly shows the main method getting a Stack and PC context.
Inside main():
Student s1 = new Student();Conceptually:
main() stack frame
│
│ s1
↓
Student object
↓
Heap
The object itself is dynamically created in the Heap.
A very important mental model is:
Reference
│
│
Stack
│
└──────────────→ Heap object
For example:
Student s1 = new Student();Think:
Stack Heap
s1 ────────────────────────→ Student object
The reference allows the executing code to reach the object.
This distinction prevents many memory-model misunderstandings.
In:
Student s1 = new Student();there are conceptually two things:
s1
↓
reference
new Student()
↓
object
The object and reference are not the same thing.
Garbage Collection is based on whether objects remain reachable.
Suppose:
Student s1 = new Student();Then:
s1
↓
Student
The object is reachable.
If:
s1 = null;then:
s1 → null
Student
↑
no reference
Assuming no other references exist, the object becomes eligible for GC.
An object can also become unreachable when the stack frame containing its only reference disappears.
For example:
void createStudent() {
Student s1 = new Student();
}During execution:
Stack
┌────────────────────┐
│ createStudent() │
│ s1 ────────────────┼────→ Student object
└────────────────────┘
After the method returns:
createStudent() frame
↓
removed
If s1 was the only reference:
Student object
↓
unreachable
↓
eligible for GC
The source explicitly connects object eligibility with references held by stack frames.
This distinction is important.
When an object becomes unreachable:
Object unreachable
↓
Eligible for GC
It does not mean:
Object immediately deleted
Instead, the Garbage Collector can reclaim its memory when appropriate.
The source connects OutOfMemoryError with the Heap.
Conceptually:
Heap
↓
Memory keeps getting occupied
↓
Available memory exhausted
↓
OutOfMemoryError
The page 2 notes explicitly label OutOfMemoryError with Heap.
Similarly:
Stack
↓
Too many stack frames
↓
Stack limit exceeded
↓
StackOverflowError
The page 2 and page 5 notes associate this with Stack memory and recursion without a base case.
| Problem | Memory Area | Typical Cause |
|---|---|---|
OutOfMemoryError |
Heap | Heap cannot provide enough memory |
StackOverflowError |
Stack | Excessive stack frames, often infinite recursion |
Easy memory trick:
Heap full
↓
OutOfMemoryError
Stack too deep
↓
StackOverflowError
The source notes state that Heap memory is usually slower than Stack.
The reason to remember at this level is that Stack allocation/deallocation follows a very simple LIFO model:
push frame
↓
execute
↓
pop frame
Heap management is more dynamic and involves object allocation and Garbage Collection.
So for revision:
Stack → fast, structured, LIFO
Heap → dynamic, object-oriented, GC-managed
Different data has different lifetimes.
Generally tied to method execution:
Method starts
↓
Frame created
↓
Method ends
↓
Frame removed
Can live much longer:
Object created
↓
May remain reachable
↓
May survive across many method calls
↓
Eventually becomes unreachable
↓
Eligible for GC
The page 5 notes illustrate Heap data potentially living for:
milliseconds
minutes
program end
depending on reachability and program behavior.
Suppose:
class Demo {
static Student student;
public static void main(String[] args) {
student = new Student();
}
}The object can remain reachable through the static reference for a long time.
This illustrates why reachability, not simply "which method created the object," matters for GC.
The source associates static variables with class-level information in the Method Area.
Conceptually:
Class
↓
static variable
↓
class-level lifetime
This differs from ordinary local variables that belong to a method's stack frame.
Consider:
class Student {
String name;
int age;
}
void method() {
int x = 10;
Student s = new Student();
}There are different categories:
x
↓
local variable
↓
associated with method execution
s
↓
reference variable
↓
associated with current execution context
new Student()
↓
object
↓
Heap
name / age
↓
instance variables
↓
part of Student object
The page 6 notes specifically distinguish objects, instance variables, arrays, and strings in the Heap discussion.
Let's put everything together.
class Student {
String name;
int age;
Student(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Demo {
public static void main(String[] args) {
int x = 5;
Student s1 =
new Student("Raquib", 20);
Student s2 = s1;
s1 = null;
}
}Conceptually:
Stack
┌─────────────────────┐
│ main() │
│ x = 5 │
│ s1 ────────────┐ │
└────────────────│────┘
│
↓
Heap ┌───────────────┐
│ Student │
│ name = Raquib │
│ age = 20 │
└───────────────┘
Then:
Student s2 = s1;Now:
s1 ─────┐
├────→ Student
s2 ─────┘
Then:
s1 = null;Now:
s1 → null
s2 ─────→ Student
The object is still reachable through s2.
Therefore:
Not eligible for GC yet
The source emphasizes that automatic GC does not remove the need for good coding practices.
Think about:
Instead of repeatedly creating objects you do not need:
Create
Create
Create
Create
...
reuse or release references appropriately when suitable.
If a collection continues holding objects that are no longer needed:
Collection
↓
unused objects
↓
still reachable
↓
memory remains occupied
Ask:
"How long does this object actually need to remain reachable?"
This mindset helps prevent memory-related problems.
False.
Java has automatic memory management, handled by the JVM and Garbage Collector.
Not exactly.
The correct sequence is:
No longer reachable
↓
Eligible for GC
↓
GC may reclaim memory
Not directly.
s1 = null;only removes that reference.
If another reference exists:
s2 = s1;then the object remains reachable.
False.
Objects and arrays are associated with Heap memory.
Stack memory primarily handles method execution and frames.
Also false.
Method execution uses Stack frames, and the JVM has other memory areas such as the Method Area and Program Counter.
False.
The source identifies Stack memory as thread-specific.
The source identifies Heap as shared across threads.
No.
StackOverflowError → Stack problem
OutOfMemoryError → Heap memory exhaustion problem
It can become a memory problem too.
Every recursive call creates another stack frame:
recursion
↓
more frames
↓
stack fills
↓
StackOverflowError
They are not.
s1
↓
reference
new Student()
↓
object
Keeping this distinction clear is essential for understanding Garbage Collection.
The JVM manages memory allocation, usage, and reclamation for the running Java program.
The Garbage Collector.
The memory area used for dynamically created objects, arrays, and instance data.
Yes, according to the source.
The class-level area containing class metadata, methods/bytecode-related information, static data, and constants.
Thread-specific execution memory containing stack frames for method calls.
Last In, First Out.
An execution context created for a method call, containing information such as local variables, parameters, references, and return information.
When the corresponding method finishes/returns.
No. Stack frames are removed automatically as method execution completes.
It tracks the current instruction being executed for a thread.
Excessive stack usage, commonly infinite recursion without a base case.
Insufficient available memory, with the source specifically associating it with the Heap.
When it becomes unreachable.
No. It only removes that reference. Other references may still keep the object reachable.
The minimum heap space/configured heap size mentioned in the source.
The maximum heap space/configured heap size mentioned in the source.
-
JVM memory management involves allocation, use/update, and deallocation/reclamation.
-
Java uses Garbage Collection instead of requiring programmers to manually free objects.
-
Automatic GC does not mean memory usage can be ignored.
-
Unnecessary object creation and object retention can cause memory pressure, leaks, and performance degradation.
-
JVM memory is divided into several areas.
-
The source focuses on:
- Heap
- Method Area
- Stack
- Program Counter
-
Heap
- stores objects,
- arrays,
- instance data,
- is shared across threads,
- is managed by GC,
- is dynamic in nature.
-
Method Area
- stores class-level information,
- class metadata,
- methods/bytecode-related information,
- static data,
- constants.
-
Stack
- is thread-specific,
- follows LIFO,
- contains method frames,
- contains local variables, parameters, references, and return information,
- is automatically cleaned when methods return.
-
Program Counter
- tracks the current instruction for a thread.
-
Every method call creates a Stack Frame.
-
Every completed method removes its frame from the Stack.
-
Infinite recursion can create too many frames and cause:
StackOverflowError
- Heap exhaustion can cause:
OutOfMemoryError
- Objects are eligible for GC when they are no longer reachable.
- Setting a reference to
nullcan make an object eligible for GC if no other references exist. - Multiple references can keep an object alive.
- Heap can be understood as a graph of objects and references.
- A useful high-level model is:
Stack
↓
references
↓
Heap
↓
objects
while:
Method Area
↓
class blueprint / metadata
and:
Program Counter
↓
current instruction
- What are the three basic responsibilities of JVM memory management?
- How is Java memory management different from manual memory management in C/C++?
- Why can Java applications still suffer from memory leaks?
- What is the difference between an unreachable object and an immediately deleted object?
- What are the major JVM memory areas discussed in the source?
- Which JVM memory areas are shared across threads?
- Which JVM memory areas are thread-specific?
- What is stored in Heap memory?
- What is stored in the Method Area?
- What is a class-level blueprint?
- What is bytecode?
- What is the purpose of the Method Area?
- What is Stack memory used for?
- What does LIFO mean?
- What is a Stack Frame?
- What happens to a Stack Frame when a method returns?
- What information can a Stack Frame contain?
- Why doesn't the Stack require Garbage Collection for normal frame cleanup?
- Why is Stack memory generally fast?
- Why is Stack memory thread-specific?
- What does the Program Counter track?
- Why does infinite recursion cause
StackOverflowError? - What is the difference between
StackOverflowErrorandOutOfMemoryError? - What does
-Xmsrepresent? - What does
-Xmxrepresent? - What makes an object eligible for Garbage Collection?
- Does
s1 = nullimmediately delete an object? - What happens if
s1ands2point to the same object ands1 = null? - Why is Heap memory described as a graph?
- What is the difference between a reference variable and an object?
- What happens when a method's only reference to an object disappears with its stack frame?
- Why does automatic Garbage Collection not remove the need for memory-friendly code?