원본 글: https://www.baeldung.com/java-string-pool#string-interning
JVM에서는 String객체를 저장하기 위한 특별한 저장공간이 있는데, 이를 String Pool이라고 합니다.
1. String Interning
JVM은 String literal에 대하여 String Pool공간에 하나의 복사본만 두고, 이를 재활횽해서 메모리 할당을 최적화할 수 있습니다. 이를 String Interning이라고 부릅니다.
String 변수에 값을 할당할 때, JVM은 String Pool에서 그 값과 동일한 값을 찾은 후 동일한 값이 존재하면 Java Compiler는 추가적인 메모리 할당 없이 찾은값에 대한 메모리 주소를 반환하게 됩니다. 만약 동일한 값이 존재하지 않으면 새로운 값을 String Pool에 할당시킨 후 해당 메모리 주소를 반환하게 됩니다.
이렇게 생성된 String은 Literal String이라 불립니다.
@Test
public void sameMemoryTest() {
//given
String s1 = "minseok";
String s2 = "minseok";
//then
Assertions.assertTrue(s1 == s2);
}
같은 메모리를 공유하기에 테스트가 통과할 수 있습니다. 위의 테스트코드가 잘 이해되지 않는다면 동등성, 동일성에 대한 개념이 필요하므로 해당 키워드 관련해서 구글에 검색하시면 좀 더 폭 넓은 자료를 보시고 이해하실 수 있으실겁니다.
2. 생성자를 이용한 String객체생성
`new` 연산자를 통해서 String객체를 생성할 수 있는데, 이렇게 생성된 경우 JVM의 힙 메모리 한 영역을 차지하게 됩니다.
이렇게 생성된 객체들은 위의 Literal String과 달리 매 번 다른 메모리 공간을 할당받고 이를 사용하게 됩니다.
그렇기에 객체의 값이 같더라도 할당된 메모리 주소가 다르므로 동VM에서는 String객체를 저장하기 위한 특별한 저장공간이 있는데, 이를 String Pool이라고 합니다.
이렇게 생성된 객체들은 위의 Literal String과 달리 매 번 다른 메모리 공간을 할당받고 이를 사용하게 됩니다.
그렇기에 객체의 값이 같더라도 할당된 메모리 주소가 다르므로 동일하다고 볼 수 없습니다.
@Test
public void new로_생성한객체는_같은메모리를_공유하지않는다() {
//given
String s1 = new String("minseok");
String s2 = new String("minseok");
//then
Assertions.assertFalse(s1 == s2);
}
당연하게도 LiteralString과 new로 생성한 String 객체의 값이 같더라도 동일할 수 없습니다.
@Test
public void new로_생성한객체와_리터럴String은_동일하지않다() {
//given
String s1 = "minseok";
String s2 = new String("minseok");
//then
Assertions.assertFalse(s1 == s2);
}
3. Intern()메서드
new로 생성한 String객체를 Literal String으로 바꾸지는 못할까요? Intern()메서드를 통하여 이를 가능하게끔 할 수 있습니다.
@Test
public void InterningTest() {
//given
String s1 = "minseok";
String s2 = new String("minseok");
//when
Assertions.assertFalse(s1 == s2);
s2 = s2.intern();
//then
Assertions.assertTrue(s1 == s2);
}
intern()메서드를 호출한 뒤 두 객체의 동일성이 보장되는 것을 확인할 수 있습니다.
해당 메서드를 호출하면 값을 String pool에 저장하고 JVM이 이에 대한 참조값을 필요할때마다 반환하여 사용자가 사용할 수 있게끔합니다.
4. Garbage Collection
Java7 이전에는 String Pool이 PermGen이라는 영역에 있었습니다.(이에 대한 추가 설명이 필요하시면 Garbage Collection을 검색하셔서 확인해보시면 좋을 것 같습니다.) 해당 영역의 크기는 고정되어있기에 많은 String 값을 저장하면 Out of Memory 문제가 생겼습니다.
Java7을 오면서 String Pool은 Heap Space영역으로 옮겨졌고, 이를 통해서 Out of Memory가 발생할때쯤이면 Garbage Collection이 이를 관리하면서 안 쓰이는 String에 대해서는 pool에서 제거하는 등의 활동으로 메모리를 절약할 수 있게되었습니다.
5. Perfomance and Optimizations
Java6에서는 JVM옵션을 건드려서 PermGen영역을 늘리는것 외에는 최적화할 방법이 없었습니다.
-XX:MaxPermSize=1G
Java7에서는 좀 더 상세한 설정이 가능해졌습니다. 일단 특별한 옵션을 줘서 JVM에 대한 모니터링이 가능해졌습니다.
-XX:+PrintFlagsFinal
-XX:+PrintStringTableStatistics
위와같은 옵션을 통해 String Pool에 대한 크기를 얻을 수있었고, 이를 통해 Pool을 늘리거나 줄이는것이 가능해졌습니다.
-XX:StringTableSize=4901
초기에는 당연하게도 Pool Size의 기본값이 1009으로 작았습니다. Java11에 와서는 기본값이 65536로 변경되었습니다.
pool size를 늘리면 메모리 소비가 늘어나지만 String값을 StringTable에 적재하는 시간이 줄어든다는 장점이 있습니다.
6. Java9에서
Java8까지는 문자열이 UTF-16의 char[]자료형으로 저장되었는데, Java9에서는 문자열이 UTF-16이고 어떤 문자열이냐에따라서 char[], 또는 byte[]로 저장하게 됩니다. 예를들어서 영어위주의 문자열은 byte[]로 저장하게 되는데 이를 Compact Strings라고 부르고, JVM이 이를 수행합니다.
위의 효과를 통해서 메모리 공간을 절약할 수 있고 Gabage Collector의 오버헤드를 줄일 수 있습니다.
7. 결론
JVM과 Java Compiler를 통해서 String 객체와 Literal String이 pool에 어떻게 저장되고 사용되는지 확인할 수 있었습니다.
'Baeldung번역&공부 > Java-string' 카테고리의 다른 글
String 비교법(Comparing Strings in Java) (0) | 2025.01.28 |
---|---|
문자열 순회 방법들(How to Iterate Over the String Characters in Java) (0) | 2025.01.27 |
문자열을 잇는방법(Concatenating Strings in Java) (0) | 2025.01.22 |
왜 String은 불병성을 가지나(Why String Is Immutable in Java?) (0) | 2025.01.21 |
스트링 객체 초기화방법(String Initialization in Java) (0) | 2025.01.20 |