[Java] String 문자열 연산의 문제점
최근에 FullGC가 빈번하게 발생하며 서비스 지연이 계속됐다.
원인 중 하나가 객체의 메모리 누수였고. 많은 비중이 String과 같은 문자열 객체였다.
* Full GC (Full Garbage Collection)
Java에서 지원하는 메모리 관리 기법. JVM의 Heap 영역에 동적으로 할당된 메모리 중 필요없는 객체를 제거한다.
Full GC는 메모리 영역 모두를 GC하기 때문에 서비스 중단 시간이 더 오래 걸릴 수 있다.
* 메모리 누수(Memory Leak)
더 이상 사용하지 않는 객체들이 heap 영역에 남아있어 불필요하게 메모리를 차지하고 있는 상황. Java에서는 가비지 컬렉션으로 자동적으로 메모리를 관리해준다. 그러나 가비지 컬렉션이 빈번하게 발생할 경우 위와 같은 문제가 발생한다.
Java에서 문자열을 위한 주요 클래스는 3가지가 있는데.
성능 최적화를 위해서 각각의 특징을 이해하고 적절하게 선택해야 한다.
String | StringBuffer | StringBuilder |
불변성 | 가변성 | 가변성 |
스레드 안전 | 스레드 안전 | 스레드 안전X |
성능 저하 위험 | 문자열 자주 변경시 적합 | 단일 스레드에 적합 |
1. String
- 불변성 (Immutable): 한 번 생성되면 변경되지 않으며 문자열을 변경하면 새로운 String 객체가 생성된다.
- 성능: 문자열을 자주 변경하거나 조작하는 경우, String을 사용하면 매번 새로운 객체가 생성되므로 성능이 떨어진다.
- 스레드 안전성 (Thread-Safety): String은 불변 객체이기 때문에 스레드에서 안전하다.
- 사용 예: 상수 문자열을 다룰 때, 또는 문자열이 자주 변경되지 않는 경우.
2. StringBuilder
- 가변성 (Mutable): 가변 객체로 문자열을 수정할 때 기존 객체를 직접 변경한다.
- 성능: 문자열을 자주 수정할 때 적합하며 StringBuffer과 비슷하면 성능이 조금 더 좋다.
- 스레드 안전성 (Thread-Safety): 스레드 안전성을 보장하지 않으므로 멀티스레드 환경에서는 주의해야 한다.
- 사용 예: 단일 스레드 환경에서 문자열을 자주 수정할 때, 성능이 중요할 때.
3. StringBuffer
- 가변성 (Mutable): 가변 객체로 문자열을 수정할 때 기존 객체를 직접 변경한다.
- 성능: 문자열을 자주 수정할 때 적합하다.
- 스레드 안전성 (Thread-Safety): 스레드 안전성을 보장한다. 멀티 스레드 환경에서 사용 가능하다.
- 사용 예: 멀티스레드 환경에서 문자열을 자주 수정해야 할 때.
그러나 사실...
Java 1.5 버전 이상에서는 String의 성능 문제를 해결하기 위해서 + 연산시에는 자동적으로 StringBuilder를 컴파일된다.
그렇다보니... 가비지 컬렉션이 향상된 자바 11 이상을 사용하면 메모리 누수 문제를 겪을 일이 많지는 않다.
자바 8까지만 되어도 이전 버전과는 달라진 점이 크게 체감되기 때문에 최소 8 이상의 버전을 사용하는 게 좋다. https://escapefromcoding.tistory.com/794
Java 8에서 개선된 가비지 컬렉터는?
개요 Java 8은 2014년 3월에 출시되었으며, Java 7과 다른 가비지 컬렉터 구조를 가집니다. 대표적으로 Java 7의 PermGen이 Java 8에서 Metaspace로 대체되었습니다. 어떤 차이가 있는지 알아보겠습니다. PermGe
escapefromcoding.tistory.com
하지만 성능 최적화를 생각하여 개발하는 건 언제나 중요하고
String + 연산시에 언제나 StringBuilder로 컴파일 되는 건 아니다.
반복문 내에서 연산을 하면 동일하게 매번 새로운 객체를 생성하며
.concat()과 같은 메소드를 사용할 때도 동일하게 성능 저하가 발생할 가능성이 높다.
아래 링크를 참고.
https://c-king.tistory.com/242
String Operator '+' 의 작동 원리(2022.03.24 수정) Java 9
String을 비교할 때 왜 .equals()를 사용할까에 대해 궁금해하면서 공부를 하게됐다. 새로운 줄에서 + 연산 public class Main { public static void main(String[] args) throws IOException { String str1 = "a"; str1 += "bc"; String
c-king.tistory.com
반복문에서 String 연산을 지양할 것. 필요하다면 StringBuilder, StringBuffer를 사용하자.
특히 String 객체가 아니더라도 반복문 내에서 객체를 생성할 경우 메모리 누수의 위험이 커진다.
메모리 누수 자체는 한 가지 원인으로 발생하는 건 아니더라도 코드를 작성할 때는 한 번씩 생각해봐야 한다.