본문 바로가기

Dev Book Review/Effective Java

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

1. 객체 재사용

똑같은 기능의 객체를 매번 사용하기 보다는 객체 하나를 재사용하는 편이 나을 때가 많다.

불변 객체는 언제든 재사용가능하다.

String s = new String("hello");	// Heap 영역에 존재
String s = "hello"; // String constant pool 영역에 존재 (Perm > Heap)

같은 JVM에서 이와 똑같은 문자열 리터럴을 사용하는 모든 코드가 같은 객체를 재 사용함이 보장된다.
string constant pool 영역에 있는지 검색 한 후에, String 객체를 재 사용한다. (== 비교 가능해진다.)

Q. string constant pool 영역이 Perm > Heap 일때 이점 [ Java7 ]

A. string constant pool의 모든 문자열도 GC 대상이 될 수 있다.
Perm 영역은 고정된 사이즈고 Runtime에 사이즈가 확장되지 않는다. 따라서 string.intern 메서드 호출은 OutofMemoryException을 발생시킬 수 있었다.

참고 : https://medium.com/@joongwon/string-%EC%9D%98-%EB%A9%94%EB%AA%A8%EB%A6%AC%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0-57af94cbb6bc
 

Java String 의 메모리에 대한 고찰

Java 언어에서 String은 무심코 사용되는 클래스 중에 하나가 아닐까 생각이 든다. String은 두 가지 생성 방식이 있고 각각의 차이점이 존재한다.

medium.com

 

2. 정적 팩터리 메서드를 제공하는 불변 클래스

Boolean(String); // deprecated
Boolean.valueOf(String); // 권장

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

 

3. 생성 비용이 비싼 객체라면 캐싱하여 재사용

// 코드 6-1 성능을 훨씬 더 끌어올릴 수 있다!
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})$");
}

// 코드 6-2 값비싼 객체를 재사용해 성능을 개선한다.
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();
}

String.matches() : 성능이 중요한 상황에서 반복해 사용하기 적합하지않다.
정규 표현식용 Pattern 인스턴스를 한번 쓰고 버려져서 GC 대상이 되기 때문

성능 개선을 위해 정규 표현식을 표현하는 (불변) Pattern 인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 생성해 초기화해두고, 이 Pattern 인스턴스를 재사용한다.

지연초기화 : 불필요한 초기화를 없앨 수 있으나 권하지않는다.
코드를 복잡하게 만드는데 성능은 크게 개선되지 않을 때가 많기 때문

 

4. 어댑터 패턴 사용

어댑터(뷰) = 실제 작업은 뒷단 객체에 위임하고, 자신은 제2 인터페이스 역할을 해주는 객체어댑터는 뒷단 객체만 관리한다. 뒷단 객체 하나당 어댑터 하나

Map 인터페이스의 KeySet 메서드 : Map 객체안의 키를 전부 담은 Set 뷰 (어댑터)를 반환

뷰 객체를 여러개 만들거라 생각할 수 있지만, 사실은 매번 같은 인스턴스를 반환한다.

@DisplayName("keyset은 같은 Map을 바라본다")
@Test
void keyset(){
  Map<String, Object> javabom = new HashMap<>();
  javabom.put("Javabom", "Hello");

  Set<String> javabomSet1 = javabom.keySet();
  Set<String> javabomSet2 = javabom.keySet();

  assertThat(javabomSet1).isSameAs(javabomSet2);
}

 

5. 오토박싱(auto boxing)

오토박싱 : 기본타입과 그에 대응하는 박싱된 기본타입의 구분을 흐려주지만 완전히 없애주진 않는다.
성능에서 좋아진다 볼 수 없다.

private static long sum(){
  Long sum = 0L;	
  for(long i =0; i <= Integer.MAX_VALUE; i++){
    sum += i;	// 불필요한 Long 인스턴스가 만들어진다.
  }
  return sum;
}

박싱된 기본타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않게 주의하자.

 

6. 본인만의 객체 풀(pool)을 만들지 말자

가벼운 객체를 다룰때는 직접 만든 객체 풀보다 훨씬 빠르다. (JVM GC가 잘 되있다.)데이터베이스 연결 - 생성비용이 워낙 비싸니 재사용이 낫다.

아이템 50 : 새로운 객체를 만들어야 한다면 기존 객체를 재사용하지 마라 : 방어적 복사 -> 버그와 보안으로 이어짐
아이템 6 : 불필요한 객체 생성을 피하라 -> 코드형태 성능에 영향