บทนำ: ปัญหาของ Memory Management
ใน Java ไม่ต้องสร้าง object ด้วยมือ แต่ต้องเข้าใจว่า object หายไปไหน
java// ❓ ปัญหา: Object ไปไหนหลังจาก method จบ?
public class MemoryDemo {
public static void main(String[] args) {
Student student = new Student("John", 3.75);
System.out.println("Student created");
// ✓ ใช้งาน student
student.displayInfo();
} // ← student ไปไหน? ใครเอาไป?
}
// ✓ ใน Java: Garbage Collector เอาไป
// ❌ ใน C: ต้อง delete เอง (ลืม → Memory Leak)
ปัญหา:
- ❓ Object ถูก deallocate เมื่อไหร่?
- ❓ ใครเอาไป?
- ❓ Memory leak เกิดได้ไหม?
- บทนำ: ปัญหาของ Memory Management
- Java Memory Model
- Memory Areas ใน Java
- Heap vs Stack
- ความต่างกัน
- ตัวอย่างที่ 1: Stack vs Heap
- Visual: Memory Flow
- Reference dan Garbage Collection
- ประเภท
- Object Lifecycle
- ลำดับการเกิดและตายของ Object
- Garbage Collection (GC)
- GC คืออะไร?
- ตัวอย่างที่ 2: Object Lifecycle
- ตัวอย่างที่ 3: Variable Scope
- ตัวอย่างที่ 4: Collections and GC
- Memory Leak ใน Java
- Scenario 1: Static Reference
- Scenario 2: Circular Reference (ไม่เป็น problem ใน Java)
- Scenario 3: Event Listeners
- finalize() Method
- ❓ ทำไมต้อง finalize()?
- ⚠️ ปัญหาของ finalize()
- ตัวอย่างที่ 5: try-with-resources
- Garbage Collection Algorithms
- Algorithm ที่สำคัญ
- Best Practices: Memory Management
- 1. Nullify References ที่ไม่ต้อง
- 2. Avoid Object Creation ใน Loop
- 3. Use StringBuilder ไม่ String Concatenation
- 4. Remove Event Listeners
- 5. Close Resources
- ตัวอย่างสุดท้าย: Memory-Efficient Design
- สรุป: Java Memory & Garbage Collection
Java Memory Model
Memory Areas ใน Java
text┌──────────────────────────────────────────────────┐
│ JVM Memory │
├──────────────────────────────────────────────────┤
│ │
│ ┌─ Heap │
│ │ └─ Objects, Arrays (Garbage Collected) │
│ │ • String literals │
│ │ • Objects ทั่วไป │
│ │ • Collections (List, Map) │
│ │ │
│ ├─ Stack │
│ │ └─ Local variables, Method calls (Auto) │
│ │ • Primitive types (int, double) │
│ │ • Object references │
│ │ • Freed when method returns │
│ │ │
│ ├─ Method Area / Metaspace │
│ │ └─ Class structures, Method data │
│ │ • Permanent until JVM stops │
│ │ │
│ └─ Others (Program Counter, Native Stack) │
│ │
└──────────────────────────────────────────────────┘
Heap vs Stack
ความต่างกัน
text┌──────────────────┬──────────────────────┬──────────────────────┐
│ │ Heap │ Stack │
├──────────────────┼──────────────────────┼──────────────────────┤
│ ขนาด │ ใหญ่ │ เล็ก │
│ ความเร็ว │ ช้า │ เร็ว │
│ เก็บอะไร │ Objects │ Primitives + refs │
│ Lifecycle │ Garbage Collected │ Auto freed │
│ Thread-safe │ ไม่ (shared) │ ใช่ (per-thread) │
│ Fragmentation │ ได้ │ ไม่ได้ │
└──────────────────┴──────────────────────┴──────────────────────┘
ตัวอย่างที่ 1: Stack vs Heap
javapublic class MemoryExample {
public static void main(String[] args) {
// STACK:
// - 'age' (int primitive)
// - 'name' (String reference)
int age = 25;
String name = "John";
// HEAP:
// - "John" (String object)
createStudent();
// After createStudent() returns:
// - local variables of createStudent() freed from Stack
// - Student object may still be in Heap (until GC)
}
public static void createStudent() {
// STACK:
// - 'student' (Student reference)
// - 'name' (String reference)
// - 'gpa' (double primitive)
String name = "Jane";
double gpa = 3.75;
Student student = new Student(name, gpa);
// HEAP:
// - Student object
// - "Jane" (String object)
} // ← Method ends
// After method ends:
// STACK: ทั้งหมด freed (student, name, gpa)
// HEAP: Student object + "Jane" string ยังอยู่
// (จนกว่า Garbage Collector มา)
}
Visual: Memory Flow
text┌─────────────┐
│ STACK │
├─────────────┤
│ age = 25 │
│ name ───┐ │
├─────────┼───┤
│ │ │
└─────────┼───┘
│
↓
┌──────────────┐
│ HEAP │
├──────────────┤
│"John"(String)│ ← name reference
│ │
│Student Object
│ "Jane"(String)
└──────────────┘
Reference dan Garbage Collection
ประเภท
java// 1. Strong Reference (ปกติ)
Student student = new Student("John", 3.75);
// ← Object ยังอยู่ ตราบเท่าที่ student ยังมี
// 2. Weak Reference
WeakReference<Student> ref = new WeakReference<>(student);
// ← Object สามารถถูก GC แม้มี weak reference
// 3. Soft Reference
SoftReference<Student> ref = new SoftReference<>(student);
// ← Object ถูก GC เมื่อ memory ต่ำ
// 4. Phantom Reference (advanced)
// ← ใช้ในการ cleanup
Object Lifecycle
ลำดับการเกิดและตายของ Object
text1. new Student("John", 3.75)
↓
2. Constructor ทำงาน
├─ allocate memory ใน Heap
├─ initialize fields
└─ return reference
↓
3. student reference ชี้ไป Object
(Object still referenced)
↓
4. student = null;
OR student ออกจาก scope
↓
5. Object ไม่มี strong reference
(Object unreachable)
↓
6. Garbage Collector วิ่งผ่าน
├─ detect unreachable
└─ free memory
↓
7. Memory freed
(Object gone)
Garbage Collection (GC)
GC คืออะไร?
Garbage Collection = ระบบอัตโนมัติ ที่ ลบ unused objects และ ปลดปล่อย memory
textใน Java:
- GC ทำงาน automatic (ไม่ต้องเขียนเองอย่าง C++ delete)
- เมื่อ object ไม่มี strong reference
- GC หาเจอ → ลบ → free memory
ตัวอย่างที่ 2: Object Lifecycle
javapublic class GCDemo {
public static void main(String[] args) {
System.out.println("Start");
// Step 1: Create object
Student s1 = new Student("John");
System.out.println("s1 created"); // Object in Heap
// Step 2: Create another reference
Student s2 = s1;
System.out.println("s2 = s1"); // Same object, 2 references
// Step 3: Set s1 to null
s1 = null;
System.out.println("s1 = null"); // Object still exists (s2 references)
// Step 4: Set s2 to null
s2 = null;
System.out.println("s2 = null"); // Object unreachable!
// Step 5: GC runs (sometime later)
System.gc(); // Suggest GC (ไม่บังคับ)
System.out.println("GC suggested"); // Object removed
System.out.println("End");
}
}
public class Student {
private String name;
public Student(String name) {
this.name = name;
}
// Called before GC removes object
@Override
protected void finalize() throws Throwable {
System.out.println("Finalize called for: " + name);
super.finalize();
}
}
ตัวอย่างที่ 3: Variable Scope
javapublic class ScopeDemo {
public static void main(String[] args) {
demonstrateScope();
// ← ทุก objects ใน demonstrateScope() ควรถูก GC
}
public static void demonstrateScope() {
// SCOPE 1: main method
Student s1 = new Student("John");
{
// SCOPE 2: block
Student s2 = new Student("Jane");
Student s3 = new Student("Bob");
} // ← s2, s3 ออกจาก scope
// ← s2, s3 unreachable, eligible for GC
// s1 still referenced
} // ← main method ends
// ← s1 ออกจาก scope, unreachable
// ← All objects eligible for GC
}
ตัวอย่างที่ 4: Collections and GC
javaimport java.util.ArrayList;
import java.util.List;
public class CollectionGCDemo {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
// Add students
students.add(new Student("John"));
students.add(new Student("Jane"));
students.add(new Student("Bob"));
System.out.println("Added 3 students");
// Remove student
students.remove(0);
System.out.println("Removed 1 student");
// ← First Student object now eligible for GC
// Clear all
students.clear();
System.out.println("Cleared all students");
// ← All Student objects eligible for GC
// Remove reference to list
students = null;
System.out.println("List = null");
// ← ArrayList object itself eligible for GC
}
}
Memory Leak ใน Java
Scenario 1: Static Reference
java// ⚠️ Memory Leak: Static collection ไม่เคยลบ
public class MemoryLeakExample1 {
private static List<Student> students = new ArrayList<>();
public void addStudent(Student student) {
students.add(student); // ← ถ้าไม่ clear → leak!
}
}
// ✓ ปรับปรุง:
public class Fixed1 {
private List<Student> students = new ArrayList<>();
// ← instance variable (ไม่ static)
public void addStudent(Student student) {
students.add(student);
}
public void clear() {
students.clear(); // ← explicit clear
}
}
Scenario 2: Circular Reference (ไม่เป็น problem ใน Java)
java// ✓ ใน Java: Circular reference ไม่ป problem
// Generational GC ล clean-up ได้
public class Node {
Node next;
Node prev;
}
// Create circular
Node n1 = new Node();
Node n2 = new Node();
n1.next = n2;
n2.prev = n1;
n1.next.next = n1; // Circular
// Set to null
n1 = null;
n2 = null;
// ✓ Java GC ยังเห็นเป็น unreachable → clean up
// ❌ ใน C++: Circular reference → memory leak!
Scenario 3: Event Listeners
java// ⚠️ Memory Leak: Listener ไม่เคย unregister
public class Button {
private List<ClickListener> listeners = new ArrayList<>();
public void addClickListener(ClickListener l) {
listeners.add(l);
}
}
public class Window {
private Button button = new Button();
public Window() {
button.addClickListener(new ClickListener() {
@Override
public void onClick() {
System.out.println("Clicked");
}
});
// ← Listener ไม่เคย remove → Window ไม่สามารถ GC!
}
}
// ✓ ปรับปรุง:
public class FixedWindow {
private Button button = new Button();
public FixedWindow() {
button.addClickListener(new ClickListener() {
@Override
public void onClick() {
System.out.println("Clicked");
}
});
}
public void cleanup() {
button.removeAllListeners(); // ← explicit cleanup
}
}
finalize() Method
❓ ทำไมต้อง finalize()?
javapublic class Resource {
private FileInputStream file;
public Resource(String filename) throws IOException {
file = new FileInputStream(filename);
}
// Called before GC removes object
@Override
protected void finalize() throws Throwable {
try {
if (file != null) {
file.close(); // ← Ensure cleanup
}
} finally {
super.finalize();
}
}
}
⚠️ ปัญหาของ finalize()
text1. ไม่มีประกาศว่า finalize ทำงานเมื่อไหร่
2. GC delay → resource ยังไม่ปล่อย
3. finalize() exception → ปัญหา
4. Performance impact (GC ต้อง track)
✓ ดีกว่า: ใช้ try-with-resources หรือ close() explicit
ตัวอย่างที่ 5: try-with-resources
javaimport java.io.*;
// ⚠️ Old style (manual close)
public class OldStyle {
public static void readFile(String filename) throws IOException {
FileInputStream file = new FileInputStream(filename);
try {
// use file
} finally {
file.close(); // ← Must explicitly close
}
}
}
// ✓ New style (auto close)
public class NewStyle {
public static void readFile(String filename) throws IOException {
try (FileInputStream file = new FileInputStream(filename)) {
// use file
// ← Automatically closed!
}
}
}
// ✓ Multiple resources
public class MultipleResources {
public static void process() throws IOException {
try (
FileInputStream in = new FileInputStream("input.txt");
FileOutputStream out = new FileOutputStream("output.txt")
) {
// use both resources
// ← Both automatically closed!
}
}
}
Garbage Collection Algorithms
Algorithm ที่สำคัญ
text1. MARK-AND-SWEEP (Traditional)
├─ Mark: ทำเครื่องหมาย reachable objects
├─ Sweep: ลบ unreachable objects
└─ Compact: ยุบ memory
2. GENERATIONAL GC (Modern)
├─ Young Generation: objects ใหม่
├─ Old Generation: objects เก่า
├─ Minor GC: clean young (บ่อย)
└─ Major GC: clean old (ไม่บ่อย)
3. G1GC (Garbage First)
├─ Divide heap to regions
├─ Mark regions with garbage
└─ Clean garbage first
Best Practices: Memory Management
1. Nullify References ที่ไม่ต้อง
java// ✓ ดี: Clear when done
public void processStudents(List<Student> students) {
for (Student s : students) {
process(s);
}
students = null; // ← Explicit nullify
}
// ⚠️ ไม่จำเป็น: ถ้า variable ออกจาก scope
public void example() {
Student s = new Student("John");
use(s);
// s = null; // ← ไม่จำเป็น, method จบแล้ว
}
2. Avoid Object Creation ใน Loop
java// ❌ ไม่ดี: Object created in every iteration
for (int i = 0; i < 1000000; i++) {
Student student = new Student("Student " + i);
process(student);
// ← 1 million objects created (GC pressure)
}
// ✓ ดี: Reuse object
Student student = new Student("");
for (int i = 0; i < 1000000; i++) {
student = new Student("Student " + i);
process(student);
// ← Same, but logical grouping
}
// ✓ ดีที่สุด: Object pool (if needed)
class StudentPool {
private Queue<Student> available = new LinkedList<>();
Student acquire() {
return available.isEmpty() ?
new Student("") : available.poll();
}
void release(Student s) {
available.offer(s);
}
}
3. Use StringBuilder ไม่ String Concatenation
java// ❌ ไม่ดี: Creates new String each time
String result = "";
for (int i = 0; i < 1000; i++) {
result += "Item " + i + ", ";
// ← 1000 String objects created
}
// ✓ ดี: Use StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append("Item ").append(i).append(", ");
// ← 1 StringBuilder object
}
String result = sb.toString();
4. Remove Event Listeners
java// ✓ ดี: Remove when done
button.addClickListener(listener);
// ... later ...
button.removeClickListener(listener); // ← Remove
// ⚠️ ไม่ดี: Listener ยังอยู่ → Memory leak
button.addClickListener(listener);
// Listener stays forever
5. Close Resources
java// ✓ ดี: Use try-with-resources
try (FileInputStream file = new FileInputStream("test.txt")) {
// use file
} // ← Auto closed
// ⚠️ ไม่ดี: Resource leak
FileInputStream file = new FileInputStream("test.txt");
// Forgot to close
ตัวอย่างสุดท้าย: Memory-Efficient Design
javaimport java.util.ArrayList;
import java.util.List;
public class EffectiveMemoryDemo {
public static void main(String[] args) {
System.out.println("=== Processing Students ===");
// Process students efficiently
try (StudentProcessor processor = new StudentProcessor()) {
for (int i = 0; i < 100; i++) {
Student student = new Student("Student " + i, 3.5 + (i % 5));
processor.process(student);
}
System.out.println("Total processed: " + processor.getCount());
}
System.out.println("=== Done ===");
}
}
class StudentProcessor implements AutoCloseable {
private int count = 0;
private StringBuilder log = new StringBuilder();
public void process(Student student) {
count++;
log.append(student.getName()).append(", ");
// Process student...
}
public int getCount() {
return count;
}
@Override
public void close() {
// Cleanup resources
System.out.println("Processed students: " + log);
log = null; // ← Release
System.out.println("Resources cleaned up");
}
}
class Student {
private String name;
private double gpa;
public Student(String name, double gpa) {
this.name = name;
this.gpa = gpa;
}
public String getName() {
return name;
}
}
สรุป: Java Memory & Garbage Collection
ในการเขียนโปรแกรม Java เราไม่ต้องกังวลเกี่ยวกับการปลดปล่อย memory ด้วยตัวเอง เช่นในภาษา C++ ที่ต้องใช้ delete เพราะ Garbage Collector จะทำหน้าที่ดูแลให้อัตโนมัติ
อย่างไรก็ตาม การเข้าใจ Java Memory Model ช่วยให้เขียนโปรแกรมที่มีประสิทธิภาพ เรารู้ว่า objects ถูกเก็บไว้ที่ไหน (Heap) และ references ถูกเก็บไว้ที่ไหน (Stack) นอกจากนี้ การเข้าใจ object lifecycle ยังช่วยเราหลีกเลี่ยง memory leaks และเขียนโค้ดที่ปลอดภัยและมีประสิทธิภาพ
ควรจำไว้ว่า Garbage Collection ทำงานอัตโนมัติ แต่ ทีมงานของเรา ยังคงมีความรับผิดชอบในการออกแบบ class ให้เหมาะสม การใช้ resources อย่างคุณค่า และการเพิ่มประสิทธิภาพของโปรแกรม โดยใช้เทคนิกต่างๆ เช่น try-with-resources สำหรับการจัดการ files หรือ resources อื่นๆ รวมถึงการหลีกเลี่ยงการสร้าง objects ที่ไม่จำเป็นในลูป
ด้วยการศึกษาเรื่องเหล่านี้ คุณจะสามารถเขียนโปรแกรม Java ที่ทั้งถูกต้อง ปลอดภัย และมีประสิทธิภาพได้
