들어가기 전에
James Gosling은 2001년 아티마 사의 회장이자 개발자인 Bill venners와 한 인터뷰에서 언제 불변성과 변성을 사용해야 하는지에 대한 질문에 이렇게 답변했다.
I would use an immutable whenever I can.
그리고는 불변성이 가지는 장점에 대해 계속해서 설명했다.
Java에서의 문자열은 불변성을 가진다. 이런 특성으로 기존의 문자열을 수정하면 새로운 객체가 생성된다.
String example = "hello";
System.out.println(example.hashCode());
example = "hello again";
System.out.println(example.hashCode());
출력 값 :
99162322
1773533842
객체를 구분하기 위한 hashCode() 메서드를 사용해서 리터럴로 할당한 문자열 example에 대한 정수 값을 출력했다. 변경된 example은 기존의 문자열이 아닌 새롭게 생성된 리터럴의 참조 값을 바라보고 있다. 그렇다면 다시 example에 "hello"를 선언하면 어떻게 될까? 당연하게도 다시 처음에 봤던 정수 값(여기서는 '99162322')이 출력된다.
String first = "hello";
String second = "hello";
System.out.println(first == second);
System.out.println(first.equals(second));
출력 값:
true
true
같은 문자열을 선언하고 있는 문자열 리터럴을 비교 연산자와 Object의 equals() 메서드를 통해 동일성(identity)과 동등성(equality)을 비교해 보면 어떨까? 당연하게도 참조 값이 같기 때문에 동일성 비교에서도 true, 리터럴 내용이 같기 때문에 동등성 비교에서도 true 값이 출력된다.
이렇게 문자열을 수정했을 때 기존의 정수 값을 출력할 수 있고 변수명이 다르게 선언된 문자열들에서 동일성과 동등성이 참으로 출력될 수 있는 이유는 자바에서 String Constant Pool(이하 SCP)를 사용하기 때문이다.
String Constant Pool
SCP는 문자열 리터럴이 저장되는 heap 메모리 안에 있는 특정 영역을 말한다. SCP는 기본적으로 비어 있는 공간으로 문자열 리터럴을 생성할 때 JVM이 먼저 해당 리터럴이 SCP에 존재하는지 확인한 후 존재한다면 기존 참조 값을 반환, 없다면 풀 내에 새로운 객체를 만들어 해당 객체의 참조 값을 반환한다. 이런 과정을 interning이라고 부르는데 Java에서는 자동으로 문자열 리터럴을 interning하고 필요하다면 String 클래스 내의 intern() 메서드를 활용해서 수동으로 해결한다. 하지만 특별히 문자열을 객체로 생성하고 SCP에 저장해야 할 일이 생기지 않는다면 String 클래스 내에 구현되어 있는 intern() 메서드를 활용할 일은 적을 것 같다.
All literal strings and string-valued constant expressions are interned.
모든 리터럴 문자열과 문자열로 선언된 상수 표현식은 SCP에 저장된다.
저장되는 영역은 어디인가
자바 7 이전에는 Permanent Generation(=Method Area)에 존재했고 한정된 메모리를 사용하기 때문에 OOM이 발생할 위험이 있었다.
하지만 Java 7 이후에 Heap 영역에 할당되게 된다. 기존과 다르게 GC의 수거 대상이 되기 때문에 더 이상 참조되지 않는 문자열은 삭제되어 메모리가 해제된다는 장점이 있지만 GC가 다른 메모리 영역에 비해 적은 빈도로 SCP를 관리하기 때문에 오랜 시간 메모리 영역에 보관되면 문제가 생기는 암호 같은 데이터들은 주의해서 사용해야 한다.
사용하는 자료구조는 무엇인가
SCP는 Hashmap으로 구현되어 있다.
이름에서도 알 수 있듯이 Map 기반의 자료구조로 기본적으로 해시 함수가 버킷에 요소들을 골고루 분산시킨다는 가정 하에 동작한다. String은 불변성을 가지기 때문에 생성될 때의 해시 코드 값이 캐시되고 다시 계산할 필요가 없어서 Map의 Key가 중복되지 않는다는 특징에 적합하고 그렇기 때문에 처리 속도가 빠르다.
사용하는 이유는 무엇인가
- 문자열의 불변성을 통해 보안을 향상시킬 수 있다.
- 멀티 스레드 환경에서 손상될 위험이 없다.
- SCP 내부에 이미 존재하는 문자열이라면 새로 생성하지 않기 때문에 시간을 절약할 수 있고 문자열을 재사용하면서 메모리를 절약할 수 있다.
예상 면접 질문
- 문자열을 리터럴로 할당하는 방식과 객체로 할당하는 방식의 차이점은 무엇인가요?
- 문자열을 객체로 사용할 때 메모리 낭비를 방지하기 위해 자바에서 제공하는 클래스는 무엇인가요?
- 문자열을 '==' 논리 연산자보다 Object의 equals() 메서드로 비교해야 하는 이유는 무엇인가요?
- 문자열을 비밀번호 저장에 사용하면 안되는 이유는 무엇인가요?
참고
- https://www.javaguides.net/2018/07/guide-to-java-string-constant-pool.html
- https://www.baeldung.com/java-string-constant-pool-heap-stack
- https://howtodoinjava.com/java/string/string-constant-pool/#5-advantages
- https://www.scaler.com/topics/java/string-pool-in-java/
- http://javatechniques.com/blog/string-equality-and-interning/
'Java' 카테고리의 다른 글
[Java] 컴포지션을 선택하는 이유 (0) | 2023.01.31 |
---|---|
[Java] 객체를 생성하는 다양한 방법 (0) | 2023.01.30 |
[Java] Method Area, PermGen 그리고 Metaspace (0) | 2022.10.06 |
JUnit5 (0) | 2022.09.11 |
[JAVA] 정규 표현식 - Matcher, Pattern, find(), group() (0) | 2022.07.13 |