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에서 작동될 때의 위치 참고도입니다.
4. 런타임 상수 풀 사용 이유
중복된 상수 값 재사용으로 인한 메모리 절약.
5. 문자열 풀 사용 이유
중복된 문자열 리터럴 재사용으로 인한 메모리 절약.