티스토리 뷰

Java & Kotlin

[Java] 중복 문자열 제거를 통한 메모리 절약을 위한 -XX:+UseStringDeduplication GC 옵션

망나니개발자 2024. 1. 23. 10:00
반응형

아래의 내용은 DZone의 포스팅을 바탕으로 참고하여 정리한 내용입니다.

 

 

 

1. 중복 문자열 제거를 통한 메모리 절약을 위한
-XX:+UseStringDeduplication GC 옵션 


[ 중복 문자열의 개념과 예시 ]

중복 문자열이란?

자바의 literal string은 heap에 별도의 공간으로 존재하는 String Constant Pool에 저장되고, 같은 literal string은 String Constant Pool에 저장된 같은 문자열을 참조한다. 따라서 str == str2 는 true이다. 하지만 new String(”hello”) 로 생성된 객체는 String Constant Pool에 저장되지 않으며, 일반 객체로서 heap에 할당된다.

 

 

이렇듯 다음과 같이 동일한 내용을 갖지만 별도로 저장된 문자열이 존재한다. 둘을 equals로 비교하면 true가 나오지만, ==으로 비교하면 false가 나온데, 이러한 문자열을 우리는 중복 문자열이라고 한다.

String string1 = new String("MangKyu");
String string2 = new String("MangKyu");

 

 

그리고 JDK 개발팀의 조사에 따르면 다음과 같은 자바 애플리케이션의 특징이 있다고 한다.

  • 프로세스의 25%는 문자열임
  • 그 중 13.5%는 중복 문자열임
  • 평균 문자열의 길이는 45자임

 

 

우리는 중복된 문자열에 의해 평균적으로 13.5%의 메모리를 낭비하고 있는 것이다. 참고로 낭비중인 실제 메모리를 측정하려면 HeapHero와 같은 도구를 사용할 수 있다.

 

 

 

중복 문자열 생성 예시1

string literal 패턴을 사용하여 public static final String으로 생성하면 메모리를 최적화할 수 있다. 하지만 이를 적용하지 않고 매번 새롭게 만드는 경우가 있다.

public static final String MANGKYU = "MangKyu";

String string1 = MANGKYU;
String string2 = MANGKYU;

 

 

 

중복 문자열 생성 예시2

뱅킹/전자상거래 애플리케이션을 구축하는 경우 모든 거래 기록에 대한 통화(예: 'USD', 'EUR', 'INR', ....)를 저장하고 있다고 하자. 모든 거래 레코드에는 통화가 있으므로 애플리케이션은 데이터베이스에서 읽은 모든 거래 레코드에 대해 'USD' 문자열 객체를 생성하게 된다. 이 고객에게 수천 건의 거래가 있는 경우, 이 한 고객을 위해 메모리에 수천 개의 중복된 'USD' 문자열 객체를 생성하는 것이다.

마찬가지로 애플리케이션이 데이터베이스에서 여러 열(고객 이름, 주소, 주, 국가, 계좌 번호, ID, .....)을 여러 번 읽을 수도 있고, 그 중에는 중복되는 항목이 있을 수 있다. 애플리케이션은 외부 애플리케이션과 함께 JSON과 같은 데이터를 읽고 쓰며, 많은 문자열을 조작한다. 이러한 모든 작업은 중복 문자열을 생성할 수 있다.

이 문제는 1990년대 중반부터 JDK 팀에서 오랫동안 인식해 왔으며, 지금까지 여러 가지 해결책을 제시해 왔다. 그리고 가장 최근에 추가된 해결책 중 하나가 '- XX:+UseStringDeduplication'이다.

 

 

 

[ -XX:+UseStringDeduplication GC 옵션 ]

-XX:+UseStringDeduplication GC 옵션이란?

-XX:+UseStringDeduplication JVM 옵션은 중복 문자열을 제거하기 위한 최소한의 노력이라고 볼 수 있는데, 같은 문자열이 heap에 중복으로 생성되는 것을 방지하기 위한 옵션이 바로 -XX:+UseStringDeduplication 이다. 해당 옵션을 사용하면, 문자열 내부의 value 값에 대하여 이미 존재하는 value를 재사용한다.

 

 

 

애플리케이션 시작 시 해당 인수를 전달하면, JVM은 가비지 컬렉션 프로세스의 일부로 중복 문자열을 제거하려고 시도한다. 가비지 컬렉션 프로세스 중에 JVM은 메모리의 모든 객체를 검사하므로 이 프로세스의 일부로 객체 중 중복 문자열을 식별하여 제거하려고 시도한다.

 

 

 

-XX:+UseStringDeduplication GC 옵션의 장전과 단점

'-XX:+UseStringDeduplication' 옵션만 추가한다고 13.5%의 메모리를 즉시 절약할 수 있는 것은 아니다. 해당 옵션에는 몇 가지 문제점이 있기 때문이다.

  1. G1 GC 알고리즘에서만 적용 가능하다
  2. Long-Lived 객체에만 적용된다
  3. 3번의 GC에서 살아남아야 대상이 되며, StringDeduplicationAgeThreshold 옵션으로 변경 가능하다
  4. GC 일시 중지 시간(Pause Time)에 영향을 줄 수 있다.
  5. 중복 문자열 객체 자체를 제거하는 것이 아니라, 내부의 char[]만 Replace된다.
  6. Java 8 Update 20에서만 적용 가능하다.
  7. XX:+PrintStringDeduplicationStatistics 옵션을 통해 실행에 걸린 시간, 제거된 중복 문자열의 양, 절감된 비용 등의 통계를 볼 수 있다.

 

 

또한 해당 옵션을 사용한다고 하여 중복 문자열 객체 자체를 제거하는 것은 아니다. 그저 String 객체 내부의 char[]만 대체할 뿐이다. 따라서 중복 문자열 객체를 제거하는 것은 개념적으로 다음과 같이 값 필드를 재할당하는 것에 불과하다.
각 문자열 객체는 최소 24바이트를 차지한다(문자열 객체의 정확한 크기는 JVM 구성에 따라 다르지만 24바이트는 최소값이다). 따라서 이 기능은 짧은 중복 문자열이 많은 경우 메모리 절약이 덜 할 것이다.

String.value = anotherString.value

 

 

위의 내용을 고려하여 정리하면 다음과 같이 정리할 수 있다.

중복 제거는 minor GC 동안 발생할 수 있으며, CPU가 여유가 있을 때 특히 발생하기 쉽다. 그 과정에서 중복인지 검사하는 방법은, hash 값이 존재하는 문자열이 존재하면 둘을 비교하여 하나의 String value 만을 활용하도록 재할당하는 것이다. 그러면 다른 동일한 값은 사용 및 참조되지 않으므로 GC 대상이 된다. 즉, 해당 옵션을 사용하면 minor GC 동작이 추가되어 추가적인 CPU가 활용될 뿐만 아니라 이로 인한 minor GC pause도 증가하게 되는 것이다.

오늘날에는 대부분 클라이언트로부터의 요청이나 DB를 통해 접근한 데이터들로부터 문자열이 생성된다. 하지만 해당 문자열은 캐싱으로 인해 오래 지속되지 않는 이상, 수명이 매우 짧기 때문에 거의 옵션 적용 대상이 되지는 않는다.

따라서 이에 대한 트레이드 오프도 고려를 해야 한다. 만약 서비스에서 중복 문자열을 재생성하는 코드가 없다면, 해당 옵션은 오히려 독이 될 수 있다. 그러므로 서비스의 특성을 고려해야 하며, 이에 대한 테스트까지 확실히 해보는 것이 좋다.

 

 

 

 

아래의 내용은 DZone의 포스팅을 바탕으로 참고하여 정리한 내용입니다.

 

 

 

참고 자료

 

 

 

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함