🎯1μ£Όμ°¨ Unit 6.1 β€” String κ³Ό String Constant Pool

PsjΒ·μ–΄μ œ

F-lab

λͺ©λ‘ 보기
41/42

🎯 Unit 6.1 β€” String κ³Ό String Constant Pool β˜…β˜…β˜…

F-lab Java 1μ£Όμ°¨ / Phase 6 / Unit 6.1 본격 ν•™μŠ΅ 자료 β€” Phase 6 μ‹œμž‘!
9-μ„Ήμ…˜ λ§ˆμŠ€ν„° ν”„λ‘¬ν”„νŠΈ ν˜•μ‹μœΌλ‘œ 깊이 νŒŒν—€μΉœλ‹€.

μ„ μˆ˜ 지식: Phase 4 (JVM λ©”λͺ¨λ¦¬), Phase 5 (GC)
λ‹€μŒ Unit: 6.2 β€” StringBuilder vs StringBuffer

이 Unit의 의미: μžλ°”μ—μ„œ κ°€μž₯ νŠΉλ³„ν•œ 클래슀 β€” String.
λ©΄μ ‘ μ΅œλ‹¨κ³¨ (== vs equals, new String() vs literal) 의 μ •ν™•ν•œ λ‹΅.
Phase 4-5 의 λ©”λͺ¨λ¦¬ 이해 μœ„μ—μ„œ String 의 특수 λŒ€μš°λ₯Ό 풀어냄.


🌍 1. 세상 속 λΉ„μœ 

String Constant Pool = "곡용 λ„μ„œκ΄€"

λ„μ„œκ΄€ μ‹œμŠ€ν…œμ„ μƒμƒν•΄λ³΄μ„Έμš”.

μ‹œλ‚˜λ¦¬μ˜€ 1 β€” λ„μ„œκ΄€ μ—†λŠ” 세상 (Pool μ—†μŒ)

  • μ‚¬λžŒλ§ˆλ‹€ 자기 μ±… 1κΆŒμ”© λ“€κ³  λ‹€λ‹˜
  • 같은 μ±… 읽고 μ‹ΆμœΌλ©΄ β†’ μƒˆλ‘œ μ°μ–΄μ„œ 가짐
  • 1000λͺ…이 같은 λ² μŠ€νŠΈμ…€λŸ¬ 읽으렀면 β†’ 1000ꢌ ν•„μš”
  • β†’ λ©”λͺ¨λ¦¬ λ‚­λΉ„ ⚠️

μ‹œλ‚˜λ¦¬μ˜€ 2 β€” 곡용 λ„μ„œκ΄€ (Pool 있음)

  • λ„μ„œκ΄€μ— μ±… 1ꢌ만 보관
  • 1000λͺ…이 같은 μ±… 보렀 해도 β†’ λͺ¨λ‘ λ„μ„œκ΄€ μ±… μ°Έμ‘°
  • β†’ λ©”λͺ¨λ¦¬ μ ˆμ•½ βœ“
  • β†’ 같은 μ±… = 같은 μ±… (식별 κ°€λŠ₯)

β†’ 이게 String Constant Pool 의 본질.


String 자체 = "μ΄λ¦„ν‘œ (immutable)"

μ΄λ¦„ν‘œλŠ” ν•œλ²ˆ μΈμ‡„ν•˜λ©΄ λ°”κΏ€ 수 μ—†μŠ΅λ‹ˆλ‹€:

  • "λ°•μŠΉμ œ" μ΄λ¦„ν‘œλ₯Ό λ§Œλ“€λ©΄
  • κ·Έ μ΄λ¦„ν‘œλŠ” μ˜μ›νžˆ "λ°•μŠΉμ œ"
  • "λ°•μŠΉμ œ2" 둜 λ°”κΎΈλ €λ©΄ β†’ μƒˆ μ΄λ¦„ν‘œ λ§Œλ“€μ–΄μ•Ό 함
  • κΈ°μ‘΄ μ΄λ¦„ν‘œλŠ” κ·ΈλŒ€λ‘œ

β†’ String 의 λΆˆλ³€μ„± (Immutability) 의 본질.


핡심 ν•œ λ¬Έμž₯

"String 은 μžλ°”μ—μ„œ κ°€μž₯ νŠΉλ³„ν•œ 클래슀 β€” λΆˆλ³€ + 곡용 ν’€ (Pool) 으둜 λ©”λͺ¨λ¦¬ 효율과 μ•ˆμ „μ„±μ„ λ™μ‹œμ— ν™•λ³΄ν•œλ‹€."

λΉ„μœ  정리:

λΉ„μœ μžλ°” κ°œλ…μ˜λ―Έ
곡용 λ„μ„œκ΄€String Constant Pool같은 λ¬Έμžμ—΄μ€ 1개만
μ΄λ¦„ν‘œImmutable Stringν•œλ²ˆ λ§Œλ“€λ©΄ λ³€κ²½ λΆˆκ°€
λ„μ„œκ΄€ μΉ΄λ“œμ°Έμ‘° (Reference)같은 μ±… 가리킴
μƒˆ μ±… 인쇄new String()Pool 우회, Heap 에 μƒˆλ‘œ

πŸ”₯ 2. 탄생 λ°°κ²½

"μ™œ String 만 νŠΉλ³„ λŒ€μš°?" β€” λΉˆλ„ + μœ„ν—˜μ„±

μžλ°”μ—μ„œ κ°€μž₯ 많이 μ“°λŠ” 객체 = String.

// 일상 μ½”λ“œμ˜ 90%
String name = "Alice";
String email = "alice@example.com";
String status = "ACTIVE";
log.info("처리 μ™„λ£Œ: " + result);

λ§Œμ•½ λͺ¨λ“  String 이 일반 객체처럼 λ™μž‘ν–ˆλ‹€λ©΄?


문제 1: λ©”λͺ¨λ¦¬ 폭발

List<Customer> customers = customerRepository.findAll();  // 1만 λͺ…

for (Customer c : customers) {
    if (c.getStatus().equals("ACTIVE")) {  // "ACTIVE" 맀번 μƒˆ 객체?
        // ...
    }
}

Pool μ—†λŠ” 세상:

  • "ACTIVE" λ¦¬ν„°λŸ΄ β†’ λ§€ λ°˜λ³΅λ§ˆλ‹€ μƒˆ String 객체
  • 1만 번 반볡 β†’ 1만 개 String 객체
  • λ©”λͺ¨λ¦¬ λ‚­λΉ„ + GC λΆ€λ‹΄

Pool μžˆλŠ” 세상:

  • "ACTIVE" λ¦¬ν„°λŸ΄ β†’ Pool 의 1개 String μ°Έμ‘°
  • 1만 번 반볡 β†’ 1개 String 객체 곡유
  • λ©”λͺ¨λ¦¬ μ ˆμ•½ + GC λΆ€λ‹΄ ↓

문제 2: λ™μ‹œμ„± 문제

// 가상 μ‹œλ‚˜λ¦¬μ˜€ β€” λ§Œμ•½ String 이 가변이라면
String userId = "alice";
Thread1: userId += "_admin";  // "alice_admin"
Thread2: log.info(userId);    // μ–΄λ–€ κ°’?

κ°€λ³€ String 이라면:

  • ν•œ μŠ€λ ˆλ“œκ°€ μˆ˜μ • β†’ λ‹€λ₯Έ μŠ€λ ˆλ“œκ°€ 영ν–₯
  • 동기화 ν•„μš” β†’ μ„±λŠ₯ ↓
  • λ³΄μ•ˆ μœ„ν—˜ (인증 우회 λ“±)

λΆˆλ³€ String 이라면:

  • μˆ˜μ • λΆˆκ°€ β†’ 항상 같은 κ°’
  • 동기화 λΆˆν•„μš”
  • μžμ—°μŠ€λŸ¬μš΄ thread-safe

문제 3: λ³΄μ•ˆ μœ„ν—˜

// λ³΄μ•ˆ 검증 μ˜ˆμ‹œ
public void connectDB(String url, String username, String password) {
    if (isValidUser(username, password)) {
        // 검증 톡과
        db.connect(url, username, password);
    }
}

κ°€λ³€ String 이라면:

  • isValidUser() 호좜 ν›„ β†’ λ‹€λ₯Έ μŠ€λ ˆλ“œκ°€ password λ³€κ²½
  • β†’ λ‹€λ₯Έ password 둜 connect
  • β†’ TOCTOU (Time-Of-Check Time-Of-Use) λ³΄μ•ˆ 취약점

λΆˆλ³€ String 이라면:

  • 검증 μ‹œμ μ˜ κ°’ = μ‚¬μš© μ‹œμ μ˜ κ°’
  • λ³΄μ•ˆ 보μž₯

문제 4: HashMap 의 ν‚€λ‘œ μ‚¬μš©

Map<String, Customer> cache = new HashMap<>();
cache.put("alice", new Customer("Alice"));

// ...

Customer c = cache.get("alice");

κ°€λ³€ String 이라면:

  • ν‚€ "alice" 의 hashCode κ°€ λ³€ν•  수 있음
  • β†’ put ν•œ ν›„ ν‚€κ°€ λ°”λ€Œλ©΄ β†’ get μ‹œ λͺ» 찾음
  • β†’ λ©”λͺ¨λ¦¬ λˆ„μˆ˜

λΆˆλ³€ String 이라면:

  • hashCode μ˜μ›νžˆ κ°™μŒ
  • HashMap 정상 λ™μž‘

μžλ°”μ˜ λ‹΅ β€” 두 κ°€μ§€ ν•΄κ²°μ±…

μžλ°”λŠ” 두 κ°€μ§€λ₯Ό λ™μ‹œμ— 적용:

1. λΆˆλ³€μ„± (Immutability)

  • String κ°μ²΄λŠ” 생성 ν›„ λ³€κ²½ λΆˆκ°€
  • λ™μ‹œμ„± μ•ˆμ „, λ³΄μ•ˆ μ•ˆμ „, hashCode μ•ˆμ •

2. String Constant Pool

  • 같은 λ¬Έμžμ—΄ λ¦¬ν„°λŸ΄μ€ 1개의 객체 곡유
  • λ©”λͺ¨λ¦¬ 효율 ↑

역사적 λ³€ν™” β€” Pool 의 μœ„μΉ˜

Java 버전SCP μœ„μΉ˜λΉ„κ³ 
Java 1~6PermGenλ©”λͺ¨λ¦¬ μ œν•œ, OOM μœ„ν—˜
Java 7Heap (Old Gen)GC λŒ€μƒ βœ“
Java 8+HeapPermGen β†’ Metaspace

Java 7 의 λ³€ν™” 의의:

  • PermGen OOM μœ„ν—˜ ν•΄μ†Œ
  • intern() 호좜 μ‹œ GC κ°€λŠ₯
  • 더 큰 Pool ν™œμš© κ°€λŠ₯

핡심 톡찰

"String 의 특수 λŒ€μš° = μžλ°”μ˜ κ°€μž₯ μ˜λ¦¬ν•œ 섀계 κ²°μ • 쀑 ν•˜λ‚˜."

λΉˆλ²ˆν•˜κ²Œ μ‚¬μš©λ˜λŠ” String 의 νŠΉμ„±μ„ μΈμ‹ν•˜κ³ , λΆˆλ³€μ„± + Pool 두 κ°€μ§€λ‘œ λ©”λͺ¨λ¦¬/μ„±λŠ₯/λ³΄μ•ˆ λͺ¨λ‘ ν•΄κ²°. μžλ°”μ˜ λ””μžμΈ μ² ν•™ ("κ°œλ°œμžκ°€ μ‹€μˆ˜ν•˜κΈ° μ–΄λ ΅κ²Œ + μ‹œμŠ€ν…œμ΄ μ•Œμ•„μ„œ 효율") 의 μ •μˆ˜.


πŸ’£ 3. μ—†μœΌλ©΄ μƒκΈ°λŠ” 문제

μ‹œλ‚˜λ¦¬μ˜€ 1: ILIC 의 λ“±κΈ‰ 비ꡐ 버그

// ILIC μ½”λ“œ
public class CustomerService {
    
    public boolean isVipCustomer(Customer customer) {
        // ❌ 버그 κ°€λŠ₯ μ½”λ“œ
        if (customer.getGrade() == "VIP") {
            return true;
        }
        return false;
    }
}

문제:

  • == λŠ” μ°Έμ‘° 비ꡐ (λ©”λͺ¨λ¦¬ μ£Όμ†Œ)
  • customer.getGrade() κ°€ DB μ—μ„œ κ°€μ Έμ˜¨ 값이라면 β†’ Pool 의 "VIP" 와 λ‹€λ₯Έ 객체
  • β†’ == 비ꡐ μ‹€νŒ¨
  • VIP 고객인데 일반 고객으둜 처리됨 β†’ 운영 사고

μ˜¬λ°”λ₯Έ μ½”λ“œ:

if ("VIP".equals(customer.getGrade())) {  // βœ“ equals μ‚¬μš©
    return true;
}

β†’ String Pool 이해 λͺ»ν•˜λ©΄ 이런 버그 λ°œμƒ.


μ‹œλ‚˜λ¦¬μ˜€ 2: λ©΄μ ‘ 단골 질문

String a = "hello";
String b = "hello";
String c = new String("hello");

System.out.println(a == b);            // ?
System.out.println(a == c);            // ?
System.out.println(a.equals(c));       // ?
System.out.println(a == c.intern());   // ?

Pool λͺ¨λ₯΄λ©΄:

  • λͺ¨λ“  닡을 ν—€λ§€κ±°λ‚˜ ν‹€λ¦Ό
  • μ‹œλ‹ˆμ–΄ 자격 μ˜μ‹¬

Pool μ•Œλ©΄:

  • a == b: true (Pool 의 같은 객체)
  • a == c: false (c λŠ” Heap 의 μƒˆ 객체)
  • a.equals(c): true (κ°’ 비ꡐ)
  • a == c.intern(): true (intern() 으둜 Pool 의 객체 λ°˜ν™˜)

μ‹œλ‚˜λ¦¬μ˜€ 3: λ©”λͺ¨λ¦¬ λˆ„μˆ˜ (intern 잘λͺ» μ‚¬μš©)

// ❌ μœ„ν—˜ν•œ μ½”λ“œ
@RestController
public class ApiController {
    @PostMapping("/log")
    public void logUserAction(@RequestBody String userInput) {
        userInput.intern();  // ❌ μ‚¬μš©μž μž…λ ₯을 Pool 에!
        // ...
    }
}

문제:

  • μ‚¬μš©μž μž…λ ₯ (λ¬΄ν•œνžˆ λ‹€μ–‘) 을 λͺ¨λ‘ Pool 에 적재
  • Pool 크기 폭발 β†’ OutOfMemoryError
  • μ˜›λ‚  μžλ°”μ—μ„œλŠ” PermGen OOM 단골 원인

Pool μ΄ν•΄ν•˜λ©΄:

  • intern() 은 μ‹ μ€‘ν•˜κ²Œ 써야 함
  • 동적 데이터에 적용 X

μ‹œλ‚˜λ¦¬μ˜€ 4: SQL Injection λ°©μ§€ μ½”λ“œ

// λ³΄μ•ˆ 검증
public boolean isValidQuery(String userInput) {
    if (userInput.contains("DROP TABLE")) {
        return false;
    }
    return true;
}

κ°€λ³€ String μ΄μ—ˆλ‹€λ©΄:

  • 검증 μ‹œμ : "SELECT * FROM users"
  • λ‹€λ₯Έ μŠ€λ ˆλ“œκ°€ λ³€κ²½: "DROP TABLE users"
  • μ‚¬μš© μ‹œμ : λ³€κ²½λœ κ°’ β†’ SQL Injection κ°€λŠ₯

λΆˆλ³€ String 의 μ•ˆμ „:

  • 검증과 μ‚¬μš© μ‹œμ μ˜ 값이 항상 κ°™μŒ
  • λ³΄μ•ˆ 보μž₯

μ‹œλ‚˜λ¦¬μ˜€ 5: String μ—°κ²°μ˜ λΉ„νš¨μœ¨

// ❌ λΉ„νš¨μœ¨ μ½”λ“œ
String result = "";
for (int i = 0; i < 10000; i++) {
    result += "item" + i;  // 맀번 μƒˆ String 생성!
}

λΆˆλ³€μ„± λͺ¨λ₯΄λ©΄:

  • "κ·Έλƒ₯ λ³€μˆ˜μ— μΆ”κ°€" 라고 생각
  • β†’ λ§€ λ°˜λ³΅λ§ˆλ‹€ μƒˆ String 생성
  • β†’ 1만 번 = 1만 개 String 객체
  • β†’ O(nΒ²) μ‹œκ°„ λ³΅μž‘λ„

λΆˆλ³€μ„± μ•Œλ©΄:

  • StringBuilder μ‚¬μš© (Unit 6.2 주제)
  • β†’ O(n) μ‹œκ°„ λ³΅μž‘λ„

μ‹œλ‚˜λ¦¬μ˜€ 6: ILIC 의 ν•­λ§Œ μ½”λ“œ 비ꡐ

// ν•­λ§Œ μ½”λ“œ 처리
List<Cargo> cargos = ...;
for (Cargo c : cargos) {
    if (c.getOriginPort() == "BUSAN") {  // ❌ μœ„ν—˜!
        processBusan(c);
    }
}

버그 λ°œμƒ μΌ€μ΄μŠ€:

  • DB μ—μ„œ κ°€μ Έμ˜¨ "BUSAN" 은 Pool μ™ΈλΆ€ 객체
  • == κ°€ false β†’ λΆ€μ‚° 좜발 ν™”λ¬Ό 처리 μ•ˆ 됨
  • β†’ 운영 사고

μ˜¬λ°”λ₯Έ μ½”λ“œ:

if ("BUSAN".equals(c.getOriginPort())) {  // βœ“
    processBusan(c);
}

μ‹œλ‚˜λ¦¬μ˜€λ³„ 영ν–₯도

μ‹œλ‚˜λ¦¬μ˜€Pool λͺ¨λ₯΄λ©΄Pool μ•Œλ©΄
equals λ²„κ·Έμš΄μ˜ μ‚¬κ³ μ •ν™•ν•œ 비ꡐ
λ©΄μ ‘ μ§ˆλ¬Ένƒˆλ½μ‹œλ‹ˆμ–΄ λ‹΅λ³€
intern 였용OOMμ‹ μ€‘ν•œ μ‚¬μš©
λ³΄μ•ˆμ·¨μ•½μ μ•ˆμ „
μ„±λŠ₯O(nΒ²)StringBuilder
운영 μ½”λ“œκ°„ν—μ  λ²„κ·Έμ•ˆμ •

β†’ String μ΄ν•΄λŠ” μžλ°” μ‹œλ‹ˆμ–΄μ˜ κΈ°λ³Έ.


βœ… 4. ν•΄κ²°μ±… β€” String 의 두 μΆ•

μΆ• 1: λΆˆλ³€μ„± (Immutability)

String 의 λ‚΄λΆ€ ꡬ쑰 (Java 9+)

public final class String {
    private final byte[] value;  // μ‹€μ œ 문자 데이터
    private final byte coder;    // 인코딩 (LATIN1 or UTF16)
    private int hash;            // μΊμ‹œλœ hashCode
    
    // ... methods
}

핡심 포인트:
1. final 클래슀 β€” 상속 λΆˆκ°€
2. private final ν•„λ“œ β€” μ™ΈλΆ€μ—μ„œ λ³€κ²½ λΆˆκ°€
3. λͺ¨λ“  λ©”μ„œλ“œ β€” μƒˆ String λ°˜ν™˜ (μˆ˜μ • X)


λΆˆλ³€μ„±μ˜ 증거

String s = "hello";
String s2 = s.toUpperCase();  // s λŠ” κ·ΈλŒ€λ‘œ

System.out.println(s);   // "hello" (λ³€ν•˜μ§€ μ•ŠμŒ!)
System.out.println(s2);  // "HELLO" (μƒˆ String 객체)

λͺ¨λ“  λ³€ν™˜ λ©”μ„œλ“œ λŠ” μƒˆ String λ°˜ν™˜:

  • toUpperCase(), toLowerCase()
  • trim(), strip()
  • replace(), concat()
  • substring()

λΆˆλ³€μ„±μ˜ 이점 ⭐

이점섀λͺ…
Thread-Safe동기화 λΆˆν•„μš”
HashMap ν‚€ μ•ˆμ „hashCode μ˜μ›νžˆ κ°™μŒ
λ³΄μ•ˆTOCTOU λ°©μ§€
캐싱 κ°€λŠ₯같은 κ°’ = 같은 객체
String Pool κ°€λŠ₯κ³΅μœ ν•΄λ„ μ•ˆμ „

μΆ• 2: String Constant Pool (SCP)

Pool 의 본질

JVM μ•ˆμ˜ νŠΉλ³„ν•œ μ˜μ—­:

  • λͺ¨λ“  λ¬Έμžμ—΄ λ¦¬ν„°λŸ΄μ΄ μ €μž₯됨
  • 같은 κ°’ = 같은 객체 (1개만)
  • μœ„μΉ˜: Heap (Java 7+)
[JVM Heap]
β”œβ”€β”€ Young Generation
β”œβ”€β”€ Old Generation
β”‚   └── String Constant Pool
β”‚       β”œβ”€β”€ "hello" (객체 1개)
β”‚       β”œβ”€β”€ "world" (객체 1개)
β”‚       └── "VIP"   (객체 1개)
└── Metaspace

λ¦¬ν„°λŸ΄ vs new String()

String a = "hello";              // λ¦¬ν„°λŸ΄ β†’ Pool
String b = "hello";              // 같은 λ¦¬ν„°λŸ΄ β†’ Pool 의 같은 객체
String c = new String("hello");  // new β†’ Heap 의 μƒˆ 객체

λ©”λͺ¨λ¦¬ κ·Έλ¦Ό:

[Stack]
  a (μ°Έμ‘°)
  b (μ°Έμ‘°)  ─── 같은 곳을 가리킴
  c (μ°Έμ‘°)

[Heap]
β”œβ”€β”€ String Constant Pool
β”‚   └── "hello" ← a, b κ°€ μ°Έμ‘°
β”‚
└── 일반 Heap
    └── "hello" ← c κ°€ μ°Έμ‘° (별도 객체)

검증:

a == b           // true (같은 Pool 객체)
a == c           // false (λ‹€λ₯Έ 객체)
a.equals(c)      // true (κ°’ 비ꡐ)

intern() λ©”μ„œλ“œ

μ—­ν• : Heap 객체λ₯Ό Pool 둜 κ°€μ Έμ˜€κΈ° (λ˜λŠ” Pool 의 객체 λ°˜ν™˜)

String a = "hello";              // Pool
String c = new String("hello");  // Heap

String d = c.intern();  // Pool 의 "hello" λ°˜ν™˜

System.out.println(a == d);  // true
System.out.println(c == d);  // false (c λŠ” μ—¬μ „νžˆ Heap)

λ™μž‘ 원리:
1. c.intern() 호좜
2. JVM 이 Pool 에 "hello" κ°€ μžˆλŠ”μ§€ 확인
3. 있으면 κ·Έ 객체 λ°˜ν™˜ / μ—†μœΌλ©΄ μΆ”κ°€ ν›„ λ°˜ν™˜


== vs equals ⭐⭐⭐ (λ©΄μ ‘ 단골)

비ꡐ==.equals()
비ꡐ λŒ€μƒμ°Έμ‘° (μ£Όμ†Œ)κ°’ (λ‚΄μš©)
Pool 영ν–₯λ°›μŒμ•ˆ λ°›μŒ
ꢌμž₯βŒβœ“
String a = "hello";
String b = new String("hello");

a == b           // false (λ‹€λ₯Έ 객체)
a.equals(b)      // true (같은 κ°’)

원칙: String 비ꡐ μ‹œ 항상 equals() μ‚¬μš©.


λ©”μ„œλ“œλ³„ λ™μž‘ 정리

String s = "Hello World";

// === λ³€ν™˜ (μƒˆ String λ°˜ν™˜) ===
s.toUpperCase()       // "HELLO WORLD" (μƒˆ 객체)
s.toLowerCase()       // "hello world"
s.trim()              // μ–‘μͺ½ 곡백 제거
s.replace("o", "0")   // μΉ˜ν™˜
s.substring(0, 5)     // "Hello"
s.concat(" !")        // "Hello World !"

// === 비ꡐ ===
s.equals("Hello World")           // true
s.equalsIgnoreCase("HELLO WORLD") // true
s.compareTo("Hello")              // μ–‘μˆ˜ (사전 순)

// === 검사 ===
s.contains("World")  // true
s.startsWith("Hello") // true
s.endsWith("ld")     // true
s.isEmpty()          // false
s.isBlank()          // false (곡백만 μžˆμ–΄λ„ true) [Java 11+]

// === 정보 ===
s.length()           // 11
s.charAt(0)          // 'H'
s.indexOf("World")   // 6

πŸ—οΈ 5. λ‚΄λΆ€ λ™μž‘ 원리

Pool 의 등둝 μ‹œμ 

컴파일 νƒ€μž„ 등둝

String a = "hello";  // 컴파일 μ‹œ Pool 에 등둝 μ˜ˆμ•½
String b = "hello";  // 같은 λ¦¬ν„°λŸ΄ β†’ 같은 Pool 객체

// 컴파일된 .class νŒŒμΌμ—:
// Constants Pool 정보 포함
// β†’ 클래슀 λ‘œλ”© μ‹œ String Pool 에 μΆ”κ°€

핡심: λ¦¬ν„°λŸ΄μ€ 클래슀 λ‘œλ”© μ‹œμ  에 Pool 에 등둝.


λŸ°νƒ€μž„ 등둝 (intern)

String c = new String("hello");  // Heap 객체
String d = c.intern();  // Pool 에 등둝 (μ—†μœΌλ©΄) + λ°˜ν™˜

Pool 의 데이터 ꡬ쑰

JVM λ‚΄λΆ€ κ΅¬ν˜„ (HotSpot):

  • HashTable 기반
  • ν‚€: String 의 hashCode
  • κ°’: String 객체 μ°Έμ‘°
  • β†’ O(1) 쑰회
StringTable (HashTable)
β”œβ”€β”€ bucket[0] β†’ "hello" β†’ "world" β†’ ...
β”œβ”€β”€ bucket[1] β†’ "VIP"
β”œβ”€β”€ bucket[2] β†’ ...
└── bucket[N] β†’ ...

κΈ°λ³Έ 크기 (Java 8+): 60013 (μ†Œμˆ˜, 좩돌 μ΅œμ†Œν™”)
μ‘°μ •: -XX:StringTableSize=N


+ μ—°μ‚°μžμ˜ λ™μž‘

String a = "Hello";
String b = "World";
String c = a + " " + b;  // μ–΄λ–»κ²Œ λ™μž‘?

컴파일러 λ³€ν™˜ (Java 8 이전):

String c = new StringBuilder()
    .append(a)
    .append(" ")
    .append(b)
    .toString();

Java 9+ (Indify):

  • invokedynamic 으둜 더 효율적 처리
  • μ»΄νŒŒμΌλŸ¬κ°€ μ΅œμ ν™” μ•Œκ³ λ¦¬μ¦˜ 선택

컴파일 νƒ€μž„ μƒμˆ˜ 폴딩

String a = "Hello" + " " + "World";  // 컴파일 μ‹œ 폴딩
String b = "Hello World";

System.out.println(a == b);  // true!

컴파일러 λ™μž‘:

  • "Hello" + " " + "World" β†’ 컴파일 μ‹œ "Hello World" 둜 λ³€ν™˜
  • Pool 의 같은 객체 μ°Έμ‘°

주의: λ³€μˆ˜κ°€ μ„žμ΄λ©΄ 폴딩 X

String s = "Hello";
String a = s + " World";  // λŸ°νƒ€μž„ μ—°μ‚°
String b = "Hello World";

System.out.println(a == b);  // false

Java 9+ 의 Compact Strings

문제: ASCII λ¬Έμžμ—΄λ„ UTF-16 으둜 μ €μž₯ β†’ λ©”λͺ¨λ¦¬ 2λ°° λ‚­λΉ„

ν•΄κ²° (Java 9+):

public final class String {
    private final byte[] value;  // byte[] 둜 λ³€κ²½ (Java 8 κΉŒμ§€λŠ” char[])
    private final byte coder;    // LATIN1 (1 byte/char) or UTF16 (2 bytes/char)
}

효과:

  • ASCII 만 μ‚¬μš© μ‹œ β†’ λ©”λͺ¨λ¦¬ 50% μ ˆμ•½
  • ν•œκΈ€ λ“± λΉ„ ASCII μ‹œ β†’ UTF16 (κΈ°μ‘΄κ³Ό 동일)
  • ILIC 같은 ν•œκΈ€/영문 혼용 μ‹œμŠ€ν…œμ—μ„œ 효과 μž‘μ„ 수 있음

intern() 의 λΉ„μš©

μ‹œκ°„:

  • HashTable 쑰회: O(1) 평균
  • μ—†μœΌλ©΄ μΆ”κ°€: O(1)

곡간:

  • Pool 에 좔가됨 β†’ λ©”λͺ¨λ¦¬ μ‚¬μš©
  • Java 7+ μ—μ„œλŠ” GC κ°€λŠ₯ (Heap 에 μžˆμœΌλ―€λ‘œ)

μœ„ν—˜:

  • 동적 데이터 (μ‚¬μš©μž μž…λ ₯ λ“±) intern() β†’ Pool 폭발
  • β†’ OOM μœ„ν—˜

Pool 의 GC

Java 6 이전: PermGen β†’ GC 거의 μ•ˆ 됨
Java 7+: Heap β†’ 정상 GC λŒ€μƒ

쑰건:

  • Pool μ•ˆμ˜ String 도 도달 κ°€λŠ₯μ„± (Reachability) 으둜 νŒλ‹¨
  • 더 이상 μ°Έμ‘° μ—†μœΌλ©΄ GC 회수
  • β†’ 동적 intern() 도 (이둠상) 회수 κ°€λŠ₯

싀무: κ·Έλž˜λ„ 동적 intern 은 μœ„ν—˜.


πŸ’» 6. μ‹€μ „ μ½”λ“œ μ˜ˆμ‹œ

μ˜ˆμ‹œ 1: ILIC 의 μ•ˆμ „ν•œ λ“±κΈ‰ 비ꡐ

public class CustomerService {
    
    // ❌ μœ„ν—˜: == μ‚¬μš©
    public boolean isVipUnsafe(Customer c) {
        return c.getGrade() == "VIP";  // 버그 κ°€λŠ₯
    }
    
    // βœ“ μ•ˆμ „: equals μ‚¬μš©
    public boolean isVipSafe(Customer c) {
        return "VIP".equals(c.getGrade());  // null-safe
    }
    
    // βœ“ 더 쒋은 방법: Enum μ‚¬μš©
    public boolean isVipBest(Customer c) {
        return c.getGrade() == CustomerGrade.VIP;  // Enum 은 == μ•ˆμ „
    }
}

enum CustomerGrade {
    NORMAL, SILVER, GOLD, VIP
}

팁:

  • λ¬Έμžμ—΄ μƒμˆ˜κ°€ λ§Žμ„μˆ˜λ‘ β†’ Enum ꢌμž₯
  • Enum 은 Pool 처럼 λ™μž‘ + == 비ꡐ μ•ˆμ „

μ˜ˆμ‹œ 2: Pool λ™μž‘ μ‹œκ°μ  확인

public class StringPoolDemo {
    public static void main(String[] args) {
        String a = "hello";
        String b = "hello";
        String c = new String("hello");
        String d = c.intern();
        
        System.out.println("a == b: " + (a == b));            // true
        System.out.println("a == c: " + (a == c));            // false
        System.out.println("a.equals(c): " + a.equals(c));    // true
        System.out.println("a == d: " + (a == d));            // true
        System.out.println("c == d: " + (c == d));            // false
        
        // hashCode 확인
        System.out.println("a hash: " + System.identityHashCode(a));
        System.out.println("c hash: " + System.identityHashCode(c));
    }
}

μ˜ˆμ‹œ 3: 컴파일 νƒ€μž„ ν΄λ”©μ˜ 함정

public class CompileTimeFoldingDemo {
    public static void main(String[] args) {
        String a = "Hello" + " " + "World";  // 컴파일 μ‹œ 폴딩
        String b = "Hello World";
        
        System.out.println(a == b);  // true (폴딩됨)
        
        // λ³€μˆ˜κ°€ λ“€μ–΄κ°€λ©΄?
        String hello = "Hello";
        String c = hello + " World";  // λŸ°νƒ€μž„ μ—°μ‚°
        
        System.out.println(b == c);   // false (Heap 객체)
        System.out.println(b.equals(c));  // true (값은 κ°™μŒ)
    }
}

μ˜ˆμ‹œ 4: 큰 λ¬Έμžμ—΄μ˜ λ©”λͺ¨λ¦¬

public class LargeStringDemo {
    public static void main(String[] args) {
        // 1000개의 λΉ„μŠ·ν•œ String
        List<String> strings = new ArrayList<>();
        
        // ❌ λΉ„νš¨μœ¨: 맀번 new
        for (int i = 0; i < 1000; i++) {
            strings.add(new String("ACTIVE"));  // 1000개 객체
        }
        
        // βœ“ 효율: literal μ‚¬μš©
        for (int i = 0; i < 1000; i++) {
            strings.add("ACTIVE");  // Pool 의 1개 객체 곡유
        }
        
        // βœ“ 동적 데이터λ₯Ό μ‹ μ€‘νžˆ intern
        Set<String> uniqueDomains = new HashSet<>();
        for (Customer c : customers) {
            String domain = extractDomain(c.getEmail());
            uniqueDomains.add(domain.intern());  // 도메인은 ν•œμ •μ 
        }
    }
}

μ˜ˆμ‹œ 5: ILIC 의 ν•­λ§Œ μ½”λ“œ 처리

public class PortService {
    
    // ILIC 의 ν•­λ§Œ μ½”λ“œ β€” ν•œμ •λœ μ§‘ν•© (~수백 개)
    private static final Set<String> KNOWN_PORTS = Set.of(
        "BUSAN", "INCHEON", "ULSAN", "TOKYO", "SHANGHAI", "LA"
    );
    
    // βœ“ Pool ν™œμš© β€” μ•ˆμ „ν•œ 비ꡐ
    public boolean isKoreanPort(String portCode) {
        return "BUSAN".equals(portCode) 
            || "INCHEON".equals(portCode) 
            || "ULSAN".equals(portCode);
    }
    
    // βœ“ 더 쒋은 방법: Enum
    public enum Port {
        BUSAN("KR"), INCHEON("KR"), ULSAN("KR"),
        TOKYO("JP"), SHANGHAI("CN"), LA("US");
        
        private final String country;
        
        Port(String country) {
            this.country = country;
        }
        
        public boolean isKorean() {
            return "KR".equals(country);
        }
    }
}

μ˜ˆμ‹œ 6: String λΉ„κ΅μ˜ best practice

public class StringComparison {
    
    // ❌ NullPointerException μœ„ν—˜
    public boolean compareUnsafe(String a, String b) {
        return a.equals(b);  // a κ°€ null 이면 NPE!
    }
    
    // βœ“ μƒμˆ˜κ°€ μ•žμœΌλ‘œ
    public boolean compareSafe(String input) {
        return "ACTIVE".equals(input);  // input 이 null 이어도 false λ°˜ν™˜
    }
    
    // βœ“ Objects.equals (null-safe)
    public boolean compareModern(String a, String b) {
        return Objects.equals(a, b);  // λ‘˜ λ‹€ null 이면 true, ν•œμͺ½λ§Œ null 이면 false
    }
    
    // βœ“ λŒ€μ†Œλ¬Έμž λ¬΄μ‹œ
    public boolean caseInsensitive(String a, String b) {
        return a != null && a.equalsIgnoreCase(b);
    }
}

μ˜ˆμ‹œ 7: ILIC 의 동적 데이터 처리

public class LogService {
    
    // ❌ μœ„ν—˜: μ‚¬μš©μž μž…λ ₯ intern
    public void logUserActionDangerous(String userInput) {
        userInput.intern();  // OOM μœ„ν—˜!
        log.info(userInput);
    }
    
    // βœ“ μ•ˆμ „: intern μ•ˆ 함
    public void logUserAction(String userInput) {
        log.info(userInput);  // Pool 에 μ•ˆ λ„£μŒ
    }
    
    // βœ“ ν•œμ •λœ μ§‘ν•©λ§Œ intern
    public void recordEventType(String eventType) {
        // 이벀트 νƒ€μž…μ€ ν•œμ •μ  (μˆ˜μ‹­ 개)
        Set<String> KNOWN_EVENTS = Set.of("LOGIN", "LOGOUT", "PURCHASE");
        if (KNOWN_EVENTS.contains(eventType)) {
            eventType = eventType.intern();  // μ•ˆμ „
        }
        // ...
    }
}

μ˜ˆμ‹œ 8: String concat 효율 비ꡐ

public class ConcatBenchmark {
    
    // ❌ O(nΒ²) β€” 맀번 μƒˆ String
    public String slowConcat(List<String> items) {
        String result = "";
        for (String item : items) {
            result += item;  // 맀번 μƒˆ 객체!
        }
        return result;
    }
    
    // βœ“ O(n) β€” StringBuilder
    public String fastConcat(List<String> items) {
        StringBuilder sb = new StringBuilder();
        for (String item : items) {
            sb.append(item);
        }
        return sb.toString();
    }
    
    // βœ“ Stream + Collectors
    public String streamConcat(List<String> items) {
        return items.stream()
            .collect(Collectors.joining());
    }
    
    // 100,000 개 처리 μ‹œ:
    // slowConcat: 수 λΆ„ (O(nΒ²))
    // fastConcat: ~10ms (O(n))
}

β†’ Unit 6.2 μ—μ„œ StringBuilder 깊이 λ‹€λ£Έ.


⚠️ 7. μ£Όμ˜μ‚¬ν•­ & ν”ν•œ μ‹€μˆ˜

μ‹€μˆ˜ 1: == 둜 String 비ꡐ

// ❌ κ°€μž₯ ν”ν•œ 버그
if (status == "ACTIVE") { ... }

문제: 객체 μΆœμ²˜μ— 따라 κ²°κ³Ό 닀름.

ν•΄κ²°: 항상 equals() μ‚¬μš©.

if ("ACTIVE".equals(status)) { ... }  // null-safe

μ‹€μˆ˜ 2: equals 호좜 μ‹œ NPE

String input = null;
if (input.equals("hello")) { ... }  // NullPointerException!

ν•΄κ²° 1: μƒμˆ˜λ₯Ό μ•žμ—

if ("hello".equals(input)) { ... }  // null 이면 false

ν•΄κ²° 2: Objects.equals

if (Objects.equals(input, "hello")) { ... }

μ‹€μˆ˜ 3: 동적 데이터 intern

@PostMapping("/log")
public void log(@RequestBody String userInput) {
    userInput.intern();  // ❌ OOM μœ„ν—˜
}

문제: λ¬΄ν•œ λ‹€μ–‘ν•œ μž…λ ₯ β†’ Pool 폭발.

ν•΄κ²°: intern 은 ν•œμ •λœ λ°μ΄ν„°μ—λ§Œ.


μ‹€μˆ˜ 4: String + 반볡

// ❌ λΉ„νš¨μœ¨
String result = "";
for (int i = 0; i < 100000; i++) {
    result += data[i];  // O(nΒ²)
}

ν•΄κ²°: StringBuilder

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
    sb.append(data[i]);
}
String result = sb.toString();  // O(n)

μ‹€μˆ˜ 5: substring 의 λ©”λͺ¨λ¦¬ λˆ„μˆ˜ (Java 6 이전)

μ˜›λ‚  μžλ°” (~Java 6):

String huge = "...";  // 100MB
String small = huge.substring(0, 10);  // 10자
huge = null;
// κ·ΈλŸ¬λ‚˜ small 이 huge 의 char[] μ°Έμ‘° β†’ 100MB λͺ» 회수!

Java 7+: substring 이 μƒˆ char[] 볡사 β†’ 해결됨.


μ‹€μˆ˜ 6: charset λ¬΄μ‹œ

byte[] bytes = "ν•œκΈ€".getBytes();  // ❌ ν”Œλž«νΌ κΈ°λ³Έ charset
String s = new String(bytes);  // λ‹€λ₯Έ ν”Œλž«νΌμ—μ„œ 깨짐

ν•΄κ²°:

byte[] bytes = "ν•œκΈ€".getBytes(StandardCharsets.UTF_8);
String s = new String(bytes, StandardCharsets.UTF_8);

μ‹€μˆ˜ 7: λ¬Έμžμ—΄ μƒμˆ˜ vs Enum

// ❌ λ¬Έμžμ—΄ μƒμˆ˜ λ‚¨μš©
public class Constants {
    public static final String STATUS_ACTIVE = "ACTIVE";
    public static final String STATUS_INACTIVE = "INACTIVE";
    public static final String STATUS_PENDING = "PENDING";
}

// μ‚¬μš©
if (Constants.STATUS_ACTIVE.equals(c.getStatus())) { ... }

문제:

  • 컴파일 νƒ€μž„ 검증 X (μ˜€νƒ€ κ°€λŠ₯)
  • μƒˆ μƒνƒœ μΆ”κ°€ μ‹œ 흩어진 처리

ν•΄κ²°: Enum

public enum Status {
    ACTIVE, INACTIVE, PENDING;
}

if (c.getStatus() == Status.ACTIVE) { ... }  // == μ•ˆμ „

πŸ”— 8. μ—°κ΄€ κ°œλ… λ§΅

Phase 6 (데이터 닀루기) μ—μ„œμ˜ μœ„μΉ˜

[Unit 6.1: String + Constant Pool] ← μ§€κΈˆ μ—¬κΈ° β˜…
        ↓
[Unit 6.2: StringBuilder vs StringBuffer] (λ‹€μŒ)
        ↓
[Unit 6.3: ArrayList vs LinkedList]
        ↓
[Unit 6.4: HashMap λ‚΄λΆ€ ꡬ쑰] β˜…β˜…β˜…
        ↓
[Unit 6.5: TreeMap, LinkedHashMap]

Phase 4-5 μ™€μ˜ μ—°κ²°

Phaseμ—°κ²°
4.1 (JVM λ©”λͺ¨λ¦¬)Pool 의 μœ„μΉ˜ (Heap, Old Gen)
4.2 (Pass by Value)String μ°Έμ‘° 전달
5.1 (GC)Pool 도 GC λŒ€μƒ (Java 7+)
5.2 (Heap ꡬ쑰)Pool 이 Old Gen 에 μœ„μΉ˜

μžλ£Œκ΅¬μ‘°μ™€μ˜ 관계

자료ꡬ쑰String ν™œμš©
HashMapν‚€λ‘œ 자주 μ‚¬μš© β€” λΆˆλ³€μ„± ν•„μˆ˜
HashSet쀑볡 제거 β€” equals + hashCode
TreeMapμ •λ ¬ ν‚€ β€” compareTo
Listμš”μ†Œλ‘œ 자주 μ‚¬μš©

λ‹€λ₯Έ 언어와 비ꡐ

μ–Έμ–΄String νŠΉμ„±
JavaλΆˆλ³€ + Pool
C#λΆˆλ³€ + Pool (Java 와 λΉ„μŠ·)
PythonλΆˆλ³€ + 일뢀 μž‘μ€ string interning
JavaScriptλΆˆλ³€ (primitive)
C++κ°€λ³€ (std::string)
RustλΆˆλ³€ (&str) + κ°€λ³€ (String)

β†’ μžλ°”μ˜ 선택은 ν‘œμ€€μ .


λ©΄μ ‘ 단골 질문 λ§€ν•‘

질문이 Unit μ—μ„œμ˜ λ‹΅
== vs equals?μ°Έμ‘° vs κ°’ 비ꡐ
new String("a") vs "a"?Heap vs Pool
String 이 μ™œ λΆˆλ³€?4κ°€μ§€ 이유 (μŠ€λ ˆλ“œ/λ³΄μ•ˆ/HashMap/캐싱)
intern() μ–Έμ œ μ“°λ‚˜?ν•œμ •λœ 데이터, μ‹ μ€‘ν•˜κ²Œ
Pool μœ„μΉ˜λŠ”?Java 7+ Heap

μΆ”κ°€ ν•™μŠ΅ ꢌμž₯

같은 Phase:

  • 6.2 (StringBuilder) β€” κ°€λ³€ String
  • 6.4 (HashMap) β€” String 의 hashCode ν™œμš©

미래 주차:

  • λ™μ‹œμ„± (4μ£Όμ°¨) β€” String 의 thread-safe νŠΉμ„± ν™œμš©
  • 데이터 처리 (Stream API) β€” String λ³€ν™˜

πŸ“ 9. 핡심 μš”μ•½ β€” 3쀄 정리

1️⃣ String 은 λΆˆλ³€ (Immutable) + Pool 둜 특수 λŒ€μš° λ°›λŠ” ν΄λž˜μŠ€λ‹€.

final 클래슀 + private final ν•„λ“œλ‘œ λ³€κ²½ λΆˆκ°€. λͺ¨λ“  λ³€ν™˜ λ©”μ„œλ“œλŠ” μƒˆ String λ°˜ν™˜. 이둜써 Thread-Safe, HashMap ν‚€ μ•ˆμ „, λ³΄μ•ˆ, 캐싱 κ°€λŠ₯ 4κ°€μ§€ 이점 확보. λΉˆλ²ˆν•˜κ²Œ μ‚¬μš©λ˜λŠ” String 의 νŠΉμ„±μ„ μΈμ‹ν•œ μžλ°”μ˜ μ˜λ¦¬ν•œ 섀계.

2️⃣ String Constant Pool 은 같은 λ¦¬ν„°λŸ΄μ„ 1개 객체둜 κ³΅μœ ν•œλ‹€.

λ¦¬ν„°λŸ΄ ("hello") 은 Pool 에 등둝 β†’ 같은 λ¦¬ν„°λŸ΄μ€ 같은 객체 곡유. new String("hello") λŠ” Heap 에 μƒˆ 객체. a == b λŠ” μ°Έμ‘° 비ꡐ β†’ Pool 영ν–₯ λ°›μŒ. a.equals(b) λŠ” κ°’ 비ꡐ β†’ 항상 μ•ˆμ „. Java 7+ μ—μ„œ Pool μœ„μΉ˜λŠ” Heap (Old Gen) β†’ GC κ°€λŠ₯.

3️⃣ 싀무 원칙 β€” equals μ‚¬μš©, 동적 데이터 intern X, Enum ν™œμš©.

== λŠ” 함정 β†’ 항상 equals() (λ˜λŠ” Objects.equals). μ‚¬μš©μž μž…λ ₯ λ“± 동적 데이터 intern() κΈˆμ§€ (OOM μœ„ν—˜). ν•œμ •λœ λ¬Έμžμ—΄ μƒμˆ˜λŠ” Enum 으둜 λŒ€μ²΄ (컴파일 νƒ€μž„ μ•ˆμ „ + == 비ꡐ κ°€λŠ₯). 큰 λ¬Έμžμ—΄ 연결은 StringBuilder (Unit 6.2). ILIC 의 λ“±κΈ‰/ν•­λ§Œ μ½”λ“œ 같은 λΉ„κ΅μ—μ„œ 이 원칙 μ§€ν‚€λ©΄ 운영 사고 λ°©μ§€.


πŸŽ“ ν•™μŠ΅ 자기 점검

κΈ°λ³Έ 이해

  • String 이 μ™œ λΆˆλ³€μΈμ§€ 4κ°€μ§€ 이유λ₯Ό μ„€λͺ…ν•  수 μžˆλ‹€
  • String Constant Pool 의 μœ„μΉ˜λ₯Ό μ•ˆλ‹€ (Heap, Old Gen)
  • λ¦¬ν„°λŸ΄κ³Ό new String() 의 차이λ₯Ό λ©”λͺ¨λ¦¬ 그림으둜 그릴 수 μžˆλ‹€
  • == 와 equals() 의 차이λ₯Ό μ •ν™•νžˆ μ•ˆλ‹€

μ‹€μ „ 적용

  • String 비ꡐ μ‹œ equals λ˜λŠ” Objects.equals μ‚¬μš©
  • null-safe ν•œ String 비ꡐλ₯Ό μž‘μ„±ν•  수 μžˆλ‹€
  • intern() 의 μœ„ν—˜μ„ μ•Œκ³  μ‹ μ€‘ν•˜κ²Œ μ‚¬μš©ν•œλ‹€
  • λ¬Έμžμ—΄ μƒμˆ˜λ₯Ό Enum 으둜 λŒ€μ²΄ν•  μ‹œμ μ„ μ•ˆλ‹€

λ©΄μ ‘ λŒ€λΉ„ (5λΆ„ λ‹΅λ³€)

  • == vs equals λ‹΅λ³€ κ°€λŠ₯
  • String s = "a" vs String s = new String("a") λ©”λͺ¨λ¦¬ κ·Έλ¦Ό
  • μ™œ String 이 λΆˆλ³€μΈμ§€ λ‹΅λ³€ κ°€λŠ₯
  • intern() 의 λ™μž‘κ³Ό μœ„ν—˜ λ‹΅λ³€ κ°€λŠ₯

자기 점검 질문 λ‹΅λ³€

Q1: String s = "hello" 와 String s = new String("hello") 의 차이λ₯Ό λ©”λͺ¨λ¦¬ 그림으둜 그렀보라.

ν•œ 쀄 λ‹΅: λ¦¬ν„°λŸ΄μ€ Pool 객체 μ°Έμ‘°, new λŠ” Heap 의 μƒˆ 객체 λ₯Ό 가리킴.

상세 μ„€λͺ…:

Case 1: λ¦¬ν„°λŸ΄

String a = "hello";
String b = "hello";

λ©”λͺ¨λ¦¬ μƒνƒœ:

[Stack]
  a (μ°Έμ‘°) ──┐
  b (μ°Έμ‘°) ───
            β”‚
[Heap]      β–Ό
β”œβ”€β”€ String Constant Pool
β”‚   └── "hello"  ← a, b λ‘˜ λ‹€ μ—¬κΈ°λ₯Ό 가리킴
β”‚
└── (Pool μ™ΈλΆ€ μ˜μ—­ λΉ„μ–΄μžˆμŒ)

핡심:

  • 컴파일 μ‹œ "hello" κ°€ .class 파일의 Constants Pool 에 기둝
  • 클래슀 λ‘œλ”© μ‹œ String Pool 에 등둝
  • 같은 λ¦¬ν„°λŸ΄μ€ 같은 객체 곡유
  • a == b β†’ true

Case 2: new String()

String c = new String("hello");
String d = new String("hello");

λ©”λͺ¨λ¦¬ μƒνƒœ:

[Stack]
  c (μ°Έμ‘°)
  d (μ°Έμ‘°)

[Heap]
β”œβ”€β”€ String Constant Pool
β”‚   └── "hello"  ← c, d κ°€ 가리킀지 μ•ŠμŒ!
β”‚
└── 일반 Heap
    β”œβ”€β”€ String 객체 #1 ("hello") ← c κ°€ 가리킴
    └── String 객체 #2 ("hello") ← d κ°€ 가리킴

핡심:

  • new λŠ” 무쑰건 Heap 에 μƒˆ 객체 생성
  • 인자둜 받은 "hello" λŠ” Pool 에 λ“±λ‘λ˜μ§€λ§Œ, κ·Έ κ°μ²΄λŠ” μ°Έμ‘°ν•˜μ§€ μ•ŠμŒ
  • λ§€ new λ§ˆλ‹€ μƒˆ 객체 β†’ λ©”λͺ¨λ¦¬ λ‚­λΉ„
  • c == d β†’ false

Case 3: ν˜Όν•©

String a = "hello";              // Pool
String c = new String("hello");  // Heap
String d = c.intern();           // Pool 의 "hello" λ°˜ν™˜

λ©”λͺ¨λ¦¬ μƒνƒœ:

[Stack]
  a (μ°Έμ‘°) ─────┐
  c (μ°Έμ‘°) ──────│──┐
  d (μ°Έμ‘°) ──────  β”‚
              β”‚  β”‚
[Heap]        β–Ό  β”‚
β”œβ”€β”€ String Pool   β”‚
β”‚   └── "hello" ← a, d κ°€ 가리킴
β”‚
└── 일반 Heap     β”‚
    └── "hello" ← c κ°€ 가리킴

검증:

  • a == c β†’ false (λ‹€λ₯Έ 객체)
  • a == d β†’ true (같은 Pool 객체)
  • c == d β†’ false (c λŠ” Heap, d λŠ” Pool)
  • a.equals(c) β†’ true (κ°’ 비ꡐ)

μ‹œκ°μ  λΉ„μœ 

Pool = λ„μ„œκ΄€:

  • λ¦¬ν„°λŸ΄ μ‚¬μš©μž = λ„μ„œκ΄€ μΉ΄λ“œ (같은 μ±… 곡유)
  • new String() μ‚¬μš©μž = 같은 μ±… μƒˆλ‘œ 인쇄 (별도 보관)
  • intern() = 자기 μ±… λ“€κ³  λ„μ„œκ΄€ κ°€μ„œ 같은 μ±… μΉ΄λ“œλ‘œ κ΅ν™˜

싀무 영ν–₯

λ©”λͺ¨λ¦¬:

  • λ¦¬ν„°λŸ΄ 1만 번 = 1개 객체
  • new String 1만 번 = 1만 개 객체

비ꡐ:

  • == λŠ” μœ„μΉ˜μ— 따라 κ²°κ³Ό 닀름
  • equals λŠ” 항상 μ•ˆμ „

ꢌμž₯:

  • new String("...") 은 거의 μ‚¬μš© μ•ˆ 함
  • λ¦¬ν„°λŸ΄ λ˜λŠ” String.valueOf() μ‚¬μš©

Q2: μ™œ String 은 λΆˆλ³€ (Immutable) 인가?

ν•œ 쀄 λ‹΅: Thread-Safe / HashMap ν‚€ μ•ˆμ •μ„± / λ³΄μ•ˆ / 캐싱 4κ°€μ§€ 이유.

상세 μ„€λͺ…:

이유 1: Thread-Safe

κ°€λ³€ String 의 문제:

// 가상 μ½”λ“œ (μ‹€μ œ μžλ°” X)
String userId = "alice";

// Thread 1
userId += "_admin";

// Thread 2
log.info("User: " + userId);  // "alice" λ˜λŠ” "alice_admin"?

λΆˆλ³€ String 의 μ•ˆμ „:

String userId = "alice";

// Thread 1
String adminId = userId + "_admin";  // μƒˆ String

// Thread 2
log.info("User: " + userId);  // 항상 "alice"

β†’ 동기화 없이 μ•ˆμ „.


이유 2: HashMap ν‚€μ˜ hashCode μ•ˆμ •

κ°€λ³€ String 의 문제:

Map<String, Customer> cache = new HashMap<>();
String key = "alice";
cache.put(key, alice);

// 가상: key κ°€ 가변이라 변경됨
// key.append("_modified");  // hashCode λ³€κ²½!

cache.get(key);  // null λ°˜ν™˜! (잘λͺ»λœ 버킷 검색)

λΆˆλ³€ String 의 μ•ˆμ „:

String key = "alice";  // hashCode μ˜μ›νžˆ κ°™μŒ
cache.put(key, alice);
cache.get("alice");  // 정상 λ™μž‘

β†’ HashMap, HashSet 의 μ •ν™•μ„± 보μž₯.


이유 3: λ³΄μ•ˆ β€” TOCTOU λ°©μ§€

κ°€λ³€ String 의 μœ„ν—˜:

public void connectDB(String url) {
    // 1. 검증
    if (isValidUrl(url)) {
        // 2. μ‚¬μš© (검증과 μ‚¬μš© 사이에 λ‹€λ₯Έ μŠ€λ ˆλ“œκ°€ url λ³€κ²½ κ°€λŠ₯)
        connect(url);  // μ•…μ˜μ  URL!
    }
}

λΆˆλ³€ String 의 μ•ˆμ „:

  • 검증 μ‹œμ μ˜ κ°’ = μ‚¬μš© μ‹œμ μ˜ κ°’
  • TOCTOU (Time-Of-Check Time-Of-Use) 곡격 λΆˆκ°€λŠ₯

이유 4: 캐싱 κ°€λŠ₯ (Pool)

λΆˆλ³€μ΄λΌμ„œ κ°€λŠ₯ν•œ 것:

  • 같은 κ°’μ˜ String 을 ν•˜λ‚˜μ˜ 객체둜 곡유 κ°€λŠ₯ (Pool)
  • 가변이라면 곡유 μ‹œ ν•œ κ³³μ—μ„œ μˆ˜μ • β†’ λ‹€λ₯Έ 곳도 영ν–₯
String a = "hello";  // Pool
String b = "hello";  // Pool 의 같은 객체

// a λ˜λŠ” b λ₯Ό 톡해 "hello" λ₯Ό μˆ˜μ •ν•œλ‹€λ©΄?
// β†’ λ‹€λ₯Έ λͺ¨λ“  곳에 영ν–₯ β†’ μž¬μ•™!

β†’ λΆˆλ³€μ΄κΈ° λ•Œλ¬Έμ— Pool κ°€λŠ₯.


이유 5 (λ³΄λ„ˆμŠ€): hashCode 캐싱

public final class String {
    private int hash;  // μΊμ‹œ
    
    @Override
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            // ν•œ 번만 계산
            h = computeHashCode();
            hash = h;  // 캐싱
        }
        return h;
    }
}
  • λΆˆλ³€μ΄λ―€λ‘œ hashCode ν•œ 번 계산 ν›„ 캐싱 κ°€λŠ₯
  • HashMap λ“±μ—μ„œ μž¬μ‚¬μš© μ‹œ μ„±λŠ₯ ↑

μ’…ν•© β€” μžλ°”μ˜ λ””μžμΈ κ²°μ •

μΈ‘λ©΄κ°€λ³€ StringλΆˆλ³€ String (Java)
Thread μ•ˆμ „μ„±λ™κΈ°ν™” ν•„μš”μžλ™ μ•ˆμ „
HashMap μ‚¬μš©μœ„ν—˜μ•ˆμ „
λ³΄μ•ˆTOCTOU μœ„ν—˜μ•ˆμ „
Pool κ°€λŠ₯Xβœ“
hashCode 캐싱Xβœ“
λ©”λͺ¨λ¦¬μ ˆμ•½ κ°€λŠ₯Pool 둜 μ ˆμ•½
μ„±λŠ₯μˆ˜μ • λΉ λ¦„λ³€ν™˜ μ‹œ μƒˆ 객체 (느림)

β†’ μžλ°”λŠ” μ•ˆμ „ + Pool 을 선택. 가변이 ν•„μš”ν•˜λ©΄ StringBuilder μ‚¬μš©.


λ‹€μŒ Unit으둜

  • StringBuilder vs StringBuffer ν•™μŠ΅ μ€€λΉ„ μ™„λ£Œ
  • κ°€λ³€ String 이 μ™œ ν•„μš”ν•œμ§€ κΆκΈˆν•˜λ‹€
  • λ™μ‹œμ„±κ³Ό String 의 관계λ₯Ό λ§Œλ‚  μ€€λΉ„ μ™„λ£Œ
profile
Software Developer

0개의 λŒ“κΈ€