2주차 Unit 1.1 — 자바 변수의 3종류

Psj·2026년 5월 11일

F-lab

목록 보기
50/239

Unit 1.1 — 자바 변수의 3종류

F-LAB JAVA · 2주차 · Phase 1 · 자바 변수 ↔ 메모리 영역의 매핑
🎯 2주차 시작 — 1주차에서 본 JVM 메모리 영역을, 이제 변수 단위로 매핑한다.


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • 자바 변수 3종류는 무엇이며, 어떤 기준으로 나뉘는가?
  • 같은 클래스의 객체 100개를 만들면 각 변수는 몇 개씩 존재하는가?
  • 매개변수는 3종류 중 어디에 속하는가?
  • 각 변수의 초기화 규칙 차이는?
  • static 키워드가 만드는 결정적 차이는?
  • 인스턴스 변수와 클래스 변수의 수명(lifetime) 차이는?
  • 왜 ILIC의 모든 Service에는 static 변수가 거의 없는가?

🎯 핵심 한 문장

자바 변수는 "어디에 선언했는가"에 따라 3종류로 나뉘고, 각각 다른 메모리 영역에 저장되며, 다른 수명을 가진다.
— 지역 변수 (Local), 인스턴스 변수 (Instance), 클래스 변수 (Class/Static).
1주차에서 본 Heap/Stack/Method Area의 분할이 변수 단위로 정확히 매핑된다.

비유 — 사무실의 3가지 물건

종류어디에누가 쓰나비유
지역 변수회의실의 화이트보드회의 참석자만회의 끝나면 지움. 다음 회의는 처음부터
인스턴스 변수개인 책상 서랍그 책상 주인만직원 100명 = 책상 100개 = 서랍 100개
클래스 변수공용 게시판모든 직원이 공유회사에 게시판 하나. 누가 바꾸면 모두에게 보임

직원 = 객체(인스턴스), 회사 = 클래스.
책상 = 인스턴스마다 1개, 게시판 = 클래스에 1개, 화이트보드 = 회의(메서드)마다 1개.


🧭 9개 섹션 로드맵

1. 변수의 본질             — 왜 3종류로 나눠야 했나
2. 지역 변수 (Local)        — 가장 짧게 살고 가장 자주 만들어진다
3. 인스턴스 변수 (Instance) — 객체의 정체성을 만드는 것
4. 클래스 변수 (Static)     — 클래스에 단 하나, 모두가 공유
5. 3종 비교 — 한눈에 보기   — 표 · 다이어그램으로 완전 정리
6. 매개변수의 정체          — 지역 변수의 특수형
7. ILIC 실무 코드           — 어디서 무엇을 쓰는가
8. 흔한 실수 7가지          — 초기화 누락 · static 남용 · 가변 static 함정
9. 면접 질문 + 자기 점검

1️⃣ 변수의 본질 — 왜 3종류로 나눠야 했나

1.1 변수란 무엇인가

int age = 30;

세 가지가 동시에 일어난다.
1. 메모리 어딘가에 공간을 확보 (4바이트, int)
2. 그 공간에 이름 age를 붙임
3. 그 공간에 값 30을 넣음

변수 = 이름 + 메모리 공간 + 값.

1.2 그런데 "어디에" 공간이 확보되는가?

여기서 자바는 3가지 선택지를 만들었다.

public class Order {

    static int totalOrders = 0;        // ← (A) 클래스에, static
    private String orderId;             // ← (B) 클래스에, static 없음

    public void process() {
        int retryCount = 0;             // ← (C) 메서드 안에
    }
}
위치이름메모리 영역수명
(A) 클래스 안 + static클래스 변수Method AreaJVM 종료까지
(B) 클래스 안 + static 없음인스턴스 변수Heap (객체 안)객체가 GC될 때까지
(C) 메서드 안지역 변수Stack (스택 프레임)메서드 종료까지

"어디에 선언했는가"가 "어디에 저장되는가"를 결정한다.
→ 그리고 그게 수명(lifetime) 도 결정한다.

1.3 왜 이렇게 나눴나 — 설계 의도

세 가지 요구가 동시에 있었다.

  1. 메서드 안에서만 잠깐 쓰는 임시 변수 가 필요 → 지역 변수
  2. 객체마다 다른 상태 를 가져야 함 (사람마다 이름이 다르듯) → 인스턴스 변수
  3. 모든 객체가 공유하는 무언가 가 필요 (회사 이름은 모두 공통) → 클래스 변수

자바는 이 셋을 메모리 영역 분리로 해결했다.

  • 잠깐 쓰는 건 빠른 Stack에
  • 객체별 상태는 Heap의 객체 안에
  • 공유는 Method Area의 클래스 자리에

⚠️ 1주차 Unit 4.1 (JVM 런타임 데이터 영역)에서 본 5분할이 여기서 변수 단위로 매핑된다.
다음 Unit 1.2에서 메모리 위치를 정확히 본다.


2️⃣ 지역 변수 (Local Variable)

2.1 정의

메서드(또는 블록) 안에서 선언되어, 그 메서드 종료 시 사라지는 변수.

public BigDecimal calculateFare(Shipment shipment) {
    BigDecimal baseFare = new BigDecimal("100000");   // ✓ 지역 변수
    int weight = shipment.getWeight();                 // ✓ 지역 변수

    if (weight > 1000) {
        BigDecimal surcharge = baseFare.multiply(...); // ✓ 지역 변수 (블록 안)
        return baseFare.add(surcharge);
    }

    return baseFare;
}   // ← 메서드 끝. baseFare, weight, surcharge 모두 사라짐

2.2 특징

❶ 초기화 강제

public void method() {
    int a;
    System.out.println(a);   // ❌ 컴파일 에러
    //                          variable a might not have been initialized
}

지역 변수는 반드시 사용 전 초기화해야 한다. 컴파일러가 강제.

→ 인스턴스/클래스 변수와의 가장 결정적 차이.

❷ 메서드 호출마다 새로 생성

public void process() {
    int count = 0;      // 호출 때마다 0으로 시작
    count++;
}

// 1만 번 호출하면? 1만 개의 count가 만들어졌다 사라짐

❸ Stack에 저장 (다음 Unit에서 깊이)

각 메서드 호출은 스택 프레임(Stack Frame) 을 만든다. 지역 변수는 그 안에 들어간다.

❹ 다른 스레드와 공유 안 됨

Stack은 스레드별로 별도 → 지역 변수는 자동으로 스레드 안전.
이게 함수형 스타일 + Stream + 람다가 동시성에 강한 이유.

2.3 블록 스코프

public void method() {
    for (int i = 0; i < 10; i++) {
        String temp = "round " + i;
        System.out.println(temp);
    }
    System.out.println(temp);   // ❌ for 블록 밖. temp 안 보임
    System.out.println(i);      // ❌ 마찬가지
}

→ 지역 변수의 스코프는 선언된 블록 {} 안.


3️⃣ 인스턴스 변수 (Instance Variable)

3.1 정의

클래스에 선언되었으나 static이 없는 변수. 객체마다 별도로 존재하며, 객체의 상태를 표현한다.

public class Shipment {

    private Long id;                  // ✓ 인스턴스 변수
    private String blNo;              // ✓
    private LocalDate eta;            // ✓
    private BigDecimal freight;       // ✓
}

3.2 객체별로 다른 값

Shipment s1 = new Shipment();
Shipment s2 = new Shipment();

s1.setBlNo("BL-001");
s2.setBlNo("BL-002");

s1.getBlNo();   // "BL-001"
s2.getBlNo();   // "BL-002"

// s1과 s2는 서로 다른 메모리(Heap)에 산다
// 각자의 blNo도 별도

3.3 자동 초기화 (Default Value)

public class Shipment {
    private Long id;             // → null (참조형 기본값)
    private int weight;          // → 0 (int 기본값)
    private boolean delivered;   // → false
    private double rate;         // → 0.0
}

Shipment s = new Shipment();
s.getId();          // null  (NullPointerException 없이 호출 가능)
s.getWeight();      // 0
s.isDelivered();    // false

지역 변수와 다르게 자동 초기화된다. 컴파일러 에러 안 남.

기본값 표:

타입기본값
byte, short, int, long0
float, double0.0
booleanfalse
char'\u0000' (null character)
참조형 (객체, 배열)null

3.4 수명 — 객체가 살아있는 동안

Shipment s = new Shipment();    // 1) Heap에 객체 생성, blNo 등 함께 할당
s = null;                        // 2) 참조 끊김 → GC 대상
                                 // 3) GC 발동 시 → 인스턴스 변수도 함께 회수

객체가 살아있는 한 인스턴스 변수도 살아있고, 객체가 GC되면 함께 사라진다.

3.5 카운팅 — "객체 100개를 만들면?"

public class Shipment {
    private String blNo;
    private LocalDate eta;
}

for (int i = 0; i < 100; i++) {
    new Shipment();    // 100개 객체 생성
}

// 메모리에 만들어진 것:
// - blNo 100개  (객체 100개 안에 1개씩)
// - eta 100개   (마찬가지)

객체 수만큼 인스턴스 변수가 존재한다.


4️⃣ 클래스 변수 (Class Variable / Static Variable)

4.1 정의

static으로 선언된 변수. 클래스 단위로 단 1개만 존재하며, 모든 객체가 공유한다.

public class Shipment {

    public static final String COMPANY_NAME = "ILIC";     // ✓ 상수
    private static int totalCount = 0;                     // ✓ 카운터
    private static final BigDecimal DEFAULT_RATE          // ✓ 정책 상수
        = new BigDecimal("1.0");

    private String blNo;   // (인스턴스 변수, 비교용)
}

4.2 클래스 단위로 단 1개

Shipment s1 = new Shipment();
Shipment s2 = new Shipment();
Shipment s3 = new Shipment();

// Shipment.totalCount는 메모리에 단 1개
// s1, s2, s3가 모두 같은 totalCount를 본다
Heap:
  ┌───────────┐  ┌───────────┐  ┌───────────┐
  │ s1 객체    │  │ s2 객체    │  │ s3 객체    │
  │ blNo=...  │  │ blNo=...  │  │ blNo=...  │  ← 인스턴스 변수: 객체마다
  └───────────┘  └───────────┘  └───────────┘
         ↓             ↓             ↓
         └─────────────┴─────────────┘
                       ↓
              Method Area:
              ┌───────────────────────┐
              │ Shipment 클래스        │
              │ totalCount = ?        │  ← 클래스 변수: 클래스에 1개
              └───────────────────────┘

4.3 객체 없이 접근 가능

Shipment.COMPANY_NAME;    // ✓ 객체 안 만들고 클래스명으로 접근
Shipment.totalCount;      // ✓
Math.PI;                  // ✓ (대표적 예)
Integer.MAX_VALUE;        // ✓

클래스 로딩 시점에 이미 메모리에 존재하기 때문.

4.4 수명 — 클래스가 로딩될 때부터 JVM 종료까지

JVM 시작
   ↓
클래스 로딩 (처음 사용될 때)
   ↓
Method Area에 클래스 정보 + 클래스 변수 할당
   ↓
... 프로그램 실행 동안 계속 살아있음 ...
   ↓
JVM 종료
   ↓
함께 사라짐

이 긴 수명이 양날의 검.

  • 장점: 어디서나 빠르게 접근, 메모리 절약 (단 1개)
  • 단점: GC 안 됨 → 잘못 쓰면 메모리 누수의 주범

4.5 final이 거의 항상 같이 붙는 이유

public static final String COMPANY_NAME = "ILIC";        // ✓ 상수
public static final BigDecimal DEFAULT_RATE              // ✓ 상수
    = new BigDecimal("1.0");

static만 쓰면 변수, static final이면 상수.

상수로 쓸 때의 장점:

  • 모든 객체가 공유 (메모리 절약)
  • 불변이라 멀티스레드 안전
  • Math.PI, Integer.MAX_VALUE 패턴

가변 static은 거의 항상 함정이다 (8장 흔한 실수에서).


5️⃣ 3종 비교 — 한눈에 보기

5.1 비교표

항목지역 변수인스턴스 변수클래스 변수
선언 위치메서드/블록 안클래스 안 (static X)클래스 안 (static O)
저장 영역StackHeap (객체 안)Method Area
생성 시점메서드 호출 시new클래스 로딩 시
소멸 시점메서드 종료 시객체 GC 시JVM 종료 시
개수호출마다 1개객체마다 1개클래스에 단 1개
자동 초기화❌ (수동 필수)✓ (기본값)✓ (기본값)
객체 없이 접근
접근 키워드(없음)this.xClassName.x
스레드 공유❌ (스레드별)객체 공유 시 ✓✓ (항상)
GC 대상❌ (자동 소멸)❌ (JVM 종료까지)

5.2 객체 100개 만들면?

public class Member {
    static int totalCount;       // 클래스 변수
    String name;                 // 인스턴스 변수

    void greet() {
        String hello = "hi";     // 지역 변수
    }
}

for (int i = 0; i < 100; i++) {
    Member m = new Member();
    m.greet();
}

메모리에 생긴 것:

Method Area:
  totalCount  ← 1개 (클래스에 1개)

Heap:
  Member 객체 100개, 각각 안에 name  ← 100개

Stack (greet 실행 중 한 순간):
  hello  ← 1개 (호출당 1개. 호출 끝나면 사라짐)

이 질문이 면접 단골. 즉답할 수 있어야 한다.

5.3 다이어그램 — 같은 클래스 안에서 세 변수의 위치

public class Shipment {

    public static final String COMPANY = "ILIC";   // ← 클래스 변수 (Method Area)

    private Long id;                                // ← 인스턴스 변수 (Heap, 객체 안)
    private String blNo;                            // ← 인스턴스 변수

    public BigDecimal calculate(int weight) {
        BigDecimal rate = computeRate();            // ← 지역 변수 (Stack)
        return rate.multiply(BigDecimal.valueOf(weight));
    }
}

호출 흐름:
        ┌────────────────────────────────────┐
        │ Method Area                        │
        │ ┌────────────────────────────────┐ │
        │ │ Shipment 클래스                 │ │
        │ │   COMPANY = "ILIC"             │ │  ← 1. 모두 공유
        │ │   (메서드 바이트코드)            │ │
        │ └────────────────────────────────┘ │
        └────────────────────────────────────┘

        ┌────────────────────────────────────┐
        │ Heap                               │
        │ ┌──────────┐ ┌──────────┐         │
        │ │ Shipment │ │ Shipment...      │  ← N(객체마다)
        │ │ id, blNo │ │ id, blNo │         │
        │ └──────────┘ └──────────┘         │
        └────────────────────────────────────┘

        ┌────────────────────────────────────┐
        │ Stack (스레드별)                    │
        │ ┌────────────────────────────────┐ │
        │ │ calculate() 프레임              │ │  ← 호출 중에만 존재
        │ │   weight (매개변수, 지역)         │ │
        │ │   rate (지역)                   │ │
        │ └────────────────────────────────┘ │
        └────────────────────────────────────┘

6️⃣ 매개변수의 정체 — 지역 변수의 특수형

6.1 매개변수도 지역 변수다

public BigDecimal calculate(int weight, BigDecimal rate) {
    //                       ↑           ↑
    //                       매개변수    매개변수
    // 이 둘은 지역 변수다.
}

매개변수는 메서드 호출 시 자동으로 초기화되는 지역 변수.

6.2 호출자가 값을 넣어준다

calculate(100, new BigDecimal("50"));
//        ↑    ↑
//        weight=100, rate=50 으로 자동 할당

지역 변수와 똑같이 Stack에 저장, 메서드 종료 시 사라짐, 스레드별로 별도.

6.3 자바의 매개변수 전달 방식 (1주차 4.2 복습)

public void modify(Shipment s) {
    s.setBlNo("CHANGED");          // ← 호출자에게 보임
    s = new Shipment();             // ← 호출자에게 안 보임
}
  • 참조 자료형: 주소값 복사 → 객체 내부 변경은 호출자에 보임
  • 재할당(=)은 매개변수의 지역 복사본만 바꿈

자바는 Pass by Value (1주차 Unit 4.2). 참조형도 "주소값을 값으로 복사".
매개변수가 지역 변수임을 이해하면 이 동작이 자연스럽다.

6.4 final 매개변수

public BigDecimal calculate(final int weight, final BigDecimal rate) {
    weight = 200;    // ❌ final이라 재할당 불가
    return rate.multiply(BigDecimal.valueOf(weight));
}

매개변수를 메서드 안에서 바꾸지 않겠다는 의도 표현. 람다에서 외부 변수를 캡처할 때 effectively final 규칙과 직결.


7️⃣ ILIC 실무 코드 — 어디서 무엇을 쓰는가

7.1 Entity — 인스턴스 변수의 표준

@Entity
public class Shipment {

    @Id @GeneratedValue
    private Long id;                    // ✓ 인스턴스 변수

    private String blNo;                // ✓
    private String origin;              // ✓
    private String destination;         // ✓
    private LocalDate eta;              // ✓
    private BigDecimal freight;         // ✓

    @OneToMany(mappedBy = "shipment")
    private List<Cargo> cargoes;        // ✓
}

포인트: Entity의 필드는 모두 인스턴스 변수. 객체마다 다른 상태(다른 화물 정보)를 표현하므로.

7.2 Service — static 변수가 거의 없는 이유

@Service
public class ShipmentService {

    // ❌ 가변 static — 멀티스레드 위험!
    // private static int processedCount = 0;

    private final ShipmentRepository repository;   // ✓ 인스턴스 final
    private final FareCalculator calculator;       // ✓

    public ShipmentService(ShipmentRepository repository,
                            FareCalculator calculator) {
        this.repository = repository;
        this.calculator = calculator;
    }

    public Shipment process(ShipmentRequest req) {
        Shipment shipment = new Shipment(req);     // ✓ 지역 변수
        BigDecimal fare = calculator.compute(shipment);

        shipment.setFreight(fare);
        return repository.save(shipment);
    }
}

왜 Service에 가변 static이 없는가?

  • Spring Bean은 싱글톤 → 하나의 인스턴스를 모든 요청이 공유
  • 인스턴스 변수도 사실상 공유되지만, final + DI로 불변 보장
  • 가변 static을 두면 → 상태 공유 + 멀티스레드 → race condition 직격

7.3 상수 — public static final

public final class FreightPolicy {

    public static final BigDecimal DEFAULT_FUEL_RATE
        = new BigDecimal("0.15");                    // ✓ 상수

    public static final int MAX_WEIGHT_KG = 28_000;  // ✓
    public static final String CURRENCY_KRW = "KRW";

    public static final Set<String> SUPPORTED_ROUTES = Set.of(
        "SEOUL-TOKYO", "SEOUL-OSAKA", "BUSAN-SHANGHAI"
    );

    private FreightPolicy() {}   // 인스턴스화 방지
}

// 사용
BigDecimal fuel = price.multiply(FreightPolicy.DEFAULT_FUEL_RATE);

포인트:

  • 상수 클래스는 인스턴스 만들 일 없음 → private 생성자
  • static final + 불변 객체 = 멀티스레드 안전 + 메모리 절약

7.4 가변 static이 적절한 드문 경우 — 카운터 with 동시성 보호

@Component
public class ShipmentMetrics {

    private static final AtomicLong totalProcessed = new AtomicLong(0);
    private static final AtomicLong totalFailed = new AtomicLong(0);

    public static void incrementProcessed() {
        totalProcessed.incrementAndGet();
    }

    public static long getTotalProcessed() {
        return totalProcessed.get();
    }
}

왜 여기선 OK?

  • AtomicLong 자체가 thread-safe
  • 진짜로 "전체 통계"가 클래스 단위에서 합리적
  • 대안: Spring Bean으로 만들어 인스턴스 변수로 → 보통 이게 더 깔끔

7.5 메서드 안 — 지역 변수의 전형

public ShipmentResponse process(ShipmentRequest req) {

    Long requestedId = req.getId();                       // ✓ 지역
    Shipment shipment = repository.findById(requestedId)  // ✓ 지역
        .orElseThrow(() -> new NotFoundException(requestedId));

    BigDecimal baseFare = calculator.compute(shipment);   // ✓ 지역
    BigDecimal surcharge = computeSurcharge(shipment);    // ✓ 지역
    BigDecimal total = baseFare.add(surcharge);           // ✓ 지역

    shipment.setFreight(total);
    return ShipmentResponse.from(shipment);
}

포인트: 비즈니스 로직의 임시 계산은 거의 다 지역 변수. 메서드가 끝나면 깨끗하게 정리 → 메모리 누수 걱정 없음.

7.6 final 지역 변수 — 의도 표현

public BigDecimal calculate(Shipment shipment) {
    final BigDecimal baseFare = lookupBaseFare(shipment);  // ✓ 의도: 안 바꿀게
    final int weight = shipment.getWeight();

    // ... 100줄 후
    // baseFare = newValue;   ← 컴파일 에러로 보호
}

람다 안에서 외부 변수를 쓰려면 사실상 final이어야 함 (effectively final). 명시하면 의도가 더 명확.


8️⃣ 흔한 실수 7가지

실수 1 — 지역 변수 초기화 누락

public BigDecimal calculate(int weight) {
    BigDecimal fare;
    if (weight > 1000) {
        fare = new BigDecimal("200000");
    }
    // weight가 1000 이하면? fare 초기화 안 됨
    return fare;   // ❌ 컴파일 에러
}

// ✅
public BigDecimal calculate(int weight) {
    BigDecimal fare = BigDecimal.ZERO;    // 명시적 초기화
    if (weight > 1000) {
        fare = new BigDecimal("200000");
    }
    return fare;
}

실수 2 — 가변 static을 Service에 사용 (멀티스레드 사고)

@Service
public class ShipmentService {

    // ❌ 모든 요청이 공유. 동시 요청 시 값이 섞임
    private static int currentBatchId = 0;

    public void process() {
        currentBatchId++;
        // ... currentBatchId 사용
    }
}

문제:

  • HTTP 요청 100개가 동시에 들어옴
  • 100개 스레드가 같은 currentBatchId 를 ++ → race condition
  • 100개가 다 같은 ID를 쓰는 경우 발생

해결:

// 옵션 1: AtomicInteger
private static final AtomicInteger currentBatchId = new AtomicInteger(0);

// 옵션 2: 인스턴스 변수 (Spring 싱글톤이라 동일 효과지만 의도 명확)
// 옵션 3: 메서드 안의 지역 변수로 옮기기 (가장 깨끗)

실수 3 — static 컬렉션의 메모리 누수

public class ShipmentCache {

    // ❌ 영원히 자라는 캐시
    private static final Map<Long, Shipment> cache = new HashMap<>();

    public static void put(Long id, Shipment s) {
        cache.put(id, s);   // 계속 쌓이기만 함
    }
}

문제:

  • static 변수는 GC 대상 아님 (JVM 종료까지)
  • cache가 들고 있는 Shipment들도 GC 안 됨
  • 메모리 폭발 → OutOfMemoryError

해결:

  • 한계 있는 캐시 (Caffeine, LinkedHashMap LRU)
  • 외부 캐시 (Redis)
  • WeakHashMap (참조가 끊기면 자동 회수)

실수 4 — 변수 가리기 (Shadowing)

public class Shipment {

    private String blNo;

    public void setBlNo(String blNo) {
        blNo = blNo;        // ❌ 매개변수가 자기 자신에게 대입. 인스턴스 변수 안 바뀜
    }

    // ✅
    public void setBlNo(String blNo) {
        this.blNo = blNo;   // ← this 명시
    }
}

디버깅 지옥: 컴파일은 되고 IDE 경고도 약함. 운영에서 "값이 안 바뀌네?" 며칠 헤맴.

실수 5 — 인스턴스 변수의 사용 전 null 가정

public class ShipmentService {

    @Autowired
    private ShipmentRepository repository;   // 인스턴스 변수, 자동 초기화로 null

    // ❌ Constructor가 끝나기 전에 호출되면?
    {
        repository.findAll();   // null이라 NPE
    }
}

// ✅ 생성자 주입 + final
@RequiredArgsConstructor
public class ShipmentService {
    private final ShipmentRepository repository;
}

→ Spring 4.3+ 권장: 생성자 주입 + final 인스턴스 변수.

실수 6 — 지역 변수와 인스턴스 변수의 이름 같음

public class Order {
    private BigDecimal total;

    public void calculate(List<Item> items) {
        BigDecimal total = BigDecimal.ZERO;       // ❌ 인스턴스 변수를 가림

        for (Item item : items) {
            total = total.add(item.price());      // 지역 변수만 바뀜
        }
        // 인스턴스 변수 total은 안 바뀐 채로 메서드 종료
    }
}

→ 이름이 같으면 지역 변수가 우선. 인스턴스 변수에 접근하려면 this.total.

실수 7 — static 변수에 인스턴스를 직접 노출

public class Config {
    // ❌ 외부에서 마음대로 바꿀 수 있음
    public static List<String> ROUTES = new ArrayList<>();
}

// 어디선가:
Config.ROUTES.clear();   // 다른 코드 전체에 영향
Config.ROUTES = null;    // 더 심각

해결:

public class Config {
    public static final List<String> ROUTES = List.of("SEOUL", "BUSAN");
    // 또는
    private static final List<String> ROUTES = new ArrayList<>();
    public static List<String> getRoutes() {
        return Collections.unmodifiableList(ROUTES);
    }
}

static final + 불변 컬렉션 = 안전.


9️⃣ 면접 질문 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
자바 변수 3종류는?지역(메서드 안) · 인스턴스(클래스, static X) · 클래스(static O)
같은 클래스 객체 100개 만들면 각 변수 개수?인스턴스 변수 100개 · 클래스 변수 1개
매개변수는 어느 종류?지역 변수의 특수형 (호출자가 자동 초기화)
지역 변수와 다른 변수의 결정적 차이?자동 초기화 안 됨 (수동 초기화 강제)
static 변수가 GC 대상이 아닌 이유?Method Area에 클래스와 함께 살아있음. JVM 종료 시 함께 소멸
Service에 static 변수가 거의 없는 이유?Spring 싱글톤 + 멀티스레드 + 가변 static = race condition
this.xx의 차이?this.x = 인스턴스 변수, x = 가까운 지역/매개변수
변수 기본값?숫자 0, boolean false, 참조형 null. 지역은 기본값 없음
public static final 패턴은?상수 — 모두 공유 + 불변 + 멀티스레드 안전
인스턴스 변수가 멀티스레드에 안전한가?객체를 공유하면 위험. 객체별이라도 동기화 필요

9.2 자기 점검 체크리스트

기본 이해

  • 3가지 변수의 선언 위치 차이를 안다
  • 각각의 저장 영역(Stack · Heap · Method Area)을 매핑할 수 있다
  • 자동 초기화 규칙 차이를 안다 (지역만 수동)
  • 객체 100개 만들었을 때 각 변수가 몇 개인지 즉답 가능
  • 매개변수가 지역 변수임을 안다

실전 적용

  • Entity의 필드를 인스턴스 변수로 작성할 수 있다
  • Service에 가변 static을 두지 않는 이유를 안다
  • 상수를 public static final로 정의할 수 있다
  • this 키워드를 적절히 쓸 수 있다
  • final 인스턴스 변수 + 생성자 주입 패턴을 안다

면접 대비 — 5분 답변

  • 3종 변수의 메모리 영역과 수명 차이
  • 객체 N개 생성 시 변수 개수 계산
  • 매개변수의 정체와 Pass by Value
  • 가변 static의 위험성
  • Spring DI와 인스턴스 변수의 final 패턴

🎯 핵심 요약 — 3줄 정리

1. "어디에 선언했는가"가 모든 것을 결정한다

  • 메서드 안 → 지역 (Stack, 메서드 종료 시 소멸)
  • 클래스 안 + static 없음 → 인스턴스 (Heap, 객체와 함께)
  • 클래스 안 + static → 클래스 (Method Area, JVM 종료까지)

2. 초기화 · 개수 · 수명이 모두 다르다

  • 자동 초기화: 인스턴스 · 클래스 ✓ / 지역 ❌ (수동 필수)
  • 개수: 호출당 / 객체당 / 클래스당 단 1개
  • GC 대상: 인스턴스 ✓ / 클래스 ❌ / 지역은 자동 소멸

3. ILIC 실무 원칙

  • Entity 필드 → 인스턴스 변수
  • 상수 → public static final
  • Service → final 인스턴스 변수 + 생성자 주입
  • 가변 static은 거의 항상 함정 — 멀티스레드 race condition

📚 다음으로...

Unit 1.2 — 변수별 저장 위치 (Stack / Heap / Method Area)

이번 Unit에서 "어디에 저장된다"고 한 부분을 메모리 다이어그램으로 정확히 추적한다.

  • Stack 프레임 구조
  • Heap의 객체와 인스턴스 변수
  • Method Area의 클래스 정보 + 클래스 변수
  • 참조(reference)는 어디에, 객체 본체는 어디에?

2주차 진행 상황

  • Unit 1.1 자바 변수의 3종류 — 이 문서
  • ⏭ Unit 1.2 변수별 저장 위치
  • ⏭ Unit 1.3 Method Area의 3개 존 (★ 2주차 핵심)
  • ⏭ Unit 1.4 Stack Area의 동작
  • ⏭ Unit 1.5 Heap과 객체-Metadata 연결
  • ⏭ Unit 1.6 Literal Pool Area
  • ⏭ Phase 2 (메서드 실행 메커니즘)
  • ⏭ Phase 3 (바이트코드와 상수 풀) ★ 2주차의 정점

1주차 → 2주차 연결점

  • 1주차 Unit 4.1 (JVM 런타임 데이터 영역)에서 5분할을 봤다면
  • 2주차에서는 그것을 변수 단위로 매핑 + Method Area의 3개 존 분해
  • 결국 Phase 3의 바이트코드 읽기까지 이어진다
profile
Software Developer

0개의 댓글