자바에서 String은 불변 객체(immutable)라고 한다. 즉, 객체 생성 이후 내부의 상태가 변하지 않는 객체
라는 뜻이다.
그러면, String 객체는 최초 한 번 생성되면 절대로 그 값이 변하지 않는다는데, 이런 상황은 어떻게 설명할 수 있을까요?
String str = "최초 문자열";
str = "변경된 문자열";
str 이라는 String 객체가 생성된 이후 "최초 문자열"
을 "변경된 문자열"
로 바꾼다고 해도 실제 내부적으로는 최초 생성된 String 객체의 값이 변경되는 것이 아니라 새로운 String 객체가 생성되어 그 참조가 str 변수에 할당된 것
입니다.
즉, 이 상태에서는 최초에 생성된 "최초 문자열" 과 "변경된 문자열" 두 개의 인스턴스가 Heap 영역에 저장되어 있는 상태입니다.
(이후 참조되지 않는 "최초 문자열"은 GC(Gabage Collection)을 통해 제거
됩니다.)
불변성
의 개념, 문자열을 재할당할 때 해당 문자열을 참조하는 객체를 수정하는 것이 아니라 새 문자열을 만들고, 변경된 문자열을 할당한다는 것.
(즉, 문자열이 변경 불가능하다는 것은 객체 자체는 변경할 수 없지만 객체에 대한 참조는 변경할 수 있다.
)
그러면 자바에서는 왜 String을 불변 객체로 만들었을까?
String str1 = "madplay";
String str2 = "madplay";
System.out.println("str1 == str2 : " + (str1 == str1)); // true
위의 코드 실행 과정을 분석해보면 문자열 s1에 해당하는 것을 상수 풀
에서 찾는다. 없다면 상수 풀에 등록하고 해당하는 레퍼런스 값을 반환한다.
문자열 리터럴을 캐싱하고 재사용하면 문자열 풀의 다른 문자열 변수가 동일한 개체를 참조하기 때문에 힙 공간을 많이 절약할 수 있습니다. 문자열 상수 풀은 이러한 장점을 가지고 있는 것이다.
String이 불변해야 하는 더 자세한 이유는 이 블로그를 참고하기 바란다.
https://starkying.tistory.com/entry/why-java-string-is-immutable
그런데, 자바에서는 String을 선언하는 방법이 두 가지가 있다. new 연산자 방식, 리터럴 방식이다. 이 둘은 어떤 차이가 있을까?
String str1 = new String("madplay"); // new 연산자 방식
String str2 = "madplay"; // 리터럴 방식
System.out.println("str1 == str2 : " + (str1 == str1)); // false
이 둘의 차이는 실제 메모리에 할당되는 영역
에 차이가 있다!
new 연산자를 통해 문자열 객체를 생성하는 경우, 메모리의 Heap 영역
에 할당되고 두 번째 방법인 리터럴 방식으로 생성할 경우
에는 String Constant Pool이라는 상수풀 영역
에 할당된다.
참고로, 문자열이 담기는 상수풀의 위치는 자바 7
부터 Heap영역
으로 옮겨졌다.
그래서 Heap 영역에서 할당하는 메모리 번지가 다르기 때문에 주소값이 달라 false 가 나오는 것이다.
메모리 할당한 영역이 달라지니 주소값비교(==
)를 하면 false가 나오는 것이다!
public class MadPlay {
public static void main(String[] args) {
String str1 = "madplay";
String str3 = new String("madplay");
System.out.println(str1 == str3); // print 'false'
}
}
참고로, 값만 비교하는 eqauls()로는 true가 나올 수 있다. 헷갈리지 말자!
System.out.println(str1.equals(str3)); // print 'true'
상수풀(String Constant Pool)의 위치는 Java 7부터 Perm 영역
에서 Heap 영역
으로 옮겨졌다. Perm(정확히 풀어서 써보면 Permanent Generation)영역은 실행 시간(Runtime)에 가변적으로 변경할 수 없는 고정된 사이즈이기 때문에 intern 메서드의 호출은 저장할 공간이 부족하게 만들 수 있었다. 즉 OOM(Out Of Memory) 문제가 발생할 수 있는 위험이 있었던 것이다.
Heap 영역으로 변경된 이후에는 상수풀에 들어간 문자열도 Garbage Collection 대상
이 된다.
관련 링크) JDK-6962931 : move interned strings out of the perm gen(Oracle Java Bug Database)
이후에 Java 8 버전
에서는 Perm 영역은 완전히 사라지고 이를 MetaSpace라는 영역
이 대신하고 있다.