자바의 스트링 풀
String 객체의 값은 불변이다.
자바에 익숙하지 않은 사람은 String
객체의 값이 불변이라는게 무슨 뜻인지 잘 모를 것이다. 불변이란 것은 한번 저장된 값이 절대 변하지 않는다는 뜻이다. 그런데, String
타입의 변수를 선언하고, 내부의 값을 바꾸면 값이 잘 바뀌는 것처럼 보이는 것은 왜일까?
사실 우리가 문자열 변수의 내용을 변경할 때는 실제로 값이 변경되는 것이 아니라, 스트링풀이라는 저장소에 미리 사용될 문자들이 몽땅 저장되어 있는데, 문자열 변수의 내용 자체가 바뀌는 것이 아니라 문자열 변수가 가리키는 주소만 바뀌는 것이다.
@Test
public void immutableString() {
String a = "abc";
System.out.println("a의 시스템상 주소: " + System.identityHashCode(a));
a = "bbc";
System.out.println("a의 시스템상 주소: " + System.identityHashCode(a));
String b = "bbc";
System.out.println("b의 시스템상 주소: " + System.identityHashCode(b));
b = "bbc";
System.out.println("b의 시스템상 주소: " + System.identityHashCode(b));
}
/*
출력결과
a의 시스템상 주소: 1939990953
a의 시스템상 주소: 1092572064
b의 시스템상 주소: 1092572064
b의 시스템상 주소: 1092572064
*/
위는 간단한 테스트코드와 그 출력결과이다. 위 결과로 다음과 같은 내용을 알 수 있다.
System.identityHashCode()
는 객체가 메모리에서 가지는 고유한 주소값을 반환한다.
String
객체에 들어있는 문자열 값이 바뀌면,String
값이 가리키는 주소도 바뀐다.String
객체가 불변임을 알 수 있다. 불변이 아닌 객체의 내용을 바꿨다면, 주소 값은 그대로고 주소에서 가리키는 값의 내용이 바뀌었을 것이다.
String
객체에 들어있는 문자열을 같은 값으로 재할당해도String
값이 가리키는 주소는 그대로다. 심지어 나중에는a
변수와b
변수가 가리키는 주소가 같아진다.- 같은 값의 문자열은 계속 같은 주소를 가리키게 된다. 문자열을 어딘가에서 저장해놓고 관리한다고 의심할 수 있다.
String은 사실 스트링 풀에 저장된다
스트링 풀이란 무엇인가?
내가 한번 썼던 문자열들을 저장해놓는 창고라고 생각하면 된다. 예를들면 위에서는 abc
라는 문자열을 한번 사용했다. 이제 JVM은 해당 문자열을 Heap 영역에 저장해놓고, 프로그램에서 사용하는 문자열을 보고 해당 문자열이 이미 스트링 풀에 저장되어 있는지 확인한다.
스트링 풀이라는 저장소를 사용함으로써, 매번 같은 문자를 쓸 때 새로운 객체를 만들지 않아서 객체 생성 비용의 낭비를 줄인다.
자바 6 이하 버전에서는
PermGen
영역에 스트링 풀을 구성해놓았는데, 너무 긴 문자열을 스트링 풀에 저장할 때Out of memory exception
이 자주 터지는 관계로 자바 7 버전부터는heap
영역에 저장하도록 바뀌었다고 한다.
스트링 풀에 문자열을 저장해놓고 쓰는 방식은 사실 다른 언어에서도 많이 차용하는 방식이다. 참고링크
String의 intern() 메서드
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section {@jls 3.10.5} of the
* <cite>The Java Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
public native String intern();
String
은 위처럼 내부에 intern()
이란 메서드가 있는데, 이 메서드를 간단히 표현한 말이 다음과 같다. returns a canonical representation
기본적인 표현을 반환한다는 것인데, 이는 다양한 방식으로 표현될 수 있는 기본 표현을 말한다. 잘 보면, 반환 타입도 native
인 것을 볼 수 있다.
canonical representation
에 대한 이해 참고 링크
이 메서드는 문자열이 문자열 풀에 있는지 먼저 확인 후에 있다면, 문자열 풀 속 해당 literal
을 가리키는 참조를 가져올 것이고 만일 없다면 문자열 풀 속에 해당 literal
을 넣고 그 주소를 가져올 것이다.
intern() 메서드 간단 테스트로 원리 깨닫기
@Test
public void stringInternTest() {
String a = "Hello";
String b = new String("Hello");
System.out.println("a == b ? : " + (a == b)); // false
System.out.println("a == b.intern() ? : " + (a == b.intern())); // true
}
위의 프린트 문 중 첫번째는 false
를 반환하고 두번째는 true
를 반환한다. 이로써 .intern()
메서드를 사용함으로써 확실히 메모리 낭비를 줄일 수 있다는 것을 깨달을 수 있다.
JVM에서 잘 알아서 해주기 때문에 아마 현실 세상에서는 딱히
intern()
메서드를 이용할 일은 없을 것 같다.
참고 링크
'Java > 자바 API' 카테고리의 다른 글
자바 Objects.requireNonNull() 을 사용하는 이유 (1) | 2023.11.06 |
---|---|
자바 CountDownLatch란? (0) | 2022.01.03 |
자바 WeakMap 쉽게 알아보기 (0) | 2021.12.23 |