String은 왜, 어떻게 불변 객체인가요? String Constant Pool

Yuri JI·2023년 4월 7일
3

TIL

목록 보기
7/10

💡 String은 불변 객체라 코딩테스트에서 자주 변경해야하는 문자열은 StringBuffer, StringBuilder를 사용하라는 팁을 종종 보았는데요. 왜 String을 불변객체로 만든 것인지, 그리고 어떻게 불변객체로 동작할 수 있는지 알아보기 위해 글을 작성했습니다.

String이 불변 객체인 이유를 적기 전에 불변 객체가 무엇인지, 그리고 String을 생성하는 두 가지 방법, String Constant Pool에 대해 알아보겠습니다 !

👀 불변 객체란?

  • 불변 객체란 말 그대로 변경이 불가능한 객체를 말한다.
  • 객체를 생성 후 외부에 의해 그 상태를 바꿀 수 없다.

❗ 여기서 바꿀 수 없다는 것은 heap 영역에서 그 객체가 가리키고 있는 데이터 자체의 변화가 불가능 함을 의미하며 stack에 있는 주소값을 다른 주소값을 가리키도록 변경하는 것은 문제가 없다.

👀 String을 생성하는 두 가지 방법

JVM의 Heap영역에는 String Constant Pool이 존재한다.

String을 Literal로 생성하면 이 영역에 저장되어 재사용할 수 있고,
new 연산자로 생성하면 일반적인 Heap 영역에 생성하여 재사용할 수 없다.

첫 번째 테스트에서 String을 Literal로 초기화 시킬 때는 같은 문자열 값에 대해 같은 객체를 참조한다.
하지만, 두 번째 테스트에서 String을 new 연산자를 통해 생성하면 같은 문자열 값이라도 서로 다른 객체를 참조하는 것을 볼 수 있다.

✅ String Constant Pool

String을 literal로 생성하면 어떻게 동일한 값에 대해서 같은 객체를 참조할 수 있을까?

String Interning

String을 literal로 초기화 한 경우 내부적으로 String.intern() 메서드가 호출된다.

JVM은 String Constant Pool에 각 문자열 값의 복사본을 하나 저장한다. 이후에 String 변수를 만들면 JVM은 String Constant Pool에서 동일한 값의 문자열을 검색하고, 있다면 그 주소 값을 반환하여 재사용한다.

이 과정을 Interning이라고 하며, 이를 통해 문자열에 할당되는 메모리를 최적화 할 수 있다.

만약 Pool에서 동일한 문자열 값을 찾을 수 없다면 Pool에 값이 추가되고 이에 대한 참조가 리턴된다.

intern() 메서드를 호출하여 String Constant Pool에 문자열을 수동으로 intern 할 수 있다.

  • literal로 생성한 String 객체와 new 연산자를 통해 생성한 String 객체는 서로 다른 객체를 참조한다.
  • 이후 new 연산자로 생성한 String의 intern()를 호출하여 새로운 String에 반환하였다.
  • intern() 메서드는 String Constant Pool에서 "interned String"을 검색한다. 첫 줄에서 이미 "interned String"이 String Pool에 저장했기때문에 이 참조값을 반환한다.
  • 그 결과 literal로 생성한 String과 intern() 메서드를 호출한 String은 같은 주소 값을 가진 것을 확인할 수 있다.

String Constant Pool

JVM은 Heap에 String Constant Pool을 가지고 있다.
이 Pool은 HashMap으로 구현되어

int num = 50;

String name = "Java";
Demo d = new Demo();

String Literal을 생성하면 JVM은 Pool에 객체를 생성하고, 해당 참조를 스택에 저장한다.

스택에는 int 리터럴 값과 String 및 Demo 객체의 참조를 저장하고 있다.

모든 객체의 값은 힙에 저장되고 모든 String Literal은 힙 내부의 String Pool로 이동한다.

String Constant Pool에 문자열 리터럴을 캐싱하고 이후에 재사용하게 되면 힙 메모리 영역을 효율적으로 사용할 수 있게 된다.

String이 불변 객체로 설계한 이유

String은 자기 자신을 변화시키지 않는다. 즉 불변 객체이다.

자바를 직접 만든 제임스 고슬링에 따르면
보안, 캐싱, 복사가 필요없는 빠른 재사용성, 동기화 때문이라고 한다.


https://www.artima.com/articles/james-gosling-on-java-may-2001#part13 인터뷰 내용 일부 발췌

1. 보안 (Security)

String은 민감한 정보를 저장하기위해 많이 사용된다.

  • 네트워크 연결 시 Host, Port 등의 정보
  • 파일 및 디렉터리 경로 등의 정보
  • Database와의 연결에 필요한 URL 등 민감한 정보 만약 String이 불변객체로 설계되지 않았다면,
    즉, 문자열이 변경 가능하다면 문자열의 무결성 검사 이후에 값이 변경될 수 있다. 이렇게 변경 가능한 문자열은 SQL 주입에 취약해지는 등 시간이 지남에 따라 보안에 위험이 생길 수 있다.

2. 해시코드 캐싱 (Hashcode Caching)

String은 HashMap, HashTable, HashSet과 같은 해시 구현에서도 사용된다. 이때 hashCode()로 정수 값을 받아서 키 값으로 이용하도록 컬렉션들이 설계되어있다.

String의 hashCode() 메서드를 보면,
최초 1번만 실계 계산을 수행하고, 이후에는 계산해서 나온 hash code를 재사용하도록 오버라이드 되어있다.

이렇게 Caching이 가능한 것은 String이 불변객체라 변경되지 않는 문자열을 보장하기 때문에 가능하다.

3. 복사가 필요없는 재사용성

String을 String Constant Pool에서 관리를 하여 Heap 영역의 메모리를 절약할 수 있다. 같은 값에 대해서는 String 객체를 다시 만들지 않고 이미 존재하는 객체를 참조할 수 있기 때문이다.
String은 가장 널리 사용되는 데이터 구조라 String의 성능 향상은 일반적으로 전체 응용 프로그램의 성능 향상에도 영향을 미친다.

4. 동기화 (Synchronization)

불변 객체는 값이 바뀌지 않기때문에 멀티스레드 환경에서 Thread-safe하다는 장점이 있다.
스레드가 값을 변경하면 동일한 String을 수정하는 대신 String Constant Pool에 새 문자열이 생성되기 때문이다.


결론
String을 new 연산자로 생성하면 Heap 영역에 매번 객체가 생성된다. 불변성의 장점을 위해 String Literal을 이용해서 String을 생성하자

참고한 블로그 목록
https://www.baeldung.com/java-string-constant-pool-heap-stack
https://www.baeldung.com/java-string-pool#string-interning
https://starkying.tistory.com/entry/what-is-java-string-pool
https://min103ju.github.io/effective%20java/immutable/
https://devlog-wjdrbs96.tistory.com/247
https://newwisdom.tistory.com/14
https://wildeveloperetrain.tistory.com/34
https://ellerymoon.tistory.com/104
https://www.artima.com/articles/james-gosling-on-java-may-2001#part13
https://velog.io/@jsj3282/String.intern

profile
안녕하세요 😄

2개의 댓글

comment-user-thumbnail
2023년 4월 12일

생각해보지 못했던 주제인데 포스팅 보고 공부하게 됐네요!!
글 잘봤습니다 :)

답글 달기
comment-user-thumbnail
2023년 4월 17일

유용한 글 잘 읽고 갑니다!! 👍👍👍

답글 달기