public static Item1 of(String name, String email){
return Item1.builder()
.name(name)
.email(email)
.build();
}
public static Boolean valueOf(boolean b){
return b ? Boolean.True : Boolean False;
}
말 그대로 생성자에 매개변수가 많으면 Builder사용을 권장한다.
설계상 유일해야하는 컴포넌트를 보통 싱글턴으로 만든다.
Effective Java에선 원소가 하나인 열거 타입을 선언할 때Enum을 통해 선언하는 것을 추천한다.
- 컴파일러가 기본 생성자를 만드는 경우는 명시된 생성자가 없을 시점이기 때문에
private생성자를 추가하면 클래스의 인스턴스화를 방지할 수 있다.
- 클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다.
- 필요한 자원을 생성자에 넘겨주자
- 의존 객체 주입이라 하는 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 개선한다.
- 똑같은 기능의 객체를 생성하기 보다 객체 하나를 재사용하는 것이 낫다.
Java에서 메모리 누수가 발생할 수 있는 위치가 여럿 존재한다.
GC에서 관리해 주긴 하지만 모두 완전히 관리하지는 못한다.
다음의 예시를 본다.
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];
}
/**
* 원소를 위한 공간을 적어도 하나 이상 확보한다.
* 배열 크기를 늘려야 할 때마다 대략 두 배씩 늘린다.
**/
private void ensureCapacity() {
if (this.elements.length == size) {
this.elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
위 코드를 보면 스택의 사이트가 커졌다 줄어들 때 스택에서 꺼내진 객체들을 GC가 회수하지 않는다.
pop을 해도 스택에서 여전히 사용이 끝난 참조 값을 가지고 있기 때문이다.
여기서 문제는 다 쓴 레퍼런스 값의 메모리 누수 외에도 해당 객체가 참조하고 있는 모든 객체를 GC가 회수할 수 없어 추가적인 메모리 누수가 발생한다.
해당 문제를 해결하기 위해 null을 사용하면 된다.
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 다 쓴 참조 해제
return result;
}
하지만 이러한 방법은 바람직 하지 않다.(코드마다 일일히 null 처리하는 것은 코드상 더럽기도 하고 번거롭다.)
참조를 담은 변수를 유효 범위(scope) 밖으로 밀어내는 것이 제일 바람직하다
캐시 또한 메모리 누수를 일으키는 주범이다.
객체 참조를 캐시에 넣어 캐시를 넣고 캐시를 비우는 것을 잊기 쉽다.
- WeakHashMap을 사용해 캐시를 만들자.
특정 key값이 사용되지 않는다고 판단되면 해당 key-value를 삭제한다.
캐시의 key에 대한 참조가 캐시 밖에서 필요 없어지면 캐시를 자동으로 비워주는 역할- 새로운 엔트리를 추가할 때 부가적인 작업으로 캐시를 비우는 방법
캐시를 만들 때 보통 캐시 엔트리의 유효 기간을 정확히 정의하기 어렵기 때문에 시간이 지날수록 엔트리의 가치를 떨어뜨리는 방식을 사용한다.LinkedHashMap의removeEldestEntry()를 사용하자.
클라이언트가 콜백을 등록만 하고 명확히 해지하지 않는다면 콜백은 계속 쌓여만 간다.
이럴 때 콜백을 약한 참조로 저장하면 GC가 즉시 수거해간다.
약한 참조로 넣어 GC의 수거대상이 되도록 한다.
- 직접 null을 넣어주는 방법(db connection pool에 null 할당)
- 콜백을
WeakHashMap에 저장하여 메모리를 더이상 사용하지 않는다면 GC에 수거되도록 할 수 있다.
Finalizer는 예측 불가능하고 대부분 불필요하다.
Finalizer과 cleaner의 단점은 다음과 같다.
- 언제 실행되는지 알 수 없다.
- 인스턴스 반납을 지연시킬 수 있다.
- finalizer이나 cleaner의 수행 시점 뿐 아니라 수행 여부조차 보장하지 않는다.
- 심각한 성능 문제
- finalizer공격에 노출되어 보안 문제를 일으킬 수 있다.
그렇다면 finalizer와 cleaner를 대신할 방법은 다음과 같다.
자원 반납이 필요한 클래스AutoCloseable인터페이스를 구현하고try-with-resource를 사용하거나, 클라이언트에서 인스턴스를 다 쓰면close를 호출하면 된다.
자바는 close()를 호출해 직접 닫아줘야 하는 자원이 많이 있다.
이러한 자원 닫기는 클라이언트가 놓치기 쉬워 예측할 수 없는 성능 문제로 이어지기도 한다.
전통적으로 자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰였다.
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
이 때 readLine()에서 예외가 발생하면 br.close()가 실패한다.
이 경우 br.close()에서 발생한 에러로 표시되므로 궁극적으로 어느 부분에서 발생한 에러인지 파악하기 어렵다.
이러한 문제를 해결하고자 try-with-resource가 Java 7에 등장하였다.
이 구조를 사용하려면 AutoCloseable 인터페이스를 구현해야 한다.
static String firstLineOfFile(String path, String defaultval) {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
} catch (IOException e) {
return defaultVal;
}
}
위 코드처럼 try-with-resources에서 catch도 사용할 수 있다.
- 꼭 회수해야 하는 자원을 다룰 때는
try-finally보다는try-with-resources를 사용하자- 코드는 더 짧고 간결해지며, 만들엊지는 예외 정보도 훨씬 유용하게 된다.
try-finally로 작성하면 실용적이지 못할 만큼 코드가 지저분해지는 경우라도,try-with-resources로는 정확하고 쉽게 자원을 회수할 수 있다.