Java의 String은 불변인가 가변인가 (ft. Heap - SCP)

String은 불변이다

  • String 클래스는 내부적으로 Final 키워드가 선언된 byte[] 필드를 사용해서 문자열을 저장한다
  • String은 참조 타입, 즉 Reference Type이므로 concat(), replace(), toUpperCase() 사용시 새로운 String 객체를 참조한다 (불변유지)

왜 String은 불변이어야 했을까

String Constant Pool

SCP를 먼저 얘기하자면 String Constant Pool자체는 JVM의 Heap 영역 안의 특별 공간이다

→ 동일한 문자열 리터럴이 여러번 등장해도 메모리를 한 번만 사용하도록 관리하는 구조!

구조를 도식화 하자면

[In Code]
  ...
  String a = "hello"
  String b = "hello"
  String c = "world"
  ...

[JVM]
┌──────────────────────────────────┐
│            Heap Area             │
│   (힙 영역 – 객체들이 저장되는 곳)      │
├──────────────────────────────────┤
│                                  │
│     String Constant Pool (SCP)   │
│      ┌──────────────────────┐    │
│      │   "hello" ────┐      │    │
│      │               │      │    │
│      │   "world"     │      │    │
│      │   "java"      │      │    │
│      └──────────────────────┘    │
│          ↑          ↑            │
│          │          │            │
│   ┌──────┘          └──────┐     │
│   │                        │     │
│ String a ──────────────→ "hello" │
│ String b ──────────────→ "hello" │
│ String c ──────────────→ "world" │
└──────────────────────────────────┘

위처럼, String a, b, c가 각각 만들어진다고 치자.

a가 “hello”를 생성했고, 그 뒤의 b는 SCP에 hello가 있음을 확인하고 기존의 hello를 참조하게 된다

 

멀티스레드 환경에서의 Thread-safe

불변한 객체는 멀티 스레드 환경에서 thread-safe 하니까!

문자열의 변경이 일어난다면 SCP가 새로운 객체를 생성할 거고 이는 동기화에 신경 쓸 필요가 없어진다.

 

해시코드의 해싱

해시코드를 한 번만 계산하고 이를 Cashing해서 재사용하게 된다.

 

민감 정보의 보호

변경 불가능하니까! 수정방지되니까!

 

String에 대한 리터럴 생성과 생성자 생성의 차이

// 리터럴 생성
String str1 = "hello";

// 생성자 생성
String str2 = new String("hello");

// 참조 확인용
String str3 = "hello";

 

 

리터럴로 생성한 String 객체

  • Heap 영역의 SCP에 저장되어 동일한 문자열을 재사용할 수 있는 상태다
  • 즉, str1로 만든 객체는 str3이 참조하게 된다
System.out.println(System.idntityHashCode(str1));
// 출력: 245846322

System.out.println(System.idntityHashCode(str3));
// 출력: 245846322

생성자로 생성한 String 객체

  • Heap 영역에서 새로운 객체를 생성한다
System.out.println(System.idntityHashCode(str2));
// 출력: 485321448

번외, intern() 사용하기

intern()을 사용하면 heap 영역에 저장된 String 객체를 SCP에 저장할 수 있다

정확히는 SCP 존재시 그 주솟값을 반환하고, 없을 경우 String SCP에 추가하여 새로운 주솟값을 반환한다

String str1 = new String("hello"); // SCP 저장 아님
String str2 = str1.intern(); // SCP 저장시키고 새로운 주소값 반환
String str3 = "hello"; // str2가 만든 "hello"의 주소값 가짐

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