String 클래스를 조심히 사용하자

이진호·2022년 8월 31일
0

JAVA

목록 보기
4/10
post-thumbnail

자바에서 가장 많이 사용되는 객체중 하나인 String 클래스는 잘못 사용하는 경우 메모리와 성능에 많은 영향을 줍니다.

Immutable Object(불변 객체)

String 클래스는 Immutable Object(불변 객체)입니다.

String str = "Hello"
str += "World!"

위 예제와 같이 코드를 실행하는 경우 str의 값은 변경되지 않고 새로운 객체를 생성해서 참조하는 방식으로 동작됩니다.

Java String 객체와 String 리터럴
Java String이 불변한 이유

String 객체 + 연산

String 객체에서 문제가 되는 부분은 우리가 흔히 사용하는 + 연산입니다.

public String plusString() {
    String temp = "";
    for (int i = 1; i <= 1000; i++) {
        temp += i;
    }
    return temp;
}

위와 같이 반복문을 통해 String 객체에서 1000번의 + 연산을 한 경우, 마지막으로 생성된 String 객체를 제외하고 연산과정에서 생성된 1000개의 객체들은 참조 대상이 없으므로 GC의 대상이 됩니다.

이러한 작업은 쓸데없이 메모리를 많이 사용하고 응답 속도에 많은 영향을 미치게 됩니다.

StringBuilder, StringBuffer

위에서 살펴봤던 String 객체 + 연산의 문제점을 해결해줄 방법은 내부적으로 동적 배열을 사용하는 StringBuilder와 StringBuffer를 사용하는 것입니다.

두 객체의 append 메서드나 insert 메서드를 사용하면 버려지는 객체 없이 문자열을 더할 수 있습니다.

public String plusString() {
    StringBuilder builder = new StringBuilder();
    for (int i = 1; i <= 1000; i++) {
        builder.append(i);
    }
    return builder.toString();
}

JDK 5.0 이상에서는 String 객체로 + 연산을 하면 컴파일 시 자동으로 StringBuilder 객체의 append 메서드를 사용하도록 변환됩니다. 하지만 반복적인 작업 전에 미리 StringBuilder 객체를 생성하지 않았다면, 컴파일시 자동으로 StringBuilder 객체로 변환되어도 + 연산을 반복한 만큼 StringBuilder 객체가 버려지게 되고, 결론적으로 이전과 같이 GC의 대상이 되어 메모리와 성능에 영향을 미치게 됩니다.

for (int i = 1; i <= 1000; i++) {
    temp += i;
}

// 컴파일 시
for (int i = 1; i <= 1000; i++) {
    temp = (new StringBuilder(temp)).append(i).toString();
}

이와 같은 현상은 JDK 9에서 makeConcatWithConstants 로직이 추가되어 추가적으로 개선되었습니다.

StringBuilder vs StringBuffer

기본적으로 StringBuilder와 StringBuffer가 제공하는 기능은 같지만 synchronized 키워드를 사용한 동기화 여부에서 차이점이 존재합니다.

// StringBuffer의 append 메서드 
@Override
@IntrinsicCandidate
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

// StringBuilder의 append 메서드
@Override
@IntrinsicCandidate
public StringBuilder append(String str) {
    super.append(str);
    return this;
}

StringBuffer 객체는 거의 모든 메서드를 synchronized를 사용해서 동기화하기 때문에 멀티 스레드 환경에서 안전하게 사용할 수 있습니다. 반대로 StringBuilder는 동기화하지 않기 때문에 멀티 스레드 환경에서 안전하지 못합니다.

대신 synchronized를 사용한 동기화는 lock을 걸고 푸는 오버헤드가 있기 때문에 비교적 속도가 느립니다. 따라서 StringBuilder가 StringBuffer보다 다소 빠른 특징이 있습니다.

Java Synchronized block(동기화 블록)

출처

0개의 댓글