불필요한 객체 생성은 피해라

String 클래스의 예

String a = new String("Test");

문장이 실행될 때마다 String 인스턴스를 새로 만든다.

String b = "Test";

하나의 String 인스턴스를 사용한다. 같은 가상 머신 안에서 이와 똑같은 문자열 리터럴을 사용하는 모든 코드가 같은 객체를 재사용함이 보장된다.

정적 팩터리 메서드

생성자 대신 정적 팩터리 메서드를 제공하는 불변 클래스에서는 정적 팩터리 메서드를 사용해 불필요한 객체 생성을 피할 수 있다.

Boolean(String) // 생성자(자바9 에서 deprecated로 지정)

Boolean.valueOf(String) // 팩터리 메서드

생성자는 호출할 때마다 새로운 객체를 만들지만, 팩터리 메서드는 그렇지 않다. 불변 객체만이 아니라 가변 객체라 해도 사용중에 변경되지 않을 것임을 안다면 재사용할 수 있다.

생성 비용이 비싼 객체

public class RomanNumerals {
    // 성능을 훨씬 더 끌어올릴 수 있다!
    static boolean isRomanNumeralSlow(String s) {
        return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
                + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
    }

    // 값비싼 객체를 재사용해 성능을 개선한다.
    private static final Pattern ROMAN = Pattern.compile(
            "^(?=.)M*(C[MD]|D?C{0,3})"
                    + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

    static boolean isRomanNumeralFast(String s) {
        return ROMAN.matcher(s).matches();
    }

위의 isRomanNumeralSlow 메서드의 String.mathces 는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해 사용하기에는 적합하지 않다. 메서드 내부에서 만드는 정규표현식용 Pattern 인스턴스는, 한 번 쓰고 버려져서 곧바로 가비지 컬렉션 대상이 된다.

위의 isRomanNumeralFast 메서드에서 불변인 Pattern 인스턴스를 클래스 초기화 과정에서 직접 생성해 캐싱해두고, 재사용한다.

어댑터의 불필요한 객체생성

  • 어댑터(View) : 실제 작업은 뒷단 객체에 위임하고, 자신은 제2의 인터페이스 역할을 해주는 객체

어댑터는 뒷단 객체만 관리하면 된다. 즉, 뒷단 객체 외에는 관리할 상태가 없으므로 뒷단 객체 하나당 어댑터 하나씩만 만들어지면 충분하다.

Map 인터페이스의 keySet 메서드는 Map 객체 안의 키 전부를 담은 Set 뷰를 반환한다. 그리고 반환한 객체 중 하나를 수정하면 다른 모든 객체가 바뀐다. 모두 똑같은 Map 인스턴스를 대변하기 때문이다. 따라서 keySet이 뷰 객체를 여러 개 만들어도 상관은 없지만, 그럴 필요가 없다.

오토박싱의 불필요한 객체생성

private static long sum() {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++)
        sum += i;
    return sum;
}

sum 변수를 long이 아닌 Long으로 선언해서 불필요한 Long 인스턴스가 만들어진다.
박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의해야한다.

이번 아이템을 “객체 생성이 비싸니 피해야 한다”로 오해하면 안 된다. 요즘의 JVM에서는 별다른 일을 하지 않는 작은 객체를 생성하고 회수하는 일은 크게 부담되지 않는다. 프로그램의 명확성, 간결성, 기능을 위해서 객체를 추가로 생성하는 것은 좋은 일이다.
거꾸로 아주 무거운 객체가 아니면 단순히 객체 생성을 피하고자 객체 풀을 만들지 말자.
데이터베이스 연결 같은 경우는 생성 비용이 워낙 비싸서 객체 풀을 만들어 재사용하는 편이 낫다. 하지만 일반적으로는 자체 객체 풀은 코드를 헷갈리게 만들고 메모리 사용량을 늘리고 성능을 떨어뜨린다. 요즘 JVM의 GC는 잘 최적화되어서 가벼운 객체용을 다룰 때는 직접 만든 객체 풀보다 훨씬 빠르다.

Comments