[자바성능튜닝이야기] 3. 자꾸 String을 쓰지 말라는 거야

3. 자꾸 String을 쓰지 말라는 거야

다음은 일반적으로 사용하는 쿼리 작성 문장이다.

String strSQL = "";
strSQL += "select * ";
strSQL += "from ( ";
strSQL += "select A_column, ";
strSQL += "B_column, ";
// 중간 생략(약 400라인)
...

이와 같은 메서드를 한 번 수행하면, 다음과 같은 메모리 사용량을 확인할 수 있다. | 구분 | 결과 | |–|–| | 메모리 사용량 | 10회 평균 약 5MB | | 응답 시간 | 10회 평균 약 5ms |

위 코드를 메모리 사용량과 응답 시간을 줄이기 위해 StringBuilder로 변경하였다.

StringBuilder strSQL = new StringBuilder();
strSQL.append(" select * ");
strSQL.append(" from ( ");
strSQL.append(" select A_column, ");
strSQL.append(" B_column, ");
// 중간 생략(약 400라인)
...

이렇게 변경한 후 수행한 결과는 다음과 같다.

구분 결과
메모리 사용량 10회 평균 약 371KB
응답 시간 10회 평균 약 0.3ms

StringBuffer 클래스와 StringBuilder 클래스

StringBuffer 클래스나 StringBuilder 클래스에서 제공하는 메서드는 동일하다. 하지만 StringBuffer 클래스는 스레드에 안전하게(ThreadSafe) 설계되어 있으므로, 여러 개의 스레드에서 하나의 StringBuffer 객체를 처리해도 전혀 문제가 되지 않는다. 하지만 StringBuilder는 단일 스레드에서의 안전성만을 보장한다.

String vs. StringBuffer vs. StringBuilder

String, StringBuffer, StringBuilder 셋 중 어느 것이 가장 빠르고 메모리를 적게 사용할까?

프로파일링 툴의 결과는 다음과 같다.

주요 소스 부분 응답 시간(ms) 비고
a+=aValue; 95,801.41ms 95초
b.append(aValue); 247.48ms 0.24초
c.append(aValue); 174.17ms 0.17초

그리고 메모리 사용량을 다음과 같다.

주요 소스 부분 메모리 사용량(bytes) 생성된 임시 객체수 비고
a+=aValue; 100,102,000,000 4,000,000 약 95Gb
b.append(aValue); 29,493,600 1,200 약 28Mb
c.append(aValue); 29,493,600 1,200 약 28Mb

응답 시간은 String보다 StringBuffer가 약 367배 빠르며, StringBuilder가 약 512배 빠르다. 메모리는 StringBuffer와 StringBuilder보다 String에서 약 3,390배 더 사용한다. 이러한 결과가 왜 발생하는지 알아보자.

a += aValue; // a = a + aValue

a에 aValue를 더하면 새로운 String 클래스의 객체가 만들어지고, 이전에 있던 a 객체는 필요 없는 쓰레기 값이 되어 GC 대상이 되어 버린다. 이러한 작업이 반복 수해오디면서 메모리를 많이 사용하게 되고, 응답 속도에도 많은 영향을 미치게 된다. GC를 하면 할수록 시스템의 CPU를 많이 사용하게 되고 시간도 많이 소요된다.