![[디자인 패턴] 0. 들어가기](https://image.inblog.dev?url=https%3A%2F%2Finblog.ai%2Fapi%2Fog%3Ftitle%3D%255B%25EB%2594%2594%25EC%259E%2590%25EC%259D%25B8%2520%25ED%258C%25A8%25ED%2584%25B4%255D%25200.%2520%25EB%2593%25A4%25EC%2596%25B4%25EA%25B0%2580%25EA%25B8%25B0%26logoUrl%3Dhttps%253A%252F%252Finblog.ai%252Finblog_logo.png%26blogTitle%3Djay0628&w=2048&q=75)
0. 컴퓨터 시스템 구조
HW → OS → APP → JVM → 실행 흐름까지 단계별로 이해해보자.
1. HW, OS, APP의 구조
컴퓨터 시스템은 크게 3가지 층으로 나눌 수 있다:
┌──────────────────────────────┐ │ Application (Slack, Chrome…) │ ← 우리가 개발하는 앱 ├──────────────────────────────┤ │ OS (Windows, macOS, Linux…) │ ← 하드웨어와 앱을 중개 ├──────────────────────────────┤ │ HW (CPU, RAM, SSD…) │ ← 실제 기계 └──────────────────────────────┘
1. HW (Hardware)
- 컴퓨터의 실제 부품들: CPU, RAM, SSD, 키보드, 모니터 등
- 모든 명령어는 **0과 1의 이진수(binary)**로 전달되어야 한다.
2. OS (Operating System)
- Windows, macOS, Linux처럼 하드웨어와 앱 사이를 중개하는 소프트웨어
- 키보드나 파일 접근 등 복잡한 하드웨어 제어를 대신 수행해준다.
- OS는 명령어를 이해하기 위한 **자기만의 언어(Shell)**를 갖고 있다. (
dir
,cd
등)
3. APP (Application)
- 우리가 개발하는 프로그램(ex. 게임, 웹 서비스)
- 보통은 Java, Python 같은 고수준 언어로 작성된다.
2. 자바 프로그램이 실행되는 전체 흐름
자바는 특별하다. 바로 **JVM(Java Virtual Machine)**이라는 중간층이 존재하기 때문이다. 이 덕분에 자바는 플랫폼 독립적으로 실행된다.
일반적인 실행 흐름
(1) .java 파일
→ [컴파일] →
(2) .class 파일
→ [실행] →
(3) JVM
→
(4) OS
→
(5) HW
- .java 파일: 우리가 작성하는 소스코드 (영어 기반의 고수준 언어)
- .class 파일: 컴파일을 거쳐 만들어진 바이트코드 (JVM이 이해함)
- JVM 실행:
.class
파일을 해석해서 OS에 맞게 명령을 전달
- OS가 명령 실행: 실제 하드웨어를 제어할 수 있는 OS 레벨의 명령 실행
- HW 동작: 모니터에 출력하거나, 파일을 저장하거나, CPU가 계산 수행
3. JVM이 중요한 이유
JVM은 자바 프로그램을 운영체제에 종속되지 않도록 해준다.
즉, 한 번 작성한 코드가 Windows, macOS, Linux 어디서든 실행될 수 있게 해주는 핵심 역할
4. C 언어와 비교하면?
C언어는 OS에 맞게 직접 컴파일된다. 그래서 Windows용으로 만든 C 프로그램은 Linux에서 실행되지 않는다. OS 종속적이다.
C 언어 → OS에 맞게 컴파일 → 실행파일 생성
→ OS와 tightly-coupled
하지만 Java는?
Java → JVM 위한 .class 생성 → JVM이 OS 맞게 실행
→ OS 독립적 (Write once, Run anywhere)
5. 요약
계층 | 설명 | 예시 |
HW | 실제 기계 부품 | CPU, RAM, SSD |
OS | HW 제어를 중개 | Windows, macOS |
APP | 우리가 만든 프로그램 | 게임, 웹 서비스 |
JVM | 자바 코드 실행을 중개 | Java Virtual Machine |
컴파일 | java → class 변환 | javac 명령 |
실행 | JVM이 바이트코드를 실행 | java 명령 |
1. 메모리 구조 이해하기: Static, Heap, Stack
프로그래밍을 하다 보면 자주 마주치는 개념이 있다. 바로
Static
, Heap
, Stack
메모리다. 이 세 가지는 JVM(Java Virtual Machine) 혹은 일반적인 프로그래밍 언어에서도 중요한 메모리 구조로, 각각의 목적과 쓰임이 분명히 다르다.1. Static 영역: 프로그램 시작과 함께
Static은 프로그램이 시작될 때 메모리에 올라가고, 종료될 때까지 유지된다. 예를 들어 태양처럼, 지구(프로그램)의 존재와 관계없이 처음부터 끝까지 존재해야 하는 정보들을 static 메모리에 올려두는 것이다. Java에서는
static
키워드로 선언된 변수나 메서드가 이 영역에 저장된다.예시
public class Earth {
static String sun = "태양"; // 프로그램 시작부터 종료까지 유지
}
2. Heap 영역: 원할 때 생성되는 공간
Heap은 객체 인스턴스가 저장되는 공간이다. 우리가
new
키워드로 객체를 생성하면, 그 객체는 heap 메모리에 저장된다. 생성 시점부터 소멸 시점까지 살아있고, 개발자가 원할 때 객체를 생성하고 제거할 수 있다.예시
Animal dog = new Animal(); // new 키워드로 heap에 올라감
3. Stack 영역: 메서드 실행 중 잠깐 사용
Stack은 메서드가 호출될 때 생기고, 메서드가 끝나면 사라진다. 예를 들어
attack()
메서드를 실행하면, 그 순간 필요한 값(예: 각도 계산, 임시 변수 등)이 스택에 저장되고, 메서드 실행이 끝나면 모두 사라진다. 메서드별로 stack 공간이 할당되며 같은 메서드를 두 번 연속하여 실행하더라도 다른 stack 공간이 할당된다. 짧은 생명 주기를 가진 메모리 공간이다.예시
public void attack() {
int angle = 20; // stack에 저장됨
// 공격 로직 수행
}
정리:
- Static: 프로그램 시작부터 종료까지
- Heap:
new
로 객체 생성 시
- Stack: 메서드 실행 중 일시적으로 생성
4. 필기

5. 전체 소스 코드
package ex00;
// static, heap, stack
class Animal {
static String name = "강아지"; // static 공간에 name 뜸
void speak() {
String sound = "멍멍"; // stack 공간에 sound 잠깐 뜸
System.out.println(sound);
}
}
public class Mem01 {
public static void main(String[] args) {
System.out.println(Animal.name); // static 공간에 name 찾음
Animal a = new Animal(); // heap 생성 (speak 메모리에 뜸)
a.speak();
}
}
2. 동적 바인딩 (추상화)
"자동차는 엔진인가?" 라는 질문을 받는다면 우리는 "아니다"라고 답할 것이다. 이처럼 현실 세계에서 ‘상속’은 적절하지 않은 상황이 많다. 그렇다면 자동차와 엔진은 어떤 관계로 설계해야 할까? 정답은
컴포지션(Composition)
이다.1. 상속 vs 컴포지션
- 상속(Inheritance): "A는 B이다(is-a)" 관계일 때 사용.
예: 강아지는 동물이다 →
Dog extends Animal
- 컴포지션(Composition): "A는 B를 가진다(has-a)" 관계일 때 사용.
예: 자동차는 엔진을 가진다 →
Car has Engine
2. 추상 클래스와 추상화
추상 클래스는 객체를 생성할 수 없다. 오직 상속을 통해 구체적인 클래스에서 구현되도록 설계된 구조다. 예를 들어
Car
가 추상 클래스라면, Sonata
, Genesis
같은 실제 차량이 이를 상속받아 구현한다.예시
abstract class Car {
abstract void run(); // 추상 메서드
}
class Sonata extends Car {
@Override
void run() {
System.out.println("Sonata run");
}
}
3. 동적 바인딩(Dynamic Binding)
Car
타입의 변수로 Sonata
나 Genesis
객체를 가리킬 수 있을 때, 실제 실행 시점에 어떤 클래스의 run()
이 실행될지는 동적으로 결정된다. 이것이 동적 바인딩이다.Car car = new Sonata(); // 타입은 Car, 객체는 Sonata
car.run(); // Sonata의 run()이 호출됨
이런 구조의 핵심은, 공통된 부모 타입(Car)을 통해 다양한 자식 클래스(Sonata, Genesis)를 하나의 타입으로 다룰 수 있다는 점이다. 설계의 유연성과 확장성을 크게 높여준다.
4. 추상화를 왜 할까?
- 여러 객체를 공통된 부모 타입으로 묶어 일괄 처리할 수 있다.
- 메서드를 재정의(override)하여 객체마다 다르게 동작하도록 할 수 있다.
- 유지보수가 쉬워진다. 새로운 객체가 추가되어도 기존 코드 수정 없이 확장 가능하다.
✨ 마무리 요약
개념 | 설명 |
Static | 프로그램 시작과 함께 떠서 종료 시까지 유지되는 메모리 |
Heap | 객체를 new 할 때 메모리에 올라가는 공간 |
Stack | 메서드 실행 중 임시로 생기는 메모리 공간 |
상속 | "is-a" 관계 (예: Dog is an Animal) |
컴포지션 | "has-a" 관계 (예: Car has an Engine) |
추상 클래스 | 객체 생성 불가, 자식 클래스에서 구현 필요 |
동적 바인딩 | 실행 시점에 실제 메서드 결정 (재정의 기반) |
추상화 | 여러 객체를 공통된 타입으로 묶어 설계하는 방법 |

package ex00;
// 동적 바인딩
abstract class Car {
abstract void run();
}
class Genesis extends Car {
// 재정의
void run() {
System.out.println("Genesis run");
}
}
class Sonata extends Car {
// 재정의
void run() {
System.out.println("Sonata run");
}
}
public class Mem02 {
public static void main(String[] args) {
Car car1 = new Sonata();
car1.run();
Car car2 = new Genesis();
car2.run();
}
}
Share article