String Constant Pool(String Pool)
String 객체는 불변객체라고 합니다. 한 번 객체가 생성되면 값이 바뀌지 않는 것이죠.
그렇다면 아래 코드의 결과는 어떻게 될까요?
public class StringExample {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str1.equals(str3));
}
}
결과
첫번째 String과 두번째 String이 다르다고 나와야 할 것 같은데 같다고 나왔습니다.
문자열 리터럴(여기서는 "abc")로 객체를 생성하면, 이 객체는 JVM 내부의 Heap영역에 있는 String Constant Pool에 저장됩니다.
코드 첫번째 줄 str1 에서 객체를 저장했으므로, 두번째줄인 str2 에서는 이 String Constant Pool에서 객체의 참조를 할당합니다.
그래서 == 연산을 했을 때 true가 나오게 됩니다.
str3에서 new String("abc") 로 객체를 생성하면, Heap영역에 String Constant Pool이 아닌 새로운 String 객체로 생성됩니다.
혹시 이 객체를 String Constant Pool에 넣는 방법이 있을까요?
String 클래스에서 제공하는 intern()라는 native 메소드를 통해서 넣을 수 있습니다.
동일한 문자열이 String Constant Pool에 없다면 해당값을 추가하고, 만약 있다면 참조값을 리턴합니다.
하지만 이 메소드는 절대 사용하면 안됩니다.
intern() 메소드를 수행한 뒤에 문자열은 equals() 메소드가 아닌, == 으로 동일한지 비교할 수 있다.
그렇지만 새로운 문자열을 쉴새 없이 만드는 프로그램에서 intern() 메소드를 사용하여 억지로 Constant pool에 값을 할당하도록 만들면, 저장되는 영역은 한계가 있기 때문에 그 영역에 대해 별도로 메모리를 청소하는 단계를 거친다.
따라서, 작은 연산 하나를 빠르게 하기 위해서 전체 자바 시스템의 성능에 악영향을 주게 된다.
출처 - 자바의 신2 중
자바에서는 String의 불변적인 특성을 살려 상수로 저장하여 성능 및 메모리 최적화를 하고 있습니다.
그러므로 String 생성시에는 new String은 지양하는 것이 좋습니다.
문자열 더하기
1. JDK 5 이전
JDK 5 이전에서는 String에 더하기 연산을 하면 새로운 String 객체를 만들어서 합쳐졌습니다.
N번 더하는 과정을 거친다면 N개의 String 객체가 생성되므로 이전 객체는 참조값이 사라져 메모리 낭비가 심해집니다.
2. JDK 5~8
JDK 5 ~8 버전에서는 String더하기 연산을 하면 컴파일 시 StringBuilder 혹은 StringBuffer 를 통한 연산으로 변경되었습니다.
둘의 차이점은 StringBuilder(StringBuffer)는 객체를 따로 생성하는 것이 아니라, 배열로 초기 공간을 할당하여 값을 추가해줍니다.
아무런 값을 주지 않을 경우 16 character 로 할당하여, 추가하는 String크기만큼 동적으로 큰 배열을 만들어 복사합니다.
StringBuilder의 더하기 메소드인 append()를 타고 들어가면, 그 안에서 AbstractStringBuilder의 append() 메소드를 사용합니다.
다시 이 메소드 내부를 들어가보면 ensureCapacityInternal() 메소드를 사용하여 배열 크기를 바꾸는 것을 볼 수 있습니다.
코드
String concat(String s, int i) {
return s + i;
}
컴파일(바이트코드)
위 코드의 class 파일을 javap -c로 읽어보면
java.lang.String concat(java.lang.String, int);
Code:
0: new #2 // class StringBuilder
3: dup
4: invokespecial #3 // Method StringBuilder."<init>":()V
7: aload_0
8: invokevirtual #4 // Method StringBuilder.append:(LString;)LStringBuilder;
11: iload_1
12: invokevirtual #5 // Method StringBuilder.append:(I)LStringBuilder;
15: invokevirtual #6 // Method StringBuilder.toString:()LString;
주석에서처럼 StringBuilder를 사용하지 않았음에도 바이트코드를 StringBuilder로 변경하여 연결하는 것을 확인할 수 있습니다.
3. JDK 9 이상
JDK 9 이상에서는 invokedynamic 지시어와 StringConcatFactory를 통해 컴파일 타임에 문자열 연결 방법을 정의하고, 런타임에 구현을 연결해 동작합니다.
코드
String concat(String s, int i) {
String str = "abc";
String s = str + 123;
}
컴파일(바이트코드)
java.lang.String concat(java.lang.String, int);
Code:
0: aload_1
1: iload_2
2: invokedynamic #2, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
7: a
이전 버전에서의 바이트코드와 달리 InvokeDynamic을 사용하고 있습니다.
런타임로직
BootstrapMethods:
0: #22 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
Method arguments:
#23 \u0001\u0001
javap -c -v 명령어를 이용해 런타임 로직을 들여다보면 위와 같이 StringConcatFactory의 makeConcatWithConstants 메소드를 연결하여 사용한 것을 확인할 수 있습니다.
'Language > JAVA' 카테고리의 다른 글
JCF - 자바 컬렉션 프레임워크의 종류 (0) | 2024.07.24 |
---|---|
자바 제네릭(Generic) 타입에 대하여 (0) | 2024.07.23 |
String 클래스 파고들기 - String 생성과 byte 변환시 주의점 (0) | 2024.04.05 |
자바가 돌아가는 동작 원리 (0) | 2024.03.06 |
자바에서의 Call by value 와 Call by reference (1) | 2024.03.06 |
남에게 설명할 때 비로소 자신의 지식이 된다.
포스팅이 도움되셨다면 하트❤️ 또는 구독👍🏻 부탁드립니다!! 잘못된 정보가 있다면 댓글로 알려주세요.