아이템6. 불필요한 객체 생성을 피하라
불필요한 객체 생성 : new String() 사용
String name = new String("LHS"); // 매번 인스턴스를 생성
String name2 = new String("LHS"); // 같은 문자열이지만, 위 name과 참조하는 주소가 다르다
name, name2 둘 다 "LHS"라는 문자열을 가지게 된다.
하지만 new String()을 이용하여 만든 name, name2는 같은 "LHS"이지만, 두 문자열이 참조하는 주소는 모두 다르기 때문에 동일한 데이터에 대해 서로 다른 메로리를 할당한다는 낭비가 발생한다.
String name3 = "LHS"; // 상수 Pool을 사용
String name4 = "LHS"; // 상수 Pool을 사용
그래서 문자열을 선언할 때는 new 키워드를 쓰는 것이 아닌 리터럴로 선언해야 한다.
위 코드의 name3, name4는 하나의 인스턴스만 사용한다. 즉 "LHS" 문자열 리터럴을 사용하는 모든 코드가 같은 객체를 재사용함을 보장한다.
이것은 Java 상수 풀의 특징 때문에 그렇다.
불필요한 객체 생성 : String.matches() 사용
static boolean isRomanNumeral(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
해당 방식은 String.matches 메서드를 사용한다는 것이 문제다.
이 메서드가 내부에서 만드는 정규 표현식용 Pattern 인스턴스는 매번 생성되기 때문에 GC에 쌓이게 된다.
성능을 개선하기 위해 정규 표현식을 표현하는 불변 Pattern 인스턴스 클래스 초기화 과정에서 직접 캐싱해두고 재사용하는 것이 바람직하다.
public class RomanNumerals {
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 인스턴스를 끄집어 낼 수 있다.
isRomanNumeral이 초기화된 후 한번도 호출되지 않는다면 낭비가 된다. 호출이 된 후에 초기화를 하는 lazy 로딩 방식이 존재하지만 권하지는 않는다.
주의 사항
위의 모든 예시에서 불필요한 객체를 캐싱할 때 모두 불변 객체로 만들었다. 그래야 재사용시에도 안전하기 때문이다. 하지만 불변 객체로 재사용을 한다는 직관에 반대되는 경우도 있다.
어댑터(뷰)는 실제 작업은 뒷단 객체에 위임하고, 자신은 제2의 인터페이스 역할을 해 주는 객체이다. 어댑터는 뒷단 객체만 관리하면 되므로 뒷단 객체 하나당 어댑터 하나씩만 만들어 주면 된다.
예를 들어 Map 인터페이스의 keySet 메서드는 키 전부를 담은 뷰를 반환한다. 해당 메서드를 호출할 때마다 새로운 Set 인스턴스가 생성될 것이라 생각할 수 있겠지만, 사실은 매번 같은 Set 인스턴스를 반환할지도 모른다.
오토 박싱
불필요한 객체를 만들어내는 예로 오토 박싱이 있다. 오토 박싱은 프로그래머가 기본 타입과 박싱된 기본 타입을 섞어서 쓸 때 자동으로 상호 변환해주는 기술이다.
오토 박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐려주지만, 완전히 없애주는 것은 아니다.
public static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
}
로직상 문제는 없지만, 성능적으로는 매우 비효율적인 코드다. 원인은 바로 sum의 타입과 for문 내에 있는 i의 타입이다.
sum의 타입은 Long 타입이고, i는 long 타입이다. 즉, long 타입인 i는 반복문을 돌면서 sum에 더해질 때마다 새로운 Long 인스턴스를 만들게 된다.
결과적으로 래퍼 타입보다는 기본 타입을 사용하고 의도치 않은 오토 박싱이 사용되지 않도록 주의해야 한다.
오해 금지!
불필요한 객체 생성을 피하라는 것을 단순하게 객체 생성의 비용이 크니까 피해야 한다.로 오해하면 안 된다.
특히 요즘 JVM에서 불필요하게 생성된 작은 객체를 생성 및 회수하는 일은 별로 부담되는 작업이 아니다. 그러므로 데이터베이스 연결과 같이 비용이 매우 큰 객체가 아니라면, 커스텀 객체 풀을 만들지 말자.
더 나아가, 방어적 복사가 필요한 상황에서 객체를 재사용할 때의 피해가, 필요 없는 객체를 반복 생성했을 때의 피해보다 훨씬 크다는 사실을 기억하자. 반복 생성의 부작용은 코드 형태와 성능에만 영향을 주지만, 방어적 복사가 실패하면 버그와 보안 문제로 직행한다.
참조 블로그 문서
'도서 > 정리' 카테고리의 다른 글
[이펙티브 자바] 아이템9. try-finally보다는 try-with-resources를사용하라 (0) | 2023.07.04 |
---|---|
[이펙티브 자바] 아이템7. 다 쓴 객체 참조를 해제하라 (0) | 2023.05.17 |
[이펙티브 자바] 아이템5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2023.05.09 |
[이펙티브 자바] 아이템4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (1) | 2023.05.07 |
[이펙티브 자바] 아이템3. private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2023.05.05 |