똑같은 기능의 객체를 매번생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다.
다음 코드는 하지 말아야 할 극단적인 예시다.
String s = new String("bikini"); // 따라 하지 말 것!
이 문장은 실행될 때마다 String 인스턴스를 새로 만든다. 완전히 쓸데 없는 행위다.
생성자에 넘겨진 "bikini" 자체가 이 생성자로 만들어내려는 String과 기능적으로 완전히 똑같다.
이 문장이 반복문이나 빈번히 호출되는 메서드 안에 있다면 쓸데없는 String 인스턴스가 수백만 개 만들어 진다.
String s = "bikini";
상수 문자열을 대입하면 하나의 인스턴스가 생성되지만 JVM의 Heap 내부 Constant Pool에 저장된다.
이후 똑같은 문자열 리터럴을 사용하는 코드들은 String 인스턴스를 새로 생성하지 않고, Constant Pool에 저장된 인스턴스를 재사용한다.
String.matches는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서는
반복해 사용하기엔 적합하지 않다.
String의matches()메서드는 내부에서 정규표현식용 Pattern 인스턴스를 생성하지만,
한 번 쓰이고 버려지기 때문에 GC의 대상이 된다. 문제는 Pattern이 입력받은 정규표현식에 해당하는 유한 상태 머신을 만들기 때문에 생성 비용이 높다.
따라서 정규표현식으로 문자열을 비교할 때 Pattern 인스턴스를 캐싱해두고 재사용하는 것이 성능상 유리하다.
2. 직관적이지 않은 경우
생성자가 아닌 정적 팩토리 메서드를 사용하면 캐싱된 객체를 참조할 수 있기 때문에 불필요한 객체 생성을 피할 수 있다. 불변 객체는 재사용해도 안전하지만 덜 명확하거나 직관적이지 않은 경우가 있다.
- Map의keySet()메서드는 매번 같은 Set 인스턴스를 반환한다.
- 의도하지 않은 오토박싱(Auto Boxing).
- 기본 타입과 박싱된 기본 타입간의 자동 상호 변환을 해주는 기술이다.
- 박싱된 기본 타입 인스턴스(Long, Integer)보다는 기본 타입(long, int)을 사용하는 것이 성능상 유리하다.
DB 연결같이 생성 비용이 비싼 객체는 별도의 Pool을 관리하여 재사용하는 것이 유리하다. 그러나 무겁지 않은 객체들을 재사용하기 위해 자체 객체 풀을 만들 필요는 없다.
- JVM과 GC가 잘 최적화되어 있기 때문에 작은 객체를 생성하고 회수하는 것은 큰 부담이 되지 않는다.
- 오히려 자체 객체 풀이 메모리 사용량을 늘려 성능 저하로 이어진다.
Item 50. 방어적 복사(Defensive Copy)와 대조적인 개념이니 함께 참조하자.