들어가며
String의 불변성 - StringBuilder와 StringBuffer의 차이점을 다뤘습니다. 이번에는 String + 연산에 대해서 조금 더 자세하게 다뤄보고도록 하겠습니다. 이전 글을 보고 오시는것을 추천드립니다.
[JAVA] StringBuilder, StringBuffer 의 차이점과 주의사항
들어가며이 글에서는 StringBuilder, StringBuffer 차이점뿐만 아니라 사용 이유에 대해서도 다루고있습니다. [JAVA] String의 불변성(immutability)들어가며JAVA를 처음 접할 때, String은 불변객체라고 배우고
tmd8633.tistory.com
이 글을 쓰는 이유
지난시간에 String 의 불변성과 StringBuilder에 대해서 알아보았습니다. 이전 글들을 작성하면서 String + 연산이 최적화 되었다는 얘기를 수 없이 보았습니다.
String[] arr = {"2", "3", "4"};
String str = "1";
for (String s : arr) {
str += s;
}
이렇게 String + 연산을 하게되면 String의 불변성 때문에 "12", "123", "1234"의 메모리가 heap 영역에 저장되고 GC 대상도 증가하게 되는 것은 이제 충분히 이해가 됩니다.
그런데 이 과정을 JDK 5 부터 컴파일 시에 내부적으로 StringBuilder를 사용해서 최적화를 하였고, 완전히 최적화가 되지않아서(매번 StringBuilder객체가 생성되는 문제 등) JDK 9부터 지금까지 StringConcatFactory가 사용되어 + 연산이 완전 최적화 되었다고 했습니다. 이를 실제로 알아보기 위해 bytecode를 까봤습니다.
public static main([Ljava/lang/String;)V
L0
LINENUMBER 13 L0
ICONST_3
ANEWARRAY java/lang/String
DUP
ICONST_0
LDC "2"
AASTORE
DUP
ICONST_1
LDC "3"
AASTORE
DUP
ICONST_2
LDC "4"
AASTORE
ASTORE 1
L1
LINENUMBER 14 L1
LDC "1"
ASTORE 2
L2
LINENUMBER 16 L2
ALOAD 1
ASTORE 3
ALOAD 3
ARRAYLENGTH
ISTORE 4
ICONST_0
ISTORE 5
L3
FRAME FULL [[Ljava/lang/String; [Ljava/lang/String; java/lang/String [Ljava/lang/String; I I] []
ILOAD 5
ILOAD 4
IF_ICMPGE L4
ALOAD 3
ILOAD 5
AALOAD
ASTORE 6
L5
LINENUMBER 17 L5
ALOAD 2
ALOAD 6
INVOKEDYNAMIC makeConcatWithConstants(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; [
// handle kind 0x6 : 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;
// arguments:
"\u0001\u0001"
]
ASTORE 2
L6
LINENUMBER 16 L6
IINC 5 1
GOTO L3
L4
LINENUMBER 19 L4
FRAME CHOP 3
RETURN
L7
LOCALVARIABLE s Ljava/lang/String; L5 L6 6
LOCALVARIABLE args [Ljava/lang/String; L0 L7 0
LOCALVARIABLE arr [Ljava/lang/String; L1 L7 1
LOCALVARIABLE str Ljava/lang/String; L2 L7 2
MAXSTACK = 4
MAXLOCALS = 7
}
StringConcatFactory.makeConcatWithConstants 메소드가 실제로 String + 연산에 사용되고 있었습니다.
그럼 직접 StringBuilder를 사용하지 않고 String + 연산을 해도 최적화가 되는건가? 라는 생각에 테스트를 진행했습니다.
속도테스트는 100배가량 +연산이 느렸고, 메모리도 낭비되는 것을 확인할 수 있었습니다.
그럼 무슨 최적화가 되었다는걸까요? 그 결과를 글로 적고싶어서 이렇게 남깁니다.
Concat
String의 concat() 메소드를 사용해보셨나요? 앞문자열과 뒷문자열을 합쳐주는 기능을 수행하는 메소드입니다.
StringConcatFactory는 그 concat 메소드처럼 여러 문자열을 하나로 합쳐주는 역할을 수행하는 것이었습니다.
String str1 = "1";
String str2 = str1 + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9";
JDK5 이전에는 이런 String + 연산을 하면 "12", "123", "1234" ... 이런 데이터가 저장이 되었지만, StringConcatFactory는 이런 연속적인 문자열 합치는 것을 한번에 수행하면서 최적화가 되는 것입니다.
L0
LINENUMBER 8 L0
LDC "1"
ASTORE 1
L1
LINENUMBER 9 L1
ALOAD 1
INVOKEDYNAMIC makeConcatWithConstants(Ljava/lang/String;)Ljava/lang/String; [
// handle kind 0x6 : 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;
// arguments:
"\u000123456789"
]
ASTORE 2
저는 str1 + "2" + "3' ... + "9" 까지 각각 문자열을 더했지만 이를 StringConcatFactory 에서 "23456789" 문자열로 하나로 합쳐 불필요한 메모리 낭비를 최적화 한겁니다. 그러니까 배열에서 하니씩 꺼내며 + 연산할때에는 최적화가 안되었던것이지요.
그러니까 String + 연산 은 절대하지말고 StringBuilder, StringBuffer나 쓰십쇼
'Language > JAVA' 카테고리의 다른 글
[JAVA] Record와 Sealed (0) | 2024.11.13 |
---|---|
[JAVA] 상속(Inheritance)과 복합(Composition) (0) | 2024.11.12 |
[JAVA] for문과 향상된 for문은 어떤 차이가 있을까? (0) | 2024.09.24 |
[JAVA] If-else 와 switch 중에 어느게 더 빠를까? (0) | 2024.09.24 |
[JAVA] StringBuilder, StringBuffer 의 차이점과 주의사항 (0) | 2024.09.23 |