String 리터럴 vs new vs intern의 차이

한 줄 정의

String 리터럴은 String Pool에서 재사용되고, new String()은 항상 Heap에 새 객체를 만들며, intern()은 Pool에 등록하거나 기존 참조를 반환하는 메서드다.

실무에서 왜 중요한가

  • ==equals()의 차이를 이해하지 못하면 문자열 비교 버그가 생긴다.
  • 대량의 동일 문자열을 new String()으로 생성하면 메모리가 낭비된다.
  • intern()을 무분별하게 사용하면 String Pool이 비대해져 GC 부담이 커진다.
  • String Pool과 Heap의 관계를 모르면 메모리 사용 패턴을 예측할 수 없다.

동작 원리

String Pool (String Constant Pool)

String Pool은 JVM이 문자열 리터럴을 저장하는 특수 영역이다. Java 7+부터 Heap 영역 안에 존재한다 (이전에는 PermGen).

Heap
┌────────────────────────────────────────┐
│                                        │
│   String Pool                          │
│   ┌──────────────────────┐             │
│   │ "hello" (0x100)      │             │
│   │ "world" (0x200)      │             │
│   └──────────────────────┘             │
│                                        │
│   일반 Heap 객체                        │
│   ┌──────────────────────┐             │
│   │ new String("hello")  │ (0x300)     │
│   └──────────────────────┘             │
│                                        │
└────────────────────────────────────────┘

리터럴 할당 vs new 할당

String s1 = "hello";          // String Pool에서 참조
String s2 = "hello";          // 같은 Pool 객체를 참조
String s3 = new String("hello"); // Heap에 새 객체 생성
 
System.out.println(s1 == s2);      // true  (같은 Pool 참조)
System.out.println(s1 == s3);      // false (다른 객체)
System.out.println(s1.equals(s3)); // true  (값은 같다)
방식객체 위치동일 문자열 재사용객체 수
리터럴 "hello"String PoolO (같은 참조)1개
new String("hello")Heap (Pool 밖)X (매번 새 객체)Pool 1개 + Heap 1개

intern() 메서드

String s3 = new String("hello");
String s4 = s3.intern();
 
System.out.println(s4 == "hello"); // true (Pool의 참조를 반환)
System.out.println(s3 == s4);      // false (s3은 여전히 Heap 객체)

intern() 동작:

  1. String Pool에 동일한 값의 문자열이 있으면 → Pool의 참조를 반환
  2. String Pool에 동일한 값의 문자열이 없으면 → Pool에 등록하고 그 참조를 반환

내부 구현

String Pool은 내부적으로 HashTable 구조다. -XX:StringTableSize로 버킷 수를 조정할 수 있다.

# String Pool HashTable 크기 설정 (기본값: 65536, Java 11+)
java -XX:StringTableSize=120011 -jar app.jar

실무 판단 기준

상황선택이유
코드에서 문자열을 직접 쓸 때리터럴Pool에서 자동 재사용
외부 입력 문자열 비교equals()참조가 아닌 값 비교 필수
대량 중복 문자열 메모리 절감intern() 검토Pool 재사용으로 메모리 절약
고유한 문자열이 대량 생성intern() 사용 금지Pool만 비대해지고 GC 부담 증가

자주 나는 실수

  • ==로 문자열을 비교해서 외부 입력이나 new String()과 비교 시 false가 나온다.
  • intern()을 무분별하게 호출해서 String Pool이 커지고 GC pause가 증가한다.
  • Java 6 이전의 “PermGen에 String Pool이 있다”는 정보를 현재 버전에 적용한다.
  • new String("hello")가 객체를 2개 만들 수 있다는 사실을 모른다 (Pool 1개 + Heap 1개).

핵심 요약

String 리터럴은 String Pool에 저장되어 같은 값이면 같은 참조를 반환합니다. new String()은 항상 Heap에 새 객체를 만들므로 == 비교 시 false가 됩니다.

intern()은 문자열을 Pool에 등록하거나 기존 Pool 참조를 반환하는 메서드입니다. 대량 중복 문자열에서 메모리 절감 효과가 있지만, 고유 문자열이 많으면 오히려 부담이 됩니다.

Java 7+에서 String Pool은 Heap에 존재하므로 GC 대상이 됩니다. 문자열 비교는 항상 equals()를 사용하는 것이 안전합니다.

꼬리 질문

점검 퀴즈

아래 문항은 개념을 실제로 설명할 수 있는지 점검하기 위한 것이다. 선택지를 누르면 정답 여부와 이유가 표시된다.

객관식new String("hello")가 불필요한 이유로 가장 적절한 것은?

OXString 값 비교는 ==보다 equals를 사용하는 것이 원칙이다.

객관식intern()을 실무에서 신중하게 써야 하는 이유는?

관련 문서