-
String Pool - Java의 String은 어디에 저장될까Programming/Java 2021. 8. 26. 01:24
Java String API(java.lang.String)는 Java에서 Primitive Data Type과 Reference Data Type의 중간 성격을 띄는, 특별한 클래스이다
https://2jinishappy.tistory.com/259?category=936901
이 글에서 언급한 것 처럼, String의 value는 immutable 특성을 가지기 때문이다.
String이 immutable하다는 것은, 서로 다른 객체에 할당되는 같은 String 객체에 대해 별도의 공간을 지정하지 않아도 된다는 것 이다.
String Interning
따라서 JVM은 Java String Pool이라는 특별한 메모리 공간을 두어 String 문자열을 관리한다
String Pool이라는 메모리 공간에 중복되지 않는 String 문자열을 저장하고, 해당 문자열에 접근할 때 마다 동일한 메모리 주소를 반환하면 할당해야 하는 메모리 공간을 절약할 수 있다.
이러한 프로세스를 interning이라고 한다.
실제로 우리가 String 변수를 생성하고 값을 할당하면 JVM은 String pool에서 동일한 value를 가진 String이 있는지 탐색한다
👉 이미 존재한다면, 새로운 메모리 공간을 할당하는 것이 아닌 해당 String의 reference를 반환한다
👉 존재하지 않는다면, pool에 새로운 String이 할당되고 해당 reference를 반환한다
String constantString1 = "2jin"; String constantString2 = "2jin"; assertThat(constantString1) .isSameAs(constantString2);
생성자를 통해 할당된 String 객체
""(큰따옴표)를 이용해 할당했을 때와는 다르게, new 연산자를 이용한 String 객체 생성은 String Pool에 저장되지 않고 Java 컴파일러가 새로운 객체를 생성하여 Heap에 할당한다.
똑같은 String value를 가진 객체를 여러 개 생성해도, String pool로 관리할 때와 달리 별도의 메모리 공간을 계속해서 할당하는 것이다.
String constantString = "2jin"; String newString = new String("2jin"); assertThat(constantString).isNotSameAs(newString);
String Literal 🆚 String Object
new 키워드를 사용한 String Object가 생성될 때, Heap 메모리에는 항상 새로운 Object가 할당된다.
반면 큰따옴표를 이용한 String Literal을 생성할 때 에는, String Pool에 존재하는 기존의 객체를 반환하고 없을 시 새로운 객체를 생성하여 재사용을 위해 String Pool에 저장한다.
String Pool과 Heap 둘다에 저장되는 value는 둘다 String Object로 동일하지만, 가장 큰 차이점은 new 키워드를 사용할 시 항상 새로운 객체가 생성된다는 것이다.
String Literal과 String Object의 차이점은 아래의 compare 예제를 통해 확인할 수 있다
String first = "2jin"; String second = "2jin"; System.out.println(first == second); // True String third = new String("2jin"); String fourth = new String("2jin"); System.out.println(third == fourth); // False String fifth = "2jin"; String sixth = new String("2jin"); System.out.println(fifth == sixth); // False
first와 second를 비교했을 때, 둘 다 String Literal을 사용했으므로 String Pool의 같은 객체를 참조한다.
따라서 피연산자의 identity를 비교하는 == 연산자를 사용했을 때 True를 반환한다.
third와 fourth를 비교했을 때, 둘 다 new 키워드를 사용하여 String Object를 새롭게 생성했으므로 Heap에는 두 개의 String Object가 생성되고 당연히 다른 메모리를 참조하고 있다.
firth와 sixth를 비교했을 때 에도 firth는 String Pool, sixth는 Heap에 있는 객체를 참조하므로 동일하지 않다.
Manual Interning
intern() 메서드를 활용하면 Java String Pool에 존재하는 String을 가져올 수 있다.
아래의 예제에서 newString.intern() 메서드를 실행했을 때, newString에 저장되어 있는 String value를 String Pool에 저장한 뒤 반환한다(혹은 이미 있다면 해당 String의 참조를 반환)
String constantString = "interned 2jin"; String newString = new String("interned 2jin"); assertThat(constantString).isNotSameAs(newString); String internedString = newString.intern(); assertThat(constantString) .isSameAs(internedString);
따라서 constantString의 String Literal 선언으로 인해 "interned 2jin"이라는 String이 이미 저장되어 있었고, newString.intern() 메서드를 통해 String Pool에 저장된 해당 메모리 주소를 반환한다.
따라서 constantString과 internedString은 동일하다.
Garbage Collection
String Pool의 활용은 GC 관점에서도 매우 유용하다.
Java7 이전에 String Pool은 고정된 크기를 가진 permanent generation에 저장되어 실행시간 내에 확장될 수 없다는 특징이 있었고, GC의 대상이 되기에 적합하지 않았다. 심지어 String Pool에 너무 많은 String이 저장될 경우 OutOfMemory 에러가 발생할 수 있었다.
Java7 이후에는 String Pool이 GC의 타겟이 되는 Heap 영역으로 이동하게 되었고, OutOfMemory 에러 발생 가능성의 감소와 함께 참조되지 않는 String을 Pool에서 지워서 메모리를 효과적으로 관리할 수 있게 되었다.
Java 8, 9 이후의 변화
Java8에서는 String이 UTF-16형식으로 인코딩된 char형 배열(char[])로 구현되어 모든 글자가 2bytes를 차지했다.
Java9에서는 Compact String을 지원하여, 저장된 내용에 따라 char형 배열(char[]) 또는 byte형 배열(byte[]) 중 다른 형식으로 인코딩 방식을 선택한다. 이로써 heap memory 양이 줄어들고, GC 오버헤드가 감소했다.
✅ 이 글은 아래의 링크를 참고하여 작성되었음 👇
https://www.baeldung.com/java-string-pool
'Programming > Java' 카테고리의 다른 글
POJO(Plain Old Java Object)와 JavaBean (423) 2021.10.08 Java에서 Thread를 사용하는 방법 (445) 2021.10.04 Checked and Unchecked Exception (422) 2021.08.25 [Java] Hotspot JVM Garbage Collection 과정 (406) 2021.08.23 [Java] Primitive Data Types (336) 2021.08.22