[Java] String과 String Constant Pool 이해하기

gomzu·2023년 3월 2일
1

포스트와 다른 의견은 언제나 환영입니다 👍

Intro

오늘은 Java의 기본 문자열 클래스인 String 클래스에 대해 한번 살펴보겠습니다.
기술 면접을 준비하는 과정에서도 종종 나오는 중요한 부분입니다.



String 생성하기

String 객체를 생성하는 방법은 두가지가 있습니다. new 키워드를 이용해서 생성하는 방법과 문자열 리터럴로 생성하는 방법입니다.

String a = new String("hello"); // 1. new 키워드로 생성하기

String b = "hello"; // 2. 리터럴로 생성하기

두가지 방식으로 String 객체를 생성해 보았습니다. a,b 변수에 각각 "hello" 라는 값이 들어간것을 볼 수 있습니다. 이 두 방식은 무슨 차이점이 있을까요? 두 값을 비교해보면서 무슨 차이점이 있는지 알아보겠습니다.

String 비교하기

String 객체를 비교할때는 두가지 방법이 있습니다. 바로 ==equals() 입니다.

  • == 연산자는 두 객체의 주소값을 비교 합니다.
  • equals() 함수는 두 객체가 가지고 있는 값을 비교합니다.
String a = new String("hello");
String b = "hello"; 
String c = "hello";

// == , equals() 두 가지 방법으로 비교해보기
System.out.print("(1) " + a.equals(b)); ////// (1)
System.out.print("(2) " + a == b); ////// (2)
System.out.print("(3) " + b == c); ////// (3)
(1) true
(2) false
(3) true

(1)는 두 객체의 값인 "hello"가 동일하므로 true가 출력됩니다.
그런데 (2),(3)의 출력결과는 다소 의문점이 있습니다. a,b,c는 모두 서로 다른 객체인데 왜 두 결과값이 false , true로 갈릴까요?
이 의문점은 문자열 생성 방식에 따른 동작방식을 알면 해결이 가능합니다.

동작방식

new 키워드

new로 객체가 생성이 되면 해당 객체는 Heap 영역에 값이 위치하게 됩니다.
보통 객체가 생성되는 원리와 유사합니다.

String word = new String("hello");

String 리터럴(Literal)

문자열 리터럴을 이용하여 생성하는 방식입니다.
리터럴을 String 레퍼런스에 바로 넣어줍니다.

String word = "hello";

이렇게 String 객체를 생성하면 String Constant Pool 이라는 공간에 값이 생성됩니다.
명칭 그대로 문자열들을 저장해놓은 Pool이라고 볼 수 있습니다.

좀 더 자세히 보자면,

문자열 리터럴을 생성할때 JVM이 String Constant Pool에 객체를 만들고 그 객체의 레퍼런스를 스택에 저장합니다. 저장 방식으로 해시맵을 사용합니다. 해시맵의 각각의 버켓은 같은 해시코드를 가진 문자열의 리스트를 포함하고 있구요.
보다 더 오래전 Java의 경우에서는 Pool의 저장 공간이 고정 사이즈이고 Could not reserve enough space for object heap 에러가 생기곤 했었습니다. Java 7 이전에는 permgen space에 풀이 있었으나 이후부터는 메인 힙 메모리에 있습니다.
클래스 로드시에 모든 클래스들의 String 리터럴들은 Pool로 갑니다. 클래스들의 동일한 String 리터럴은 같은 객체여야하기 때문이죠.
출처

String 리터럴이 Pool에 저장되는 방식을 좀 더 자세히 살펴보겠습니다.
String 리터럴이 String 레퍼런스에 값이 생성될때 String 클래스 메소드인 intern()를 호출합니다.

 	 * <p>
     * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>
     public native String intern();

쉽게 말하면 String Constant Pool에 현재 객체화 하고자하는 String 리터릴의 값이 있다면 있는 값을 리턴하고, 없다면 Pool에 해당 값을 추가후 리턴해준다는 뜻입니다.

다음 그림을 통해 new로 생성하는 경우와 리터럴로 생성하는 경우의 메모리에 저장되는 차이를 보도록 하죠.

  • word1과 word2는 new로 생성되었기 때문에 값은 같을 수 있으나 각각 다른 객체를 참조합니다
  • word3 , word4 그리고 word5는 리터럴로 생성되었기때문에 String Constant Pool에 값이 있습니다
  • word3 , word4는 리터럴로 생성되었고 값이 같기때문에 Pool안에 있는 같은 객체를 참조합니다

String 연산하기

String 객체는 기본적으로 불변(immutable)객체 입니다. 여러 객체가 Pool에 있는 같은 String을 참조하기때문에 String은 immutable해야합니다. 또한 Thread-safe하죠.
String에 값을 변경하려고 한다면 객체의 값이 변경되는것이 아니라 기존 객체는 유지되고 새로운 객체가 생성됩니다.
+ 연산자를 이용하여 String의 값을 추가하곤 합니다.

Java 5 부터는 + 연산자로 연산할때 내부적으로 StringBuilder를 사용한다고 합니다.



마치며

  • String은 new키워드 , String 리터럴 두가지 방법으로 생성이 가능하다.
  • 비교 방법에는 == , equals()가 있는데 각각 주소값비교, 값비교 이다.
  • String 리터럴로 생성하면 String Constant Pool에 객체가 저장된다.
  • String 리터럴로 생성할때 내부적으로 intern() 메소드를 이용한다.
  • String 객체는 immutable하며 Thread-safe하다.
  • String 객체를 연산할때 "+" 연산자로 하며 연산 후에는 값이 바뀌는게 아니라 새로운 객체가 생성된다.

Reference
https://madplay.github.io/post/java-string-literal-vs-string-object
https://www.baeldung.com/java-string-constant-pool-heap-stack

profile
Log Of The Day

0개의 댓글