8. 불변 객체와 String 클래스

1. 불변(Immutable, 읽기 전용) 객체의 논리적 개념

사이드 이펙트와 참조자

  • 특정 인스턴스에 대한 참조는 여러 개가 될 수 있으며 이를 막을 수도 없다.

    • 문법적으로 막아놓지 않는다.. (컴파일 오류 발생 X)

  • 모든 참조는 대상 인스턴스에 대한 읽기, 쓰기, 접근이 모두 허용된다.

  • 인스턴스에 대한 무결성을 보장해야 하는 구조에서는 (논리적) 문제가 발생할 수 있다.

    • 예를 들어, 여러 스레드가 각각의 값을 가져야하지만, 동일한 값을 사용하는 (논리적) 문제가 발생한다.

    • 의도하지 않은 값을 사용하게 된다.

    • 때문에, 동시성이 발생하는 환경에서는 동기화가 중요한 케이스이다.

2. 불변 객체의 특성 정의

  • 모든 필드를 final 선언함으로써 상수화한 클래스 (무결성)

  • 필드 값을 변경해야 한다면 새로운 사본 클래스를 생성하면서 원하는 수정된 초기값을 기술해 반환하는 구조로 개발 (원자성 보장)

  • 대표적 불변객체

    • String, Wrapper class

class MyInt {
    private final int data;
    public MyInt(int param) {
        data = param;
    }
    
    public int getData() {
        return data; 
    }
    
    public MyInt widthInt(int param) {
        return new MyInt(this.data + param); 
    }
}

6. JVM 이 문자열 상수를 관리하는 구조

📌 1. C/C++과의 비교: 정적 메모리에 문자열 상수 저장

C/C++에서 .exe 파일을 만들면, 문자열 리터럴은 실행 파일의 정적 메모리 영역에 포함됩니다. 이때 동일한 문자열은 하나의 주소로 공유되며, 실행 시 별도로 할당되지 않습니다.

  • 예: "hello"가 여러 번 등장해도 하나의 메모리 주소에 저장됨

  • "hello"를 참조하는 포인터 주소는 모두 동일


📌 2. Java에서 문자열 리터럴은 ‘시점’에 따라 관리 위치가 다르다

Java에서는 문자열 리터럴도 상수로 관리되지만, JVM이 실행되며 거치는 시점마다 저장 위치가 다릅니다.

🔸 컴파일 시점 (.class 파일 생성)

  • Java 소스 코드 내 "hello" 같은 리터럴은 .class 파일 내 Constant PoolCONSTANT_String_info 형태로 메타데이터가 저장됩니다.

  • 이 시점에서는 아직 java.lang.String 객체는 생성되지 않습니다.

🔸 클래스 로딩 시점 (Runtime Constant Pool)

  • 클래스 로딩 시, .class 파일의 Constant Pool이 JVM의 Runtime Constant Pool으로 적재됩니다.

  • 이 영역은 JVM의 Method Area(Java 8부터는 Metaspace) 내부에 존재하며, 문자열 리터럴도 심볼릭 참조 형태로 유지됩니다.

🔸 사용 시점 (실제 실행 중 리터럴 사용)

  • 최초로 문자열 리터럴을 참조할 때, JVM은 해당 문자열을 Heap에 String 객체로 생성하고 String Constant Pool (intern pool)에 등록합니다.

  • 이후 동일한 리터럴이 다시 등장하면, JVM은 intern pool에 등록된 String 객체를 재사용합니다.


📌 3. String Constant Pool (Intern Pool)

  • intern pool은 Java의 Heap 영역에 존재하며, 동일한 문자열 리터럴이 여러 번 생성되는 것을 방지합니다.

  • "hello"처럼 문자열 리터럴은 자동으로 intern 처리되지만, new String("hello")와 같이 명시적으로 객체를 생성한 경우에는 intern pool에 등록되지 않습니다.

  • Java 6까지는 intern pool이 PermGen에 존재했지만, Java 7부터는 Heap으로 이동하여 메모리 유연성이 향상되었습니다.


📌 4. 구조 요약

시점
저장 위치
내용

컴파일 시점

.class 파일의 Constant Pool

문자열 리터럴의 메타 정보 (CONSTANT_String_info)

클래스 로딩 시점

Method Area의 Runtime Constant Pool

심볼릭 참조 정보

사용 시점

Heap 내부의 String Constant Pool

실제 String 객체 (intern 처리)


📌 5. JVM이 문자열 리터럴을 효율적으로 처리하는 이유

  • 중복 객체 생성을 방지해 메모리 절약

  • 동일한 문자열 리터럴 비교 시 ==으로 비교 가능 → 성능 최적화

  • 예측 가능한 메모리 모델로 GC 최적화도 유리


🧠 함께 알아두면 좋은 내용

  • intern() 메서드는 수동으로 문자열을 intern pool에 등록하거나 검색할 수 있게 해줍니다.

  • JVM의 GC 대상이 되는 String pool은 Java 7 이후 Heap 영역에 존재하기 때문에 사용하지 않는 intern 문자열은 GC로 회수될 수 있습니다.

  • String Deduplication 기능은 G1 GC 환경에서 -XX:+UseStringDeduplication을 통해 내용이 같은 String 객체의 내부 char 배열을 공유하여 추가적인 메모리 절약을 시도합니다.

Last updated