티스토리 뷰
[Java] 자바 9에 개선 및 최적화된 String 내부 구조(JEP254: Compact Strings)
망나니개발자 2025. 1. 14. 10:00
1. 자바 9에 개선 및 최적화된 String 내부 구조 (JEP254: Compact Strings)
[ 자바 8까지의 String 클래스의 문제점 ]
자바 8까지는 내부적으로 char형 배열을 사용하여 문자열을 다루고 있었다.
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
private int hash;
// ...
}
하지만 이러한 방식은 메모리 비효율적인 문제가 있었다. 왜냐하면 자바의 Char 타입과 String 타입은 유니코드를 통해 전 세계적으로 일관되게 텍스트 데이터를 표현하고자 그 당시에 주목받던 UTF-16을 활용하기 때문이다.
UTF-16은 가변 길이 인코딩 방식으로, 대부분의 문자는 2바이트를 사용하지만, 보조 평면 문자(U+10000 이상)는 4바이트를 사용한다. 하지만 UTF-16은 당시 주류였던 아스키코드와 호환되지 않으며, 아스키코드에서는 대부분의 사용 비중을 차지했던 영어가 1바이트만 사용되는 데 반해 UTF-16은 영어에도 2바이트를 사용하는 등의 문제가 있었다.
그 외에도 여러 가지 이유들로 인해 현재는 UTF-8이 사실상 표준이 되었지만, 내부적으로 UTF-16을 사용하는 자바의 Char 타입과 이를 활용하는 String 타입에 의해 불필요한 메모리 낭비가 존재했다.
[ 자바 9부터 개선된 String 클래스의 내부 동작 ]
자바 언어의 개발자들 역시 이러한 문제를 인지하고 있었고, 자바 6에 -XX:+UseCompressedStrings 옵션을 통한 문자열 압축 기능으로 최적화 시도를 했었지만, 의도치 않은 성능 저하로 인해 자바 7에 해당 기능이 바로 제거되었다. 그리고 이후에 JEP 254:Compact String이라는 내용으로 해당 부분을 다시 개선하고자 하였다.
자바 9부터는 UTF-16이 아닌 LATIN-1 표현을 사용하여 char[]가 아닌 byte[]로 문자열을 저장하게 되었다. 참고로 ISO_8859_1으로도 불리는 LATIN-1은 기존 아스키코드에 서유럽 문자를 표현하는 문자 집합이 추가된 것이다. LATIN-1은 영어와 같이 한 문자만 필요한 경우에는 1바이트만 사용하므로 문자열 사용에 따른 메모리 사용량을 절감 할 수 있게 되었다.
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;
private final byte coder;
private int hash;
// ...
static final byte LATIN1 = 0;
static final byte UTF16 = 1;
static final boolean COMPACT_STRINGS;
static {
COMPACT_STRINGS = true;
}
public int indexOf(int ch, int fromIndex) {
return isLatin1()
? StringLatin1.indexOf(value, ch, fromIndex)
: StringUTF16.indexOf(value, ch, fromIndex);
}
}
private boolean isLatin1() {
return COMPACT_STRINGS && coder == LATIN1;
}
}
이로 인해 문자열의 길이를 계산하는 로직 등에도 최적화가 가능해졌다. 문자열이 LATIN-1만 포함하는 경우, coder의 값은 0이 되어 문자열의 길이는 바이트 배열의 길이와 동일하다. 문자열이 UTF-16 표현인 경우 coder의 값은 1이 되고, 따라서 길이는 실제 바이트 배열 크기의 절반이 된다.
public int length() {
return value.length >> coder;
}
즉, 자바9 이전에는 String에서 1바이트만 필요한 경우에도 내부적으로 2바이트가 사용되었지만, 자바 9부터는 String 내부의 개선을 통해 필요한 만큼의 메모리만 사용 가능하도록 개선이 있었다. 메모리 사용량이 줄어든다는 것은 그 만큼의 GC 발생 역시 줄어든다는 것을 의미하여 큰 효과가 있을 것으로 기대되며, 내부 벤치마킹 결과에 따르면 Latin형식의 문자열이 20%정도 빠르며 30%정도 가비지가 적었다고 한다. 그리고 무엇보다도 Compact String에 대한 모든 변경 사항은 String 클래스의 내부 구현에만 적용되며, String을 사용하는 개발자에게는 어떠한 영향도 미치지 않는다.
다음과 같은 상황에서는 해당 기능의 비활성화를 고려할 수 있다고 하는데, 이를 위해 새로운 JVM 옵션 -XX:-CompactStrings가 도입되었다.
- JVM/애플리케이션에서 사용되는 문자열 객체가 다중 바이트 문자 문자열이 압도적으로 많은 경우
- JDK 8에서 JDK 9로 마이그레이션하는 과정에서 Compact String에 의한 심각한 성능 저하가 발생한 경우
참고 자료
- https://openjdk.org/jeps/254
- https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html
- https://www.oracle.com/java/technologies/javase/9-new-features.html
- https://www.baeldung.com/java-9-compact-string