01. 객체 지향
1. 객체(Object)
프로그래밍 세계에서의 객체란 현실 세계에서 인지 가능한 모든 대상을 의미한다. 무생물 뿐만 아니라 사람, 동물, 개념이나 사상까지 표현할 수 있으며, 객체란 결국 특징(State)과 행위(Behavior)를 가진 독립적인 단위라고 볼 수 있다. 객체 지향 프로그래밍에서는 이러한 작은 객체들을 조합하여 하나의 프로그램을 완성한다.
2. 객체 지향 프로그래밍(Object-Oriented Programming, OOP)
객체 지향 프로그래밍은 "현실 세계는 수많은 객체로 이루어져 있으며, 객체 간 상호작용을 통해 사건이 발생한다"는 철학을 기반으로, 프로그래밍에 필요한 정보를 한 곳에 모아 상태와 행동을 가지는 객체를 만들고 이 객체들의 상호 협력을 통해 하나의 프로그램을 완성한다. 이러한 객체 지향 프로그래밍은 현실의 구조와 유사하게 상태와 동작을 구분하고 표현할 수 있기 때문에 이해와 유지보수가 쉽다.
| 객체 지향 프로그래밍의 중요한 개념 | |
| 객체 (Object) |
상태(state)오 행동(behavior)을 가진 독립적인 존재 |
| 클래스 (Class) |
객체를 만들기 위한 설계도 |
| 메시지 | 객체 간의 상호작용 방식 >> 메서드 호출 |
| 협력 | 여러 객체가 상호작용하며 프로그램을 구성 |
// 1. 클래스 Cat: 고양이 객체의 설계도
class Cat {
// 멤버 필드: 고양이 객체의 속성
String name;
// 메서드: 고양이 객체의 행동
void meow() {
System.out.println(name + "가 웁니다!");
}
}
3. 객체 지향적 사고방식(Object-Oriented Thinking)
기능의 구현을 중점으로 생각하는 전통적인 절차 지향적 사고방식과 다르게, 객체 지향은 기능의 담당을 중점으로 생각하는 사고방식이다. 즉, 객체 지향적 사고방식은 기능 중심적 사고가 아닌 책임 중식적 사고라고 할 수 있다. 이러한 사고방식은 객체의 역할과 책임이 명확한 코드 구조를 만들 수 있을 뿐만 아니라, 향후 유지보수에 유리하다는 장점이 있다.
| 객체 지향이 유리한 상황 | |
| 기능이 계속 추가되는 코드 | 설계 변경 없이 객체 추가만으로 확장 가능 |
| 여러 개발자가 동시에 개발 | 각자의 책임이 명확하여 충돌 없이 개발 가능 |
| 테스트 및 유지보수가 필요한 시스템 | 단위 객체 테스트가 가능하고, 코드 수정 범위가 제한됨 |
| 재사용 가능한 라이브러리나 프레임워크 | 객체 단위로 분리되어 다양한 상황에서 재활용 가능 |
| 절차 지향 (Procdural) |
객체 지향 (OOP) |
|
| 중심 | 순서와 함수 중심 | 객체의 역할과 책임 중심 |
| 재사용성 | 낮음 | 높음 |
| 유지보수 | 어렵고 반복 많음 | 유연하고 모듈화 쉬움 |
| 예시 언어 | C, 초기 Python | Java, Kotlin, C# |
// 1. 절차 지향적 코드
int a = 10;
int b = 5;
char op = '+';
if (op == '+') {
System.out.println(a + b);
} else if (op == '-') {
System.out.println(a - b);
} // ... 이후 모든 연산마다 else if 반복
// 2. 객체 지향적 코드
class Calculator {
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
}
class Application {
public static void main(String[] args) {
Calculator cal = new Calculator();
System.out.println(cal.add(10, 5));
}
}
02. 클래스와 객체
1. 클래스(Class)
클래스란 객체를 정의한 설계도(blueprint) 또는 틀(frame)이라 정의할 수 있다. 클래스는 객체를 생성하는데 사용되며, 반대로 객체는 클래스에 정의되고 설계된 내용 그대로 생성된다. 여기서 주의할 점은 클래스는 객체 그 자체가 아니라 단지 객체를 생성하는데 사용되는 하나의 틀이라는 것이다. 즉, 클래스는 객체 그 자체가 될 수 없다.
또한 이처럼 클래스를 통해 생성된 객체를 인스턴스(instance)라고 하며, 이러한 과정을 과정을 인스턴스화(instaninate)라고 한다. 즉, 인스턴스란 현실 세계의 사물이나 개념을 추상화 한 객체의 속성과 특징을 설계한 클래스를 통해 생성된 것으로, 모든 인스턴스는 객체이지만 모든 객체가 특정 클래스의 인스턴스는 아니다.

2. 클래스의 구성 요소
이러한 클래스는 class 키워드를 사용하여 정의하며, 클래스명은 대문자로 시작하는 것이 관례이다. 그 중 필드와 메서드는 각각 객체의 상태(state)와 행동(behavior)를 표현한다.
| 구성 요소 | |
| 필드 (Filed) |
객체가 가지는 속성 |
| 메서드 (Method) |
객체가 수행할 수 있는 동작 |
| 생성자 (Constructor) |
객체 생성 시 호출되는 특수한 메서드 |
| 이너 클래스 | 클래스 내부에 정의된 또다른 클래스 |
3. 클래스 vs 객체
| 클래스 (Class) |
객체 (Object) |
|
| 개념적 의미 | 객체를 추상화 한 설계도이자, 인스턴스를 생성하는 목적의 코드 단위 | 현실 세계의 사물이나 개념을 추상화 한 실체 |
| 문법적 의미 | 필드와 메서드를 정의하는 사용자 정의 자료형 | 클래스를 기반으로 생성된 메모리 상의 인스턴스 |
// 1. 클래스
class Person {
String name; // 필드 (상태)
void greet() { // 메서드 (행동)
System.out.println(name + "님, 반가워요!");
}
}
// 2. 객체
public class Main {
public static void main(String[] args) {
Person p = new Person(); // 객체 생성
p.name = "영수";
p.sayHello(); // 메시지 전달 (메서드 호출)
}
}
4. 객체 간의 협력과 책임
객체는 자신의 책임(Role)을 수행하고, 필요한 경우 다른 객체에게 메시지를 보내 협력한다.
class Order {
void pay(PaymentProcessor processor) {
processor.processPayment();
}
}
class PaymentProcessor {
void processPayment() {
System.out.println("결제를 처리합니다.");
}
}
// 사용 예시
Order order = new Order();
PaymentProcessor processor = new PaymentProcessor();
order.pay(processor); // Order가 PaymentProcessor에게 협력 요청
5. 객체의 생성
이러한 객체는 new 키워드를 활용하여 생성할 수 있다. 이때 참조 변수는 실제 데이터 값이 아닌 실제 데이터가 저장되어 있는 힙 메모리의 주소값을 가리키는데, 이는 new 키워드가 생성된 객체를 힙 메모리에 넣으라는 뜻을 가진 명령어로, 생성자를 통해 만들어진 객체를 힙 메모리에 넣는 역할을 수행하기 때문이다. 즉, new 키워드와 생성자를 통해 클래스와 객체를 생성한다는 것은 해당 객체를 힙 메모리에 넣고 그 주소값을 참조 변수에 저장하는 것과 같다.
| 클래스 영역 (Method Area) |
스택 영역 (Stack Area) |
힙 영역 (Heap Area) |
|
| 저장 대상 | 메서드 구현 코드를 포함한 클래스 자체 | 참조 변수 | 객체 인스턴스, 필드 |
| 특징 | 같은 클래스의 모든 인스턴스가 메서드 공유 >> 딱 한 번만 저장하기 위해 클래스 영역에 저장 |
힙 영역에 생성된 객체의 주소값 저장 | 고유한 필드 값 |

클래스명 참조_변수명; // 인스턴스를 참조하기 위한 참조변수 선언
참조_변수명 = new 생성자(); // 인스턴스 생성 후, 객체의 주소를 참조 변수에 저장
클래스명 참조_변수명 = new 생성자();
5. 객체의 활용
이렇게 생성한 객체는 해당 위치에 존재하는 특정 인스턴스 객체를 가리키는 역할을 수행하는 포인트 연산자(.)를 통해 객체 뿐만 아니라 내부의 필드와 메서드에도 쉽게 접근할 수 있다. 특히 메서드 호출은 객체 간 메시지 전달을 의미하며, 이를 통해 누가 누구에게 무엇을 요청하는지 해석할 수 있다.
참조 변수명.필드명 // 필드값 불러오기
참조 변수명.메서드명() // 메서드 호출
// 메서드 호출
order.pay(processor); // Order 객체가 PaymentProcessor 객체에게 결제를 요청
public class CarTest {
public static void main(String[] args) {
Car tesla = new Car("Model 3", "빨강");
System.out.println("내 차의 모델은 " + tesla.model + "이고 색은 " + tesla.color + "입니다.");
tesla.power();
tesla.accelerate();
tesla.stop();
}
}
class Car {
public String model;
public String color;
public Car(String model, String color) {
this.model = model;
this.color = color;
}
void power() {
System.out.println("시동을 걸었습니다.");
}
void accelerate() {
System.out.println("더 빠르게!");
}
void stop(){
System.out.println("멈춰!!");
}
}
03. 필드와 메서드
1. 필드(Field)
필드란 클래스에 포함된 변수로, 객체가 가지게 될 상태 데이터를 정의하기 위해 사용된다. 즉, 객체 지향 프로그램에서 필드란 클래스의 속성임과 동시에 인스턴스화 된 객체가 가지는 상태 데이터이다. 또한 변수는 크게 세 가지로 구분되는데, 이때 지역 변수를 제외한 클래스 변수와 인스턴스 변수가 필드에 해당한다.
| 클래스 변수 (Class Value, cv) |
인스턴스 변수 (Instance Value, iv) |
지역 변수 (Local Value, lv) |
|
| 정의 | 모든 인스턴스가 공유하는 변수 >> static 키워드로 단 한 번 생성 |
객체가 가질 고유한 특성을 저장하는 변수 >> new 생성자()로 각 객체가 별도로 생성 |
해당 블록 내에서만 유효한 변수 |
| 선언 위치 | 클래스 블록 내부 / 메서드 외부 | 클래스 블록 내부 / 메서드 외부 | 메서드 내부 >> 메서드 내에서만 접근 가능 |
| 정적(static) 여부 | 있음 >> 클래스명.변수명을 통해 접근 가능 |
없음 | 상관없음 |
| 메모리 영역 | 클래스 영역 | 힙 영역 | 스택 영역 |
| 초기화 | 자동 초기화 | 자동 초기화 | 직접 초기화 필요 |
| 생성 시점 | 클래스 로딩 시 | 객체 생성 시 | 메서드 호출 시 |
| 소멸 시점 | 프로그램 종료 시 | 객체가 가비지컬렉션(GC) 될 때 | 메서드 종료 시 |
class Example {
int instanceVariable; // 인스턴스 변수
static int classVariable; // 클래스 변수 (static 변수)
void method() {
int localVariable = 0; // 지역 변수
}
}
// 1. 인스턴스 변수 예시
class Person {
String name; // 인스턴스 변수
int age;
}
Person p1 = new Person(); // 객체 1
Person p2 = new Person(); // 객체 2
p1.name = "영수";
p2.name = "민지";
// 2. 클래스 변수 예시
class Counter {
int count = 0; // 인스턴스 변수
static int totalCount = 0; // 클래스 변수
void increment() {
count++;
totalCount++;
}
}
Counter c1 = new Counter();
Counter c2 = new Counter();
c1.increment();
c2.increment();
System.out.println(c1.count); // 1
System.out.println(c2.count); // 1
System.out.println(Counter.totalCount); // 2
// 3. 지역 변수 예시
class Test {
void doSomething() {
int temp = 10; // 지역 변수
System.out.println(temp);
}
}
2. 정적 선언 static
static 키워드는 필드, 메서드, 이너 클래스 등 클래스 멤버에 붙여 정적 멤버를 정의하는데 사용된다. 즉, static 키워드는 클래스가 처음 로딩되는 시점에 딱 한 번 실행되며, 생성자보다 먼저 정적 메모리 영역에 할당되는 멤버를 선언 및 할당한다. 정적으로 선언된 멤버는 별도로 객체를 생성하지 않아도 클래스명.변수명 형식으로 접근할 수 있으며, 해당 클래스를 기반으로 생성된 모든 인스턴스에게 공유된다.
static 키워드가 붙은 메서드도 마찬가지로 객체를 생성하지 않고도 호출이 가능한 정적 메서드가 된다. 하지만 다른 인스턴스 메서드와 달리 인스턴스가 없는 상태에서 호출되기 때문에 정적 메서드 내부에서 인스턴스 멤버를 사용하는 것은 불가능하다.
| 인스턴스 멤버 (Instance Member) |
정적 멤버 (Static Member) |
|
| 저장 위치 | 힙 영역 | 클래스 영역 |
| 공유 여부 | 공유하지 않음 >> 개체마다 독립적으로 생성되어 별도로 보유 |
클래스 기반으로 생성된 모든 인스턴스 간 공유 |
| 생성 시점 | 객체 생성 시 (new) | 클래스 로드 시 |
| 접근 방법 | 객체.멤버 | 클래스명.멤버 |
| 사용 예시 | 객체의 개별 상태 관리 | 공통 속성, 유틸리티 메서드 |

class StaticExample {
int num1 = 10; // 인스턴스 변수
static int num2 = -10; // 클래스 변수
}
// 1. 접근 방법 예제
public class StaticTest {
public static void main(String[] args) {
StaticExample staticExample = new StaticExample();
System.out.println("인스턴스 변수: " + staticExample.num1);
System.out.println("클래스 변수: " + StaticExample.num2);
}
}
// 2. 정적 필드 값 공유 예제
class StaticField {
int num1 = 10;
static int num2 = 15;
}
public class StaticFieldTest {
public static void main(String[] args) {
StaticField a = new StaticField();
StaticField b = new StaticField();
a.num1 = 100;
b.num1 = 200;
System.out.println(a.num1); // 100
System.out.println(b.num1); // 200
a.num2 = 300;
b.num2 = 400;
System.out.println(a.num2); // 400
System.out.println(b.num2); // 400
}
}
이러한 static 키워드는 프로그램 전역에서 단 하나의 인스턴스만 필요할 때 사용하는 디자인 패턴인 싱글톤(SingleTone) 패턴에 유용하게 사용된다. static을 사용하여 프로그램 전역에서 공유할 단 하나의 인스턴스를 생성하고, 외부에서 해당 클래스로부터의 객체 생성을 막는 것이다.
class Singleton {
private static Singleton instance = new Singleton(); // 단 하나의 인스턴스 생성
private Singleton() {} // 외부 생성 차단
public static Singleton getInstance() {
return instance;
}
}
// 이미 생성된 하나의 인스턴스를 반환 받아 사용
Singleton s = Singleton.getInstance();
2. 메서드(Method)
메서드란 특정 작업을 수행하는 일련의 명령문 집합(블록)으로, 객체의 행동(behavior)를 정의한다. 이러한 메서드는 클래스 내부에서 메서드명(인자 목록); 형태를 통해 호출하거나 클래스 외부에서는 생성한_인스턴스의_참조변수명.메서드(인자목록); 형태로 호출한다. 또한 이러한 인자는 메서드 정의에 있는 매개변수의 수와 타입에 정확하게 일치해야 한다.
| 메서드 구성 요소 | ||
| 메서드 시그니처 (Method Signature) |
메서드 이름 + 매개변수 (타입, 개수, 순서) | 반환 타입은 포함되지 않음 |
| 메서드 바디 (Method Body) |
실제로 수행할 작업들 | 반환 타입이 void가 아닌 경우, return 문을 통해 반환 타입과 동일한 값을 반환해야 함 |
자바제어자 반환타입 메서드명(매개변수) {
// 메서드 바디
}
// int 변수 2개를 매개변수로 받고, int 형 변수를 반환하는 정적 메서드 add
public static int add(int x, int y) {
int result = x + y;
return result;
}
메서드명(인자1, 인자2);
printHello(); // 출력: hello!
getNumSeven(); // 반환: 7
multiply(4, 4.0); // 반환: 16.0
4. 메서드 오버로딩 (Method Overloading)
메서드 오버로딩이란 동일한 이름의 메서드를 매개변수의 타입, 개수, 순서를 달리하여 여러 개 정의하는 것을 뜻한다. 이러한 메서드 오버라이딩은 동일한 기능을 가진 다양한 입력 타입에 직접 대응 가능할 뿐만 아니라 코드의 가독성을 높이고 재사용성을 향상 시킨다. 대표적으로 매개변수 타입에 따라 다양한 타입의 데이터를 출력할 수 있도록 설계된 System.out.println() 함수가 있다.
// 메서드 오버로딩 시그니처 예시 (반환 타입은 무관함)
public void test() {}
public void test(int num) {}
public void test(int num1, int num2) {}
public void test(int num, String name) {}
public void test(String name, int num) {}
// 메서드 오바로딩 예시
class Shape {
public void area() {
System.out.println("넓이");
}
public void area(int r) {
System.out.println("원 넓이 = " + 3.14 * r * r);
}
public void area(int w, int h) {
System.out.println("직사각형 넓이 = " + w * h);
}
public void area(double b, double h) {
System.out.println("삼각형 넓이 = " + 0.5 * b * h);
}
}
6. 가변 인자
가변 인자란 ... 문법으로 매개변수 선언부의 마지막에 선언하며, 매개변수의 개수가 유동적일 때 사용 가능하다. 이러한 가변 인자는 내부적으로 입력된 매개변수를 배열로 처리하여 유용하지만, 가변인자와 배열을 각각 매개변수로 하여 오버로딩 할 시 모호성(Ambiguity) 문제를 유발할 수 있다.
class Logger {
void log(String... messages) {
for (String msg : messages) {
System.out.println(msg);
}
}
}
Logger logger = new Logger();
logger.log("시작합니다.");
logger.log("DB 연결", "쿼리 실행", "연결 종료");
04. 생성자 (Constructor)
1. 생성자
생성자란 클래스의 인스턴스가 생성될 때 자동으로 호출되는 초기화 메서드이다. 생성자의 이름은 반드시 클래스명과 같아야 하며, 따로 반환 타입이 존재하지 않는다. 여기서 주의할 점은 위에서도 언급했듯, 인스턴스 생성 자체는 new 키워드가 담당하고 생성자는 new 키워드가 생성한 인스턴스의 초기화 역할을 수행하는 것이다. 즉, 생성자를 사용함으로써 객체를 생성함과 동시에 초기화 하는 것이 가능하다.
또한 컴파일러는 기본적으로 개발자가 생성자를 명시적으로 작성하지 않으면 매개변수가 없는 기본 생성자를 자동으로 추가한다. 하지만 개발자가 기본 생성자를 제외한 다른 생성자를 하나라도 정의한다면 기본 생성자는 자동으로 생성되지 않는다. 즉, 생성자 정의 없이 기본 생성자 하나만 사용하거나 기본 생성자와 별도 생성자를 추가로 정의하여 사용하는 두 가지 선택지가 있다. 이러한 사용자 정의 생성자는 하나의 클래스에서 여러 개 정의될 수 있는데, 이는 생성자를 매개변수 개수나 타입에 따라 오버로딩 하는 것이 가능하다는 뜻이다.
클래스명(매개변수) {
// 인스턴스 초기화 코드
}
class Car {
public String model;
public String color;
public Car() {} // 기본 생성자 (자동 생성)
public Car(String model, String color) {
this.model = model;
this.color = color;
}
void power() {
System.out.println("시동을 걸었습니다.");
}
void accelerate() {
System.out.println("더 빠르게!");
}
void stop() {
System.out.println("멈춤");
}
}
// 사용 예시
Car c = new Car("Model 3", "빨강");
System.out.println("내 차의 모델은 " + c.model + "이고 색은 " + c.color + "입니다.");
c.power();
c.accelerate();
c.stop();
2. this vs this()
| this | this() | |
| 의미 | 현재 인스턴스 자신을 참조 | 같은 클래스의 다른 생성자 호출 |
| 위치 | 메서드, 생성자 등 어디서든 사용 가능 | 생성자 내부, 반드시 첫 줄 |
| 주 용도 | 인스턴스 변수와 지역 변수의 구분 | 생성자 재사용, 중복 제거 |
| 형태 | this.변수명 | thist(...) |
// 1. this 예제
public class ConstructorExample {
public static void main(String[] args) {
Car car = new Car("Model X", "빨간색", 250);
System.out.println("제 차는 " + car.getModelName() + "이고, 컬러는 "
+ car.getColor() + "입니다.");
}
}
class Car {
private String modelName;
private String color;
private int maxSpeed;
public Car(String modelName, String color, int maxSpeed) {
this.modelName = modelName; // 생성된 객체의 인스턴스 필드 = 매개변수
this.color = color; // this가 없으면 지역 변수로 간주
this.maxSpeed = maxSpeed;
}
public String getModelName() {
return modelName;
}
public String getColor() {
return color;
}
}
// 2. this() 예제
public class Test {
public static void main(String[] args) {
Example example1 = new Example();
Example example2 = new Example(5);
}
}
class Example {
public Example() {
System.out.println("Example의 기본 생성자 호출!");
}
public Example(int x) {
this(); // Example() 호출
System.out.println("Example의 두 번째 생성자 호출!");
}
}
05. 자바 가상 머신(Java Virtual Machine, JVM)
1. JVM
JVM이란 자바 애플리케이션을 운영체제로부터 독립적으로 실행하기 위한 가상 컴퓨터(Virtual Machine)이다. 자바 소스 코드를 컴퓨터가 이해할 수 있도록 실행시키는 해석기(interpreter) 역할을 수행하며, 실질적으로 .class 파일을 읽고 해석하여 실행한다. 하지만 이러한 독립성을 실현시키기 위해서 JVM은 운영체제마다 별도로 제작된다. 즉, 자바는 플랫폼 독립적이지만 JVM은 플랫폼 종속적이다.
// 1. 자바 소스코드 (.java) 작성
// 2. 자바 컴파일러(javac)가 바이트코드(.class)로 컴파일
// 3. JVM이 해당 바이트 코드를 실행
[.java 파일] --(javac)--> [.class 파일] --(JVM)--> 실행
| 일반적인 프로그램 실행 방식 | 자바 실행 방식 | |
| 소통 방식 | 운영체제와 직접 소통 | JVM을 통해 운영체제와 간접 소통 |
| 종속성 | 운영체제마다 요청 방식이 달라 OS에 종속적 | JVM 덕분에 특정 OS으로부터 독립적 |
| 코드 호환성 | 운영체제가 바뀌면 코드를 새로 짜거나 컴파일해야 함 | 한 번 작성하면 어디서든 실행 가능 >> Write Once Run Anywhere, WORA |
| 핵심 역할 | 프로그래밍 언어가 자원을 직접 요청 | JVM이 프로그램과 운영체제 사이 통역기 역할 수행 |
2. JVM 실행 순서
.class 바이트 코드로 변환된 자바 코드는 JVM에서 다음과 같이 동작한다.

| 실행 순서 | ||
| 클래스 로더 (Class Loder) |
.class 파일을 메모리에 로드 | |
| 메모리 할당 | 런타임 데이터 영역(Runtime Data Area)에 메모리 할당 | |
| 해석 및 실행 | 실행 엔진(Execution Engine)이 바이트코드를 해석하고 실행 | - 인터프리터: 한 줄씩 실행 - JIT 컴파일러: 반복 실행 코드를 통쨰로 기계어로 번역 |
3. JVM 메모리 구조

| 메서드 영역 (Method Area) |
힙 영역 (Heap Area) |
스택 영역 (Stack Area) |
|
| 핵심 용도 | 클래스의 설계도 저장 | 실제 객체 인스턴스 저장 | 메셔드 실행 및 변수 저장 >> 메서드 호출 시마다 스택 프레임이 위로 쌓임 |
| 저장 내용 | 클래스 정보, static, final | 인스턴스 객체, 배열, 인스턴스 필드 | 지역 변수, 참조 변수, 매개변수, 반환 값 등 >> 참조 변수가 객체의 주소값을 참조 |
| 생명 주기 | JVM 종료 시까지 | GC가 제거 전까지 | 메서드 종료 시 자동 제거 |
| 공유 / 독립 | 모든 스레드에서 공유 | 모든 스레드에서 공유 | 각 스레드마다 독립적 |
| 장단점 / 특징 | 프로그램 전체적으로 하나만 존재 | 메모리가 유연하지만 상대적으로 느림 | 빠르지만 제한적 메모리 >> LIFO 구조 |
class Student {
String name;
int age;
}
public class MainApplication {
public static void main(String[] args) {
Student s = new Student();
s.name = "민지";
s.age = 20;
}
}
| 실행 흐름 | 관련 메모리 영역 | 메모리에 할당된 변수 | |
| 클래스 로딩 | JVM이 Student, MainApplication 클래스 로딩 | 메서드 영역 | 클래스 구조 저장 >> 필드, 메서드, static |
| main() 메서드 실행 | 스택 영역에 스택 프레임 생성 | 스택 영역 | String[] agrs, 지역 변수 s |
| 객체 생성 | Student 객체 생성 >> 지역 변수 s에 생성한 객체의 주소값 저장 |
힙 영역 | 객체 인스턴스 |
| 필드 초기화 | 지역 변수 s가 가리키는 힙 영역의 객체 내부 필드 수정 | 힙 영역 | 객체 인스턴스 필드 값 수정 |
| 메서드 종료 | 지역 변수 s를 포함한 스택 프레임 제거 >> 객체는 힙 영역에 여전히 존재하며, 이후 GC가 폐기 |
스택 영역 |
4. 가비지 컬렉션 (Garbage Collection, GC)
가비지 컬렉션이란 프로그램 실행 도중 더 이상 참조되지 않는 객체를 탐지하고 제거해 메모리를 확보하는 자동화된 메커니즘이다. 자바에서 기본적으로 제공하는 자동으로 메모리를 관리하는 기능으로, 더이상 참조되지 않는 객체를 정리한다. 덕분에 개발자는 별도로 메모리를 해제할 필요가 없을 뿐만 아니라 메모리 누수 및 Dangling Porinter 등의 위험을 줄일 수 있다.
| 주요 특징 | ||
| 가비지 컬렉션 실행 시점 | 정해진 주기 없이 JVM이 판단하여 실행 | 메모리 부족, 시스템 idle, 개게 수 급증 등 조건에 따라 동작 |
| 참조 해제 조건 | 명시적 null 대입 | null을 대입함으로써, 참조 변수 스코프를 벗어남 |
Person person = new Person();
person.setName("김코딩");
person = null; // 참조 해제 → GC 대상
person = new Person();
person.setName("박해커");
person = new Person("김러키"); // 이전 인스턴스는 GC 대상
5. 가비지 컬렉션 실행 순서
| 실행 순서 | |
| Stop The World | GC를 실행하기 위해 JVM이 애플리케이션의 모든 스레드를 정지시키는 작업 >> GC 전용 스레드만 남기고 전체 애플리케이션의 동작을 중지시키며, 모든 스레드는 처리 완료 후 재실행됨 |
| Mark | GC가 사용 중인 객체를 식별 (참조 여부 확인) |
| Sweep | 참조되지 않은 객체 제거 및 메모리 회수 |
6. 가바지 컬렉션 세대별 구조

| Young Generation | Old Generation | |
| 객체 공간 | 새로운 객체가 생성되는 공간 | Young 세대에서 오래 살아남은 객체가 머무는 공간 |
| 생존 기간 | 대부분 아주 짧음 (금방 죽음) | Young 세대에서 끝까지 살아남아 승격됨 |
| GC 이름 | Minor GC | Major GC (또는 Full GC) |
| GC 빈도 | 매우 높음 (자주 발생) | 낮음 |
| GC 속도 | 매우 빠름 | 상대적으로 느림 (성능 부담 큼) |
| 세부 구성 | Eden, Survivor 0, Survivor 1 | 단일 영역 (상대적으로 큰 공간) |
7. 가바지 컬렉션 알고리즘
| 가비지 컬렉션 종류 | |
| Serial GC | 단일 스레드로, 소규모 애플리케이션에 적합 |
| Parallel GC | 멀티스레드 기반이며, Throughput 우선 |
| CMS GC | 애플리케이션 정지 최소화 >> Mark / Sweep 병렬 수행 |
| G1 GC | Java 11 이상에서 기본 >> Young / Old를 세분화하여 병렬 처리 및 예측 가능 |
8. 메모리 누수 방지 방법 (Memory Leak Prevention)
메모리 누수란 더 이상 사용되지 않지만 여전히 참조되고 있어 GC가 수고하지 못하는 객체에 의해 발생한다.
// 1. 컬렉션에 객체를 추가하고 제거하지 않은 경우
List<Student> list = new ArrayList<>();
Student s = new Student();
list.add(s);
s = null; // 여전히 list가 s를 참조
// 1-1. 예방 방법
list.remove(s);
// 2. static 변수에 객체를 저장한 경우
public class Cache {
public static List<User> users = new ArrayList<>();
}
// 3. 리스터, 콜백 등 이벤트 객체 미해제
button.addClickListener(new ClickListener() {
public void onClick() {
// DO SOMETHING
}
});
// 3-2. 예방 방법
button.removeClickListener() 호출'[Codeit Review] > 객체 지향 프로그래밍' 카테고리의 다른 글
| [06] UML을 활용한 객체 지향 설계 시각화 (0) | 2026.01.26 |
|---|---|
| [05] 예외 처리 (Exception Handling) (0) | 2026.01.26 |
| [04] 내부 클래스와 익명 클래스, 그리고 열거 타입(ENUM)의 활용 (0) | 2026.01.26 |
| [03] SOLID 원칙 (0) | 2026.01.26 |
| [02] 객체 지향 프로그래밍의 4가지 핵심 개념 (0) | 2026.01.26 |