[Java] 상수 풀(Constant Pool), 문자열 풀(String Pool)

1. 상수 풀의 종류

[1] 상수 풀(Constant Pool): 클래스 파일 내에 존재하는 하나의 섹션입니다. 클래스의 구조를 정의하는 상수들의 집합으로, 참조되는 클래스의 이름, 문자열 및 숫자 상수의 초기 값, 실행에 필요한 기타 데이터가 포함됩니다.

[2] 런타임 상수 풀: JVM의 Metaspace에 로딩된 상수 풀입니다.

[3] 문자열 풀(String Pool): 문자열 리터럴에 대한 정보를 따로 관리합니다.

2. 상수 풀 자세히 알아보기

클래스 파일의 상수 풀을 확인하기 위해 cmd(명령 프롬프트)에서 javap로 “Test.class” 파일의 내용을 확인하겠습니다. javap는 Java 클래스 파일의 바이트 코드를 역어셈블하여 텍스트 형식으로 출력하는 도구입니다.

아래는 “Test.class” 파일의 내용입니다.

package constant_pool;

public class Test {
    public final int num1 = 999;
    public int num2 = 888;

    public static void main(String[] args) {
        String str1 = "Hello";
        String str2 = new String("World!");

        final int num3 = 777;

        System.out.println("DevHyena.com");
    }
}

아래는 cmd에서 “Test.class” 파일을 javap를 사용해 역어셈블한 과정과 결과입니다.

[1] javap를 사용하기 위해 javap가 있는 해당 경로로 이동.(자바를 시스템 환경 변수에 추가했다면 경로 이동은 필요없음.)

C:\Users\devhy>cd C:\Program Files\Java\jdk-21\bin

[2] javap를 사용하여(-v는 자세한 정보를 출력하도록 지시하는 옵션) Test.class 파일을 역어셈블해서 출력.

C:\Program Files\Java\jdk-21\bin>javap -v C:\Users\devhy\Documents\devHyena_Java\out\production\devHyena_Java\constant_pool\Test.class

[3] 결과물

Classfile /C:/Users/devhy/Documents/devHyena_Java/out/production/devHyena_Java/constant_pool/Test.class
  Last modified 2024. 4. 26.; size 805 bytes
  SHA-256 checksum af39c57cc4233d76228032db64665d76e364cf76a81b5be567478da4b9e1b906
  Compiled from "Test.java"
public class constant_pool.Test
  minor version: 0
  major version: 65
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #8                          // constant_pool/Test
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 2, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Fieldref           #8.#9          // constant_pool/Test.num1:I
   #8 = Class              #10            // constant_pool/Test
   #9 = NameAndType        #11:#12        // num1:I
  #10 = Utf8               constant_pool/Test
  #11 = Utf8               num1
  #12 = Utf8               I
  #13 = Fieldref           #8.#14         // constant_pool/Test.num2:I
  #14 = NameAndType        #15:#12        // num2:I
  #15 = Utf8               num2
  #16 = String             #17            // Hello
  #17 = Utf8               Hello
  #18 = Class              #19            // java/lang/String
  #19 = Utf8               java/lang/String
  #20 = String             #21            // World!
  #21 = Utf8               World!
  #22 = Methodref          #18.#23        // java/lang/String."<init>":(Ljava/lang/String;)V
  #23 = NameAndType        #5:#24         // "<init>":(Ljava/lang/String;)V
  #24 = Utf8               (Ljava/lang/String;)V
  #25 = Fieldref           #26.#27        // java/lang/System.out:Ljava/io/PrintStream;
  #26 = Class              #28            // java/lang/System
  #27 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = String             #32            // DevHyena.com
  #32 = Utf8               DevHyena.com
  #33 = Methodref          #34.#35        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #34 = Class              #36            // java/io/PrintStream
  #35 = NameAndType        #37:#24        // println:(Ljava/lang/String;)V
  #36 = Utf8               java/io/PrintStream
  #37 = Utf8               println
  #38 = Utf8               ConstantValue
  #39 = Integer            999
  #40 = Utf8               Code
  #41 = Utf8               LineNumberTable
  #42 = Utf8               LocalVariableTable
  #43 = Utf8               this
  #44 = Utf8               Lconstant_pool/Test;
  #45 = Utf8               main
  #46 = Utf8               ([Ljava/lang/String;)V
  #47 = Utf8               args
  #48 = Utf8               [Ljava/lang/String;
  #49 = Utf8               str1
  #50 = Utf8               Ljava/lang/String;
  #51 = Utf8               str2
  #52 = Utf8               num3
  #53 = Utf8               SourceFile
  #54 = Utf8               Test.java
{
  public final int num1;
    descriptor: I
    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
    ConstantValue: int 999

  public int num2;
    descriptor: I
    flags: (0x0001) ACC_PUBLIC

  public constant_pool.Test();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: sipush        999
         8: putfield      #7                  // Field num1:I
        11: aload_0
        12: sipush        888
        15: putfield      #13                 // Field num2:I
        18: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 11
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      19     0  this   Lconstant_pool/Test;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=4, args_size=1
         0: ldc           #16                 // String Hello
         2: astore_1
         3: new           #18                 // class java/lang/String
         6: dup
         7: ldc           #20                 // String World!
         9: invokespecial #22                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
        12: astore_2
        13: sipush        777
        16: istore_3
        17: getstatic     #25                 // Field java/lang/System.out:Ljava/io/PrintStream;
        20: ldc           #31                 // String DevHyena.com
        22: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        25: return
      LineNumberTable:
        line 8: 0
        line 9: 3
        line 11: 13
        line 13: 17
        line 14: 25
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      26     0  args   [Ljava/lang/String;
            3      23     1  str1   Ljava/lang/String;
           13      13     2  str2   Ljava/lang/String;
           17       9     3  num3   I
}
SourceFile: "Test.java"

출력 결과물 사이에 Constant pool(상수 풀) 섹션이 있습니다.
읽는 방법은 (#인덱스 번호 = 타입, 값, //값에 대한 주석) 입니다.

#16 = String #17 // Hello
#20 = String #21 // World!
#31 = String #32 // DevHyena.com 등 문자열들이 상수 풀에 저장되어 있는 게 보입니다.

#39 = Integer 999
final로 선언된 num1의 999는 상수 풀에 저장되었지만 final로 선언되지 않은 num2는 상수 풀에 저장되지 않았습니다. 또한 num3은 final로 선언되었지만 Test.class의 멤버 변수가 아닌 지역 변수이기 때문에 상수 풀에 저장되지 않았습니다.

3. 상수 풀, 런타임 상수 풀, 문자열 풀 위치

아래 이미지는 “Test.class” 파일이 JVM에서 작동될 때의 위치 참고도입니다.

상수 풀, 문자열 풀 01

4. 런타임 상수 풀 사용 이유

중복된 상수 값 재사용으로 인한 메모리 절약.

상수 풀, 문자열 풀 02

5. 문자열 풀 사용 이유

중복된 문자열 리터럴 재사용으로 인한 메모리 절약.

상수 풀, 문자열 풀 03