Home [Effective Java] Item06. 불필요한 객체 생성을 피라하!
Post
Cancel

[Effective Java] Item06. 불필요한 객체 생성을 피라하!

Item06. 불필요한 객체 생성을 피라하!

똑같은 기능의 객체는 재사용하자!

1
2
3
4
5
// 아래 문장은 실행될 때마다 String 인스턴스를 생성한다. "bikini" 자체가 String 생성자로 만들어내려는 결과와 완전동일하다.
String s = new String("bikini");

// 위 코드의 개선. 하나의 String 인스턴스를 이용한다. 같은 VM 안에서 "bikini" 문자열 리터럴 사용은 이 객체를 재사용함이 보장된다.
String s = "bikini";
1
2
3
4
5
6
// 생성자 대신 정적 팩터리 메서드를 제공하는 불변 클래스에서
// 아래 코드는 불필요한 객체 생성을 계속한다.
Boolean(String);

// 팩터리 메서드 사용을 통해 불필요한 객체 생성을 피할 수 있다.
Boolean.valueOf(String);
  • 불변 객체 뿐만 아니라 가변 객체더라도 사용 중 변경이 이뤄지지 않는다면 재사용이 가능하다.
1
2
3
4
//주어진 문자열이 유효한 로마 숫자인지 확인하는 메서드
static boolean isRomanNumeral(String s) {
  return s.match("^(?=.)M*(C[MD]|D?C{0,3})"+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"));
}
  • 위 경우 성능이 중요한 상황에서 적합한 방법이 아니다.
  • 정규표현식에서 패턴으로 사용하는 인스턴스는 한번 사용하고 가비지 컬렉션의 대상이 된다. 즉 쓰레기가 된다.
  • isRomanNumeral 함수를 실행할 때마다 유한 상태 머신을 만들기에 생성 비용이 높다.
1
2
3
4
5
6
7
// 미리 객체를 생성하여 캐싱해두고, 재사용하는 방식으로 개선
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 isRomanNumeral(String s) {
    return ROMAN.matcher(s).matches();
}
  • 성능 개선을 위해 패턴을 Pattern 인스턴스 클래스 초기화 시 직접 생성해 캐싱한다. 즉 메모리에 올려둔다.
  • 미리 생성한 패턴 재사용을 통해 이전 방식과 비교해 상당한 성능 개선을 보인다.

불변객체 재사용을 조심하자!

  • 불변 객체인 경우에 안정적으로 재사용하는 것이 매우 명확하지만 그렇지 않은 경우도 분명 존재한다.
  • 그 예가 어댑터를 사용하는 경우다.
  • 어댑터란? 실제 작업은 뒷단 객체에게 위임하고, 자신은 제2의 인터페이스 역할을 해주는 객체이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UsingKeySet {
    public static void main(String[] args) {
        Map<String, Integer> menu = new HashMap<>();
        menu.put("Burger", 8);
        menu.put("Pizza", 9);

        Set<String> names1 = menu.keySet();
        Set<String> names2 = menu.keySet();

        names1.remove("Burger");
        System.out.println(names2.size()); // 1
        System.out.println(menu.size()); // 1
    }
}
  • Map 인터페이스가 제공하는 keySet의 경우, Map 인스턴스를 대변하는 Set 인터페이스 뷰를 반환한다. keySet 호출을 할 때마다 서로 다른 Set 인스턴스를 반환할 것 같지만 그렇지 않다. 반환하는 인스턴스는 같은 Map을 대변하는 Set이기에 모두 같다.

오토박싱

  • 불필요한 객체를 만들어내는 또다른 예이다.
  • 오토박싱이란? 프로그래머가 기본 타입과 박신된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해주는 기술이다.
  • 오토박싱은 프리미티브 타입과 박스 타입의 경계를 흐리게 해준다. 하지만 완전히 없애는 것은 아니다.
1
2
3
4
5
6
7
private static long sum() {
  Long sum = 0L;
  for(long i=0; i<=Integer.MAX_VALUE; i++) {
    sum += i;
  }
  return sum;
}
  • 위 코드의 경우, sum을 정의할 때 long 프리미티브 타입이 아닌 Long 박스 타입으로 선언했다. 그렇기 때문에 sum이 계산되는 과정마다 새로운 Long 객체가 생성된다. 매우 비효율적이다.
  • Long -> long : 6.3s -> 0.59s

주의 사항

  • ‘객체 생성은 비용이 많이 드니 피해야만 한다’로 오해하지 말자! 프로그램의 명확성, 간결성, 기능을 위해 객체를 추가로 생성하는 것이라면 일반적으로 의미있는 일이다.
  • 데이터베이스 연결 같이 생성비용이 높은 경우가 아니면 객체 생성을 피하고자 자신만의 객체 풀을 생성해 사용하는 것은 지양하자! 일반적으로 자체적으로 풀을 만들어 사용하면 유지보수성 낮아지고, 잘 최적화된 요즘의 가비지 컬렉터와 비교 시 성능 떨어진다.
  • 객체를 재사용하라는 것은 ‘방어적 복사’ 개념과 대비되는 내용이다. 방어적 복사가 필요한 상황에서 객체를 재사용해 얻게 되는 피해가 객체 반복 생성을 통해 얻게 되는 피해보다 훨씬 크다. 방어적 복사 실패 시 버그, 보안적 이슈 등의 피해를 볼 수 있다.
This post is licensed under younghwani by the author.

[Effective Java] Item05. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라!

[Effective Java] Item07. 다 쓴 객체 참조를 해제하라!

Comments powered by Disqus.