금요일, 3월 29
Shadow

#002 객체의 생성과 소멸 2번째

객체를 언제 어떻게 생성하는지, 언제 어떻게 생성을 피해야 하는지, 적합한 방법으로 소멸되는 것을 어떻게 보장하는지, 그리고 객체 소멸에 앞서 선행되어야 하는 클린업 작업을 어떻게 관리할 것인가에 대해 설명한다.
3. private 생성자나 enum 타입을 사용해서 싱글톤의 특성을 유지하자
싱글톤이란 정확히 하나의 인스턴스만 만들어지는 클래스이다.
싱글톤은 스레드 풀이라던가, 캐시 등등 객체가 전체 프로그램에서 오직 하나만 생성되어야 하는 경우에 사용한다.
싱글톤 구현 방법
1. public static final 멤버 필드 사용

// public final를 갖는 싱글톤
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() { }

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

2. static 팩토리 메소드 사용

// static 팩토리 메소드를 갖는 싱글톤
public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() { }
    public static Elvis getInstance() { return INSTANCE; }

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.getInstance();
        elvis.leaveTheBuilding();
    }
}

3. enum 타입을 사용
[장점] serialization 할 경우 2번 방식에서는 부가적인 작업을 해야만 하나의 객체가 유지되는데, enum 방식은 그럴 필요가 없다.

// Enum 싱글톤 - 가장 좋은 방법임
public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

4.private 생성자를 사용해서 인스턴스 생성을 못하게 하자
static메소드와 static 필드로만 이루어진 클래스를 만들어야 할 때가 있다.

예. java.lang.Math, java.util.Arrays, 직접 만든 유틸리티 클래스

유틸리티 클래스의 인스턴스는 아무런 의미가 없기 때문에 외부에서 인스턴스를 아예 만들지 못하게 하라.
[이유] 원래 의도와 다르게 기본 생성자를 사용해서 인스턴스를 생성하여 접근 할 수 있기 때문이다.

public class UtilityClass{ 
    // private생성자를 추가해서 컴파일러에서 자동으로 생성자를 추가하지 못하게 한다. 
    private UtilityClass(){ } 
}

5.불필요한 객체의 생성을 피하자
동등한 기능을 하는 객체를 필요할 때마다 생성하는 것보다 한 객체를 재사용 하는 것이 낫다.
불변 클래스(Immutable class)는 언제나 재사용될 수 있다.

String s = new String("바보"); // 호출될 때마다 새로운 인스턴스를 생성한다.
String s = "이젠 바보 아니야"; // 매번 새로운 인스턴스를 생성하지 않는다.

생성자, 스태틱 팩토리 메소드 모두 제공하는 불변 클래스가 있을 때, 스태틱 메소드를 사용하자.
[이유] 생성자와 달리 스태틱 팩토리 메소드는 새로운 객체를 생성하지 않으므로, 불필요한 객체 생성을 피할 수 있다.

Boolean(String)보다는 Boolean.valueOf(String)을 쓰는것이 낫다.

// java.lang.Boolean.java 일부 발췌
package java.lang;

public final class Boolean implements java.io.Serializable, Comparable
{
    public static final Boolean TRUE = new Boolean(true);

    public static final Boolean FALSE = new Boolean(false);

    private final boolean value;

    public Boolean(boolean value) {
	this.value = value;
    }

    public Boolean(String s) {
	this(toBoolean(s));
    }

    public static Boolean valueOf(String s) {
	return toBoolean(s) ? TRUE : FALSE;
    }

    private static boolean toBoolean(String name) { 
	return ((name != null) && name.equalsIgnoreCase("true"));
    }
}

6.쓸모 없는 객체 참조를 제거하자

// 어디서 "메모리 누출"이 생기는지 찾을 수 있는가?

import java.util.*;

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

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

    /**
     * 배열에 요소를 저장하는데 필요한 공간을 확인하고
     * 배열이 커질 필요가 있을 때는 그 크기를 2배로 늘린다.
     */
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

이 프로그램에는 특별히 잘못된 곳은 없으나 잠재된 문제점이 있다. 바로 메모리 누출(memory leak)이다.

메모리 누출이란? 가비지 컬렉션 되어야 하는데도 그 대상이 되지 못하는 객체들이 생기는 경우를 말한다.

메모리 누출로 인해, 최악의 경우 디스크상의 페이징(paging)이 생길 수 있으며, 흔하지 않지만 OutOfMemoryError로 인해 프로그램 실행이 중단될 수도 있다.
이 코드에서 문제가 발생한 곳은 바로 pop 메소드 이다.
Stack이 커졌다가 줄어들면, Stack에서 꺼냈던 객체들은 더 이상 참조되지 않더라도 가비지 컬렉션 되지 않는다.
왜냐하면, 쓸모 없는 참조를 Stack에서 여전히 가지고 있기 때문이다.

쓸모 없는 참조란? 객체에 대한 참조 값(null이 아닌)을 가지고 있지만, 다시는 사용되지 않을 참조를 말한다.

이 문제의 해결책은 쓸모 없는 참조를 null로 만드는 것이다.

 public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[--size] = null; // 쓸모 없는 참조를 제거한다.
        return result;
    }

객체 참조를 null로 변경하는 것은 꼭 필요할 때만 예외적으로 행해야 한다.
쓸모 없는 참조를 제거하는 가장 좋은 방법은 참조 값을 갖는 변수가 유효 범위(scope) 밖에 있도록 하는 것이다 (항목 45).
메모리 누출은 잘 드러나지 않으므로, 코드를 철저히 검사하거나 힙 프로파일러(heap profiler)와 같은 디버깅의 도움을 받아야만 원인을 찾을 수 있다.
미리 문제를 예상하고 발생을 막는 방법을 배우는 것이 바람직하다.

7 파이널라이저(finalizer)의 사용을 피하자

Finalizer는 신속히 실행된다는 보장이 없으며, OutOfMemory Error가 생기기도 한다.
Exception도 무시하며, 경고도 출력하지 않고, 상상을 초월하는 성능저하가 발생한다. (약 430배 실행시간 over)
Native method(외부 명시, 연동 목적 method)는 일반적인 java object가 아니므로, gc()가 동작하는지 안하는지 알 수 없다 때문에 finalizer를 일부 사용해 볼 수는 있다.

그 외에는 finalizer를 사용할 일이 없다.

C/C++과 같이 Java가 아닌 타 언어를 사용할 때, JNI(Java Native Interface)를 사용한다.
여기서 사용하는 Native method가 Native Method Stack에 올라간다.

출처: eBook – BACK TO THE BASIC, JAVA 핵심 요약 노트 : 빠르게 훑어보는 자바 프로그래밍 (javacafe회장 김흥래 저)
어쩔 수 없이 사용하는 경우, 상위 class의 finalize를 실행하지 않으므로 반드시 개발자가 super.finalize()를 해주어야 한다.

creative by javacafe

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.