본문 바로가기
Language/Effective java

객체 생성과 파괴

by y.j 2022. 6. 1.
728x90

불팔요한 객체 생성을 피하라

똑같은 기능의 객체를 매번 생성하기 보다는 하나를 사용해서 재 사용 하는 것이 성능면에서나 메모리 관리면에서 더 좋다.

String s = new String("bikini");      // 잘못된 사용
String s = "bikini";                  // 옳은 사용

위에 코드처럼 작성하게 되면 반복문일 경우 객체가 계속해서 생성된다. 책에서도 Boolean(String)을 쓰지말고, Boolean.valueOf(String)을 사용하라고 나와 있다.

@HotSpotIntrinsicCandidate
public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

java코드를 보면 static으로 하나의 메서드만 생성해서 사용하도록 하고 있다. 정적 팩토리 메서드를 사용하게 되면 불필요한 객체의 생성을 막아주고 재사용성을 높여준다. 또한, 생성비용이 높은 객체를 캐싱하여 효율성있게 사용 할 수 있다.


첫 번째 코드는 isRomanNumeral를 콜 할 때마다 메모리에 로드하는 과정과 유한 상태 머신을 만드는 과정을 동반하기 때문에 인스턴스 생성 비용이 높다. 

private 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})$");                   
} 

이 코드는 캐싱이 되어 있기 때문에 인스턴스를 재사용 한다.

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})$"
    );

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

어댑터 패턴의 경우 어떻게 될까? 어댑터 패턴도 뒷단 객체 외에는 관리 할 상태가 없으므로 뒷단 객체 하나당 어댑터 하나씩만 만들어지면 충분하다.

다른 예로는 오토박싱이 있다. 오토박싱은 기본 타입과 그에 대응하는 박싱된 기본타입의 맞춰주는 것이다. 


Long은 객체이므로 for문 만큼 객체가 생성된다. 값은 바르게 나오지만 성능에 큰 문제를 일으킨다.

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

하지만, 객체 생성이 무조건 나쁜 것은 아니다. 작은 객체를 생성하고 회수하는 것은 JVM에 크게 부담이 되지 않는다. 프로그램의 명확성, 간결성, 기능을 위해서 객체를 추가로 생성하는 것이라면 일반적으로 옳은 일이다.

 

다 쓴 객체 참조를 해체하라.

가비지 컬렉터에서 메모리를 잘 관리해주고 있지만 신경쓰지 않아도 되는 것은 아니다.


스택 예

public class Stack {
    
    private Object[] element;
    private int size = ...;

    ...
    
    
    public Object pop() {
        if(size == 0)
            throw new EmptyStackException();
        return element[--size];
    }
}

pop함수는 자체의 결과물은 문제가 없지만 메모리 누수가 발생한다. 왜냐하면 element[size]는 size가 줄었어도 가비지 컬렉터에서 회수하지 않는다. 

public class Stack {

    private Object[] element;
    private int size = ...;

    ...

    public Object pop() {
        if(size == 0)
            throw new EmptyStackException();
        Object result = element[--size];
        element[size] = null;
        return result;
    }
}

null값을 넣어주면 참조가 해제 되어진다.


하지만, 다 쓴 객체에 모두 null처리하는 것은 예외적인 경우이다. Stack클래스의 경우에는 직접 메모리를 관리하기 때문에 가비지 컬렉터에서 비활성 영역을 알 길이 없다. 따라서, 메모리를 직접 관리하는 클래스에서 메모리 누수를 주의해야 한다.

 

캐시 역시 메모리 누구를 일으키는 주범이다. 객체 참조를 캐시에 넣어놓고 까맣게 잊는 경우가 있다. 따라서 캐시를 한번씩 청소해주어야 한다.

 

리스너 혹은 콜백도 메모리 누수를 일으킨다. 클라이언트가 콜백을 등록만 하고 명확히 해지하지 않는다면, 계속 쌓인다. 콜백은 약한 참조로 저장하면 가비지 컬렉터가 즉시 수거해간다.

728x90

'Language > Effective java' 카테고리의 다른 글

모든 객체의 공통 메서드  (0) 2022.06.06
객체 생성과 파괴  (0) 2022.06.05
객체 생성과 파괴  (0) 2022.06.01
객체 생성과 파괴  (0) 2022.05.22
객체 생성과 파괴  (0) 2022.05.09

댓글