[Effective Java] 5. 불필요한 객체 생성 지향

2 분 소요

기능적으로 동일한 객체는 필요할때마다 생성하는 것보다 재사용 하는 것이 좋다.

1. String 객체

String str = new String("newString");

극단적인 예로 위 방법은 실행할때 불필요한 String 객체를 하나 더 만든다.
“newString” 자체도 String 객체인데, 이를 String 생성자를 통해 또 객체를 생성하기 때문이다.

그냥 String str = "newString"; 으로 쓰면 된다.

특히 String의 경우 같은 JVM 내에서 재활용되기 때문에 다른 곳에서 동일한 문자열을 같은 String객체를 사용할때 이미 생성된 객체를 활용한다.

(궁금한 점) : 생성자를 통해 String 생성할때 이미 같은 문자열이 jvm에 있는 경우.. 이 때도 재활용하는가? 그렇다면 new String(“”);이 불필요한 객체를 생성하는 것은 아니지 않나?

2. Immutable 객체를 생성할때

변경불가능한 Immutable class가 있고, 만약 이 class가 생성자와 static factory method를 모두 제공한다면, 생성자보다 static factory method를 사용하는 것이 좋다.

예를 들어 new Boolean(String) 보다는 Boolean.valueOf(String) 을 사용하는 것이 좋다. 생성자는 호출할때마다 새로운 객체를 만들지만 static factory method는 보통 그렇지 않기 때문이다.
어차피 Immutable 객체인 경우 값이 바뀌지 않기 때문에 같은 객체를 리턴해줘도 무방하겠다.

3. static 블럭의 활용

한번 생성 후 값을 바꿀일이 없는 객체가 자주 불리는 경우 static 블럭을 통해 한번만 생성해줄 수 있다.

private final Date birthDate;
public boolean isBabyBoomer() {
    Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    gmtCal.set(1946, Calendar.JANUARY, 1,0,0,0);
    Date boomStart = gmtCal.getTime();
    gmtCal.set(1965, Calendar.JANUARY, 1,0,0,0);
    Date boomEnd = gmtCal.getTime();
	
    return birthDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) <0;
}

여기서 isBabyBoomer() 는 호출될때마다 Calendar, Date, TimeZone 객체를 생성한다. 만약 이 메서드가 자주 불린다면 성능 향상을 위해 static 블럭에서 초기화하도록 개선할 수 있다.

private final Date birthDate;

// final static도 static 블럭에서는 초기화 가능하다.
private static final Date BOOM_START;
private static final Date BOOM_END;

static {
    Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    gmtCal.set(1946, Calendar.JANUARY, 1,0,0,0);
    BOOM_START = gmtCal.getTime();
    gmtCal.set(1965, Calendar.JANUARY, 1,0,0,0);
    BOOM_END = gmtCal.getTime();
}

public boolean isBabyBoomer() {
    return birthDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) <0;
}

위 코드는 이 클래스가 초기화될때 static 블럭을 실행하여 BOOM_START, BOOM_END 날짜를 계산해두기 때문에 isBabyBoomer() 메서드가 실행될때마다 불필요하게 객체 생성하는 일이 없다.

4. Autoboxing

JDK 1.5부터 Autoboxing이 생기면서 기본 자료형과 Wrapper class간 연산이 가능해졌다.

이러한 연산의 의미적인 차이는 미미하지만 성능 차이는 크다.

public static void main(String[] args) {
    Long sum = 0L;
    for(long i=0; i<Integer.MAX_VALUE; i++) {
        sum += i;
    }
}

위 연산의 경우 변수 sum이 기본자료형 long이 아니라 Wrapper class인 Long을 사용하여 성능이 한참 떨어진다.

그 이유는 덧셈이 발생할때마다 새로운 Long 객체가 생성되기 때문이다.

가능한 wrapper class 사용보다는 기본 자료형을 사용하도록 하여 예상치 못한 객체 생성이 발생하는 것을 방지하는 것이 좋겠다.

5. Object pool

불필요한 객체 생성을 하지 않기 위해서 Object pool을 만드는 방법이 있다.
하지만 객체 생성 비용이 극단적으로 높은것이 아닌 이상 가능한 Pool을 사용하지 말자.

괜히 Pool을 관리하게 되면 코드가 어려워지고, 메모리 사용량이 늘어난다.

최신 JVM이라면 최적화된 GC로 인해 가벼운 객체일때 Object Pool 보다 나은 성능을 보여줄 수 있다.

5.1. Thread pool이나 Database Connection pool

일반적으로 pool을 운영하는 흔한 경우이다.
Database Connection Pool의 경우 pool을 운영하는 것은 합당하다.

데이터베이스에 접속하는 비용은 충분히 높고, 라이센스 정책등에 의해 데이터베이스에 접속가능한 연결 개수가 제한되어 있는 경우도 있기 때문이다.

댓글남기기