반갑습니다!

[Java] String 메모리 관리 본문

개발

[Java] String 메모리 관리

김덜덜이 2020. 5. 8. 22:09

String

Java에서 String은 2가지의 생성 방법이 있는 특이한 자료형이다.

  1. new 키워드를 사용하는 방법
  2. 리터럴로 선언하는 방법

선언해서 사용하는 사람 입장에서는 똑같은 것처럼 여겨질 수 있지만, 사실 두 가지 방법에는 큰 차이가 있다. 예시 코드를 보면서 확인해보자.

class Main {
    public static void main(String[] args) {
        String str1 = "Hello Java!";
        String str2 = new String("Hello Java!");
        if(str1.equals(str2)){
            System.out.println(str1 + "과 " + str2 +"는 내용이 같습니다.");
        } else{
            System.out.println(str1 + "과 " + str2 +"는 내용이 다릅니다.");
        }

        if(str1 == str2){
            System.out.println(str1 + "과 " + str2 +"는 주소가 같습니다.");
        } else{
            System.out.println(str1 + "과 " + str2 +"는 주소가 다릅니다.");
        }
    }
}

/* 실행 결과
Hello Java!과 Hello Java!는 내용이 같습니다.
Hello Java!과 Hello Java!는 주소가 다릅니다.
 */

같은 내용의 문자열을 2개 생성하여 비교하는 간단한 예제이다. 당연하게 값 비교를 하는 .equals() 함수의 결과가 잘 나온 것을 볼 수 있다. 그런데 주소 값을 비교하는 == 연산 결과가 이상하다. 똑같은 문자열인데 주소 값이 다르다는 결과가 나온 것이다.

이는 String을 생성하는 두 방식이 다르게 작동하기 때문이다. new 키워드를 사용하게 되면 문자열을 동적할당하기 때문에 Heap 영역에 문자열이 생성된다. 반면 리터럴로 선언한 경우에는 Heap 영역에 있는 Constant String Pool이라는 영역에 문자열이 할당된다.

그렇다면 Constant String Pool 영역은 왜 있는 것일까?

[Kotlin] 자료형과 변수 포스팅의 문자열 부분에서 잠깐 언급한 적이 있는데, 동일한 내용을 가지는 String이 Constant String Pool 안에 있다면 주소 값을 넘겨주어 여러 메모리에 같은 내용의 문제열을 저장하는 것을 방지함으로써 메모리를 절약하기 위해서이다.

String을 리터럴로 선언했을 때의 내부 동작 과정을 살펴보면, intern() 이라는 함수를 통해 Constant String Pool 안에 동일한 내용의 문자열이 있는지 확인해서 있다면 해당 문자열의 주소 값을 반환하고, 없다면 Constant String Pool에 리터럴로 선언한 문자열을 넣고 새로 할당한 주소 값을 반환한다. 이번엔 새로운 예제를 살펴보자.

class Main {
    public static void main(String[] args) {
        String str1 = "Hello Java!";
        String str2 = str1.intern();

        if(str1.equals(str2)){
            System.out.println(str1 + "과 " + str2 +"는 내용이 같습니다.");
        } else{
            System.out.println(str1 + "과 " + str2 +"는 내용이 다릅니다.");
        }

        if(str1 == str2){
            System.out.println(str1 + "과 " + str2 +"는 주소가 같습니다.");
        } else{
            System.out.println(str1 + "과 " + str2 +"는 주소가 다릅니다.");
        }
    }
}

/* 실행 결과
Hello Java!과 Hello Java!는 내용이 같습니다.
Hello Java!과 Hello Java!는 주소가 같습니다.
 */

이번엔 str2가 intern() 함수를 통해 이미 Constant String Pool에 있는 "Hello Java!"의 주소 값을 가져오기 때문에 내용과 주소가 모두 같다는 것을 알 수 있다.

class Main {
    public static void main(String[] args) {
        String str1 = new String("Hello Java!");
        String str2 = str1.intern();

        if(str1.equals(str2)){
            System.out.println(str1 + "과 " + str2 +"는 내용이 같습니다.");
        } else{
            System.out.println(str1 + "과 " + str2 +"는 내용이 다릅니다.");
        }

        if(str1 == str2){
            System.out.println(str1 + "과 " + str2 +"는 주소가 같습니다.");
        } else{
            System.out.println(str1 + "과 " + str2 +"는 주소가 다릅니다.");
        }
    }
}

/* 실행 결과
Hello Java!과 Hello Java!는 내용이 같습니다.
Hello Java!과 Hello Java!는 주소가 다릅니다.
 */

이번엔 str1을 new 키워드를 통해 선언했기 때문에 Heap영역에 str1이 할당되었다. 이 때 intern()함수를 사용하면 str1은 Heap에는 있지만 Constant String Pool에는 없기 때문에 str1과 str2의 주소가 다르다는 것을 알 수 있다.

Constant String Pool

이번엔 Constant String Pool에 대해서 조금 알아보자. Java6까지 Constant String Pool은 Perm 영역이라고 하는 Runtime 시에 고정된 크기를 갖는 영역에 위치했다고 한다. 하지만 Java7로 넘어오면서 Heap 영역으로 변경되었는데, 이는 OOM (Out Of Memory Exception) 문제 때문이다. Perm 영역은 Compile time에는 크기를 조절할 수 있지만, Runtime에는 동적으로 크기를 조절할 수 없기 때문에 intern() 함수를 사용하면 OOM이 발생할 수 있었다. 따라서 Java6에서는 intern() 함수를 사용하지 않는 것이 권장된다고 한다.

그렇다면 왜 Constant String Pool은 Heap영역으로 변경되었을까?

이는 Heap 영역으로 옮김으로써 Constant String Pool 내부의 문자열이 GC (Garbage Collector)의 대상이 되기 때문에 더욱 효율적인 메모리 관리를 하기 위함이라고 볼 수 있다.

'개발' 카테고리의 다른 글

[JUnit 5] JUnit 5 - 2  (0) 2020.12.28
[JUnit 5] JUnit 5 - 1  (0) 2020.12.27
[Java] Java Reference  (0) 2020.10.15
[Java] Garbage Collection  (0) 2020.10.15
[UML] Class Diagram  (0) 2020.06.22