CS

CS 스터디 (Java) 7 - new String과 리터럴 문자열의 차이는?

gentle-tiger 2025. 5. 15. 14:51

문자열 리터럴

String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true

- Java에서 "hello"와 같은 리터럴 문자열은 String Constant Pool에 저장됩니다.

 

- 같은 문자열 리터럴이 여러 곳에서 사용되면, JVM은 동일한 인스턴스를 공유합니다.

 

 

new String()으로 생성한 문자열 

String s1 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s3)); // true

- new String("hello")는 항상 Heap에 새로운 객체를 생성하므로, 리터럴로 생성한 s1과는 참조가 다릅니다.  

 

요약 비교표 

항목 리터럴 문자열 ("hello") new String("hello")
저장 위치 String Constant Pool Heap
객체 생성 최초 한 번 (중복 사용 시 재사용) 항상 새로운 객체 생성
== 비교 동일한 참조 (true) 다른 참조 (false)
.equals() 비교 값만 같으면 true 값만 같으면 true
메모리 효율성 높음(공유됨) 낮음 (불필요한 객체 생성)
추천 사용 방식 대부분의 경우 리터럴 사용 특별한 상황에서만 필요 (예: 보안, 복사 등)

 

 

JVM 메모리 구조를 예전에 학습한 분들이라면 이런 의문이 생길 수 있습니다.
"String Constant Pool도 Heap 영역 아닌가요?"

 

맞습니다! Java 7 이후부터는 String Constant Pool은 Heap 메모리 내부에서 관리됩니다.  
하지만 여기서 중요한 점은, 같은 Heap 안에 있어도 `new String()`은
String Constant Pool에 있는 문자열 인스턴스를 재사용하지 않고, 새로운 객체를 명시적으로 생성한다는 것입니다.

 

String Constant Pool이 Heap 내부에 존재하더라도, new String("hello")는 String Constant Pool 객체를 참조하지 않고 항상 새로운 객체를 생성합니다. 이는 메모리 위치와 무관하게, 자바의 new 연산자가 명시적으로 새 인스턴스를 생성하도록 정의되어 있기 때문입니다. String Constant Pool에 저장된 문자열은 리터럴 또는 intern() 메서드 호출을 통해서만 공유됩니다.

 

public class InternExample {
    public static void main(String[] args) {
        String str1 = new String("hello");
        String str2 = str1.intern();
        String str3 = "hello";

        System.out.println(str1 == str2); // false
        System.out.println(str2 == str3); // true
    }
}

 

- new String("hello")는 Heap에 새 객체를 생성하고, intern()은 동일한 문자열이 String Constant Pool에 있다면 그 객체의 참조를 반환합니다.

- 따라서 str2와 str3는 모두 SCP 객체를 참조하므로 == 결과가 true입니다.

 

 

 

JDK 버전별 Runtime Constant Pool 구조 변화

- 아래는 JDK 버전별로 Runtime Constant Pool과 String Constant Pool이 어떻게 JVM 메모리 영역에서 배치되었는지를 정리한 도표입니다.
- JDK 1.6부터 1.8까지의 변화를 중심으로 구조적 차이를 확인하실 수 있습니다.

JDK 버전 PermGen / Metaspace 영역 Heap 영역 비고
JDK 1.6 이하 ✔ Runtime Constant Pool
(모든 상수 포함)
✖ 없음 문자열 상수도 PermGen에 포함
JDK 1.7 ✔ Runtime Constant Pool
(일반 상수만 유지)
✔ String Constant Pool
(문자열만 Heap 이동)
문자열 상수만 Heap으로 이동됨
JDK 1.8 이상 ✔ Metaspace
(클래스 메타 정보만 관리)
✔ Runtime Constant Pool
(문자열 포함 전체)
전체 Runtime Constant Pool이
Heap으로 이동

 

 

- JDK 1.6까지는 Constant Pool이 PermGen에 있었고, 1.7에서 문자열 상수만 Heap으로 이동했으며, 1.8부터는 모든 Runtime Constant Pool이 Heap에 위치합니다.

- 변경된 이유는 PermGen은 크기가 고정되어 OutOfMemoryError 위험이 있었고, GC 대상에서도 유연하지 않아 관리가 어려웠습니다.

- 이를 해결하기 위해 상수 풀을 Heap으로 옮겨, GC 관리 효율성과 메모리 확장성을 확보한 것입니다. 

 

 

intern() 메서드 

- intern() 은 Heap에 존재하는 문자열을 String Constant Pool에 수동으로 등록하거나, 동일한 문자열이 이미 존재할 경우 해당 상수 풀의 참조를 반환합니다. 

- 이는 메모리 절약을 위한 문자열 공유(string deduplication)의 일환이며, 리터럴은 컴파일 시 자동으로 상수 풀에 저장되지만, new String()으로 생성된 문자열은 Heap에 존재하므로, intern() 호출로 수동 등록이 필요합니다.

String a = "hello";               // String Literal → Constant Pool 자동 저장
String b = new String("hello");   // Heap 영역에 새로운 객체 생성
String c = b.intern();            // Constant Pool에 존재하는 "hello" 참조 반환

System.out.println(a == b); // false (Heap vs Pool)
System.out.println(a == c); // true  (둘 다 Pool 참조)

 

 

결론

"hello"와 같은 리터럴 문자열은 JVM의 힙 메모리 내에 있는 String Constant Pool에서 관리되며, 동일한 리터럴은 하나의 객체로 공유되어 참조 비교(==) 시에도 동일한 객체로 간주됩니다. 반면, new String("hello")는 동일한 값이 스트링 상수 풀에 있더라도 힙에 별도의 인스턴스를 생성하므로 참조값이 달라지고 == 비교 결과는 false가 됩니다. 이는 new 연산자가 항상 새로운 객체 생성을 보장하도록 자바 언어 사양에 정의되어 있기 때문입니다. 참조를 공유하려면 intern() 메서드를 사용하여 문자열을 스트링 상수 풀에 등록하거나 기존 객체를 참조할 수 있습니다.