네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'전체 글'에 해당되는 글 194건


반응형

finalizer와 cleaner 사용을 피해라

## 1. 자바의 객체 소멸자

- finalizer: 예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요하다. 오동작, 낮은 성능, 이식성 문제의 원인이다. **"쓰지 말자"** 자바 9에서는 사용 자제(deprecated) API로 지정

- cleaner: finalizer보단 덜 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요하다.

 

* C++의 파괴자(destructor): 특정 객체와 관련된 자원을 회수하는 보편적인 방법 (Java의 try-with-resources, try-finally)

* Java의 가비지 컬렉터: 접근할 수 없게된 객체를 회수

 

## 2. finalizer와 cleaner 사용을 피해야 하는 이유

### 1) 즉시 수행된다는 보장이 없다.

객체에 접근할 수 없게 된 이후부터 실행되기까지 얼마나 걸릴지 알 수 없다. 제때 실행되어야 하는 작업은 절대 할 수 없다.   

수행 시점이 전적으로 GC 알고리즘에 달렸으며, 구현 방식에 따라 천차만별이다.     

finalizer 쓰레드는 다른 애플리케이션보다 우선순위가 낮다. 

 

### 2) 수행 여부도 보장하지 않는다.

접근할 수 없는 객체에 딸린 종료작업을 수행하지 못한 채 프로그램이 중단될 수도 있다. 

상태를 영구적으로 수정하는 작업에서 절대 finalizer나 cleaner에 의존해서는 안된다.   

System.gc, System.runFinalization 메서드에 현혹되지 말자. 실행 가능성은 높여주나 보장해주진 않는다.     

 

### 3) finalizer 동작 중 발생한 예외가 무시되며, 처리할 작업이 남아있어도 그 순간 종료된다.

잡지 못한 예외로 객체가 덜 마무리된 상태로 남아있을 수 있다. 훼손된 객체를 사용하려 할 때 예측할 수 없다.   

 

### 4) finalizer와 cleaner은 심각한 성능문제도 동반한다.

AutoCloseable 객체를 생성해 GC 수거시간: 12ns   

finalizer 수거시간: 550ns -> GC의 효율을 떨어뜨린다.    

 

### 5) finalizer 공격에 노출되어 심각한 보안 문제를 일으킬 수도 있다.

생성자나 직렬화 과정에서 예외가 발생하면 생성되다 만 객체에서 악의적인 하위 클래스의 finalizer가 수행될 수 있다.    

객체 생성을 막으려면 생성자에서 예외를 던지면 되는데, finalizer가 있으면 그렇지도 않다.     

final이 아닌 클래스를 finalizer 공격으로부터 방어하려면 아무 일도 하지 않는 finalize 메서드를 만들고 final로 선언하자.

 

## 3. finalizer, cleaner의 대안

AutoClosable을 구현하고, 클라이언트에서 인스턴스를 다 쓰면 close 메서드를 호출한다. 예외가 발생해도 잘 종료되도록 try-with-resources를 사용한다.

 

## 4. finalizer, cleaner의 쓰임새

### 1) 자원의 소유자가 close메서드를 호출하지 않는 것에 대비한 안전망 역할

안전망 역할의 finalizer를 작성할 때는 그럴만한 값어치가 있는지 심사숙고하자.    

자바 라이브러리의 일부 클래스는 안전망 역할의 finalizer를 제공한다: FileInputStream, FileOutputStream, ThreadPoolExecutor   

 

### 2) 네이티브 피어와 연결

네이티브 피어(native peer): 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체. 자바 객체가 아니라 가비지 컬렉터는 그 존재를 알지 못한다.      

성능 저하를 감당할 수 있고 네이티브 피어가 심각한 자원을 가지고 있지 않을 때에만 해당된다. 성능 저하를 감당할 수 없거나 네이티브 피어가 사용하는 자원을 즉시 회수해야 한다면 close 메서드를 사용하자.   

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

# 다 쓴 객체 참조를 해제하라

Java의 장점 중 하나는 가비지 컬렉션을 지원하는 언어라는 점  

하지만, 가비지 컬렉션을 통해 소멸 대상이 되는 객체가 되기 위해서는 어떠한 reference 변수에서 가르키지 않아야 한다. 다 쓴 객체에 대한 참조를 해제하지 않으면 가비지 컬렉션의 대상이 되지 않아 계속 메모리가 할당되는 메모리 누수 현상이 발생된다.    

가비지 컬렉션을 지원하는 언어에서는 메모리 누수를 찾기가 까다롭다. 객체 참조(reference)를 하나 살려두면, 가비지 컬렉터는 그 객체뿐만 아니라 그 객체 내에서 참조하고 있는 객체까지 회수할 수 없다.   

 

## 1. 직접 할당 해제

- 객체 참조 변수를 null로 초기화한다.

- 실제 Heap 메모리에 존재하는 객체는 어떤 참조(reference)도 가지지 않기 때문에 가비지 컬렉션의 소멸 대상이 된다.

- 클래스 내에서 메모리를 관리하는 객체라면 이 방법을 통해 다 쓴 객체는 할당을 해제하는 것이 옳다.

 

## 2. Scope을 통한 자동 할당 해제

- 보통은 변수 선언과 동시에 초기화를 사용한다. 그 변수에 대한 scope가 종료되는 순간 reference가 해제되어 가비지 컬렉션의 대상이 된다.

- try-catch 와 같은 구문에서는 finally 구문에서 변수에 대한 참조를 해제한다.

 

## 메모리 누수의 주범

1. 자기 메모리를 직접 관리하는 클래스

- 클래스 내에서 인스턴스에 대한 참조(reference)를 관리하는 객체

- 프로그래머가 항시 메모리 누수에 주의한다.

 

2. 캐시 (Map)

- 캐시에 객체 참조를 넣고, 객체를 다 쓴 후에도 놔두는 경우

- 해결책)

    - WeakHashMap을 이용해 캐시를 만든다. 외부에서 키를 참조하는 동안만 엔트리가 살아있는 캐시

    - 시간이 지날수록 엔트리 가치를 떨어뜨리는 방식 -> ScheduledThreadPoolExecutor(쓰지 않는 엔트리 청소)

 

3. 리스너(listener), 콜백(callback)

- 클라이언트가 콜백만 등록하고 해지하지 않는 경우, 계속 쌓인다.

- 해결책)

    - 콜백을 약한 참조(weak reference)로 저장한다. WeakHashMap에 키로 저장한다.

 

## Java Reference

### GC의 reachability

- reachable: 어떤 객체에 유효한 참조가 있다. (root set: 유효한 최초의 참조)

- unreachable: 어떤 객체에 유효한 참조가 없다. (GC 대상)

 

### java.lang.ref 패키지의 객체 참조 종류 4가지

1. Strong Reference

- 우리가 흔히 사용하는 참조

- String str = new String("hello"); 와 같은 형태

- Strong Reference는 GC의 대상이 아니다.

- Strong Reference 관계의 객체가 GC의 대상이 되기 위해서 null로 초기화해 객체에 대한 reachability 상태를 unreachable 상태로 만들어줘야 한다.

 

2. Soft Reference

- 객체의 reachability가 strongly reachable 객체가 아닌 객체 중 Soft Reference만 있는 상태

- SoftReference<Class> ref = new SoftReference<>(new String("hello")); 와 같은 형태

- Soft Reference는 대게 GC대상이 아니다가 out of memory 에러가 나기 직전에 Soft Reference 관계에 있는 객체들은 GC 대상이 된다.

 

3. Weak Reference

- 객체의 reachability가 strongly reachable 객체가 아닌 객체 중 Soft Reference가 없고 Weak Reference만 있는 상태

- WeakReference<Class> ref = new WeakReference<Class>(new String("hello")); 와 같은 형태

- WeakReference는 GC 동작마다 회수된다.

- WeakReference 객체 내의 weakly reachable 객체에 대한 참조가 null로 설정되면, GC에 의해 메모리 회수

WeakReference<Sample> wr = new WeakReference<Sample>(new Sample());
Sample ex = wr.get(); // 참조
ex = null; // weakly reachable 객체 = null -> 메모리 회수

 

 

4. Phantomly Reference

- 객체의 reachability가 strongly reachable 객체가 아닌 객체 중 Soft Reference와 Weak Referencerk 모두 해당되지 않는 객체

- finalize 되었지만 메모리가 아직 회수 되지 않은 객체

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

# 불필요한 객체 생성을 피하라

 

## 1. 객체 재사용

똑같은 기능의 객체를 매번 생성하는 것보다 객체 하나를 생성해서 재사용하는 편이 좋다. 불변 객체는 언제든지 재사용할 수 있다.     

// bad example - 호출될 때마다 새로운 인스턴스 생성
String s = new String("bad example"); // Heap 영역에 존재

// good example - 하나의 인스턴스를 사용
String s = "good example"; // String constant pool 영역에 존재 (Perm > Heap)

- 같은 JVM에서 똑같은 문자열 리터럴을 사용하는 경우 모든 코드가 같은 객체를 재사용하는 것이 보장된다.      

- String constant pool 영역에 있는지 검색 후, String 객체를 재사용한다. (== 연산자로 비교 가능 ! )

 

## 2. 정적 팩터리 메서드를 제공하는 불변 클래스

public Boolean(String s) { // 생성자 - Java9에서 deprecated
    this(parseBoolean(s));
}

public static Boolean valueOf(String s) { // 정적 팩터리 메서드
    return parseBoolean(s) ? TRUE : FALSE;
}
boolean b = new Boolean("false");
boolean b = Boolean.valueOf("true");

- 생성자는 매번 새로운 객체를 생성하지만, 팩터리 메서드는 그렇지 않으므로 Boolean(String s) 생성자 대신 Boolean.valueOf(String s) 팩터리 메서드를 사용하는 것이 좋다.

 

## 3. 생성 비용이 비싼 객체라면 캐싱하여 재사용

// 정규 표현식을 활용해 유효한 로마 숫자인지 확인하는 메서드
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() 메서드를 사용

public boolean matches(String regex) {
    return Pattern.matches(regex, this);
}

- String.matches 메서드 내부에서 만드는 정규표현식용 Pattern 인스턴스는 한 번 쓰고 버려져 곧 바로 가비지 컬렉션의 대상이 된다.

- Pattern은 입력받은 정규 표현식에 해당하는 유한 상태 머신(finite state machine)을 만들어 인스턴스 생성 비용이 높다.

- 해결) 생성 비용이 많이 드는 객체가 반복해서 필요하다면, 캐싱하여 재사용하는 것을 권장한다.

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 isRomanNumeralFast(String s) {
        return ROMAN.matcher(s).matches();
    }
}

- 클래스가 초기화된 후 이 메서드를 한 번도 호출하지 않는다면, ROMAN 필드는 필요없이 초기화된 것이다. lazy initialization으로, 지연 초기화는 코드를 복잡하게 만드는데, 성능은 크게 개선되지 않을 때가 많으므로 권하지 않는다.

 

## 4. 어댑터 패턴 사용

- Map 인터페이스의 keySet 메서드는 Map 객체안의 키를 전부 담은 Set 뷰를 반환한다.

- 뷰 객체를 여러개 만드는 것이 아니라 매번 같은 인스턴스를 반환한다. 

```java

@DisplayName("keyset은 같은 Map을 바라본다.")
@Test
void keyset() {

    Map<String, Object> map = new HashMap<>();
    map.put("Effective Java", "Hello");

    Set<String> Set1 = map.keySet();
    Set<String> Set2 = map.keySet();
    assertThat(Set1).isSameAs(Set2);
}

 

## 5. 오토박싱(autoboxing)

- auto boxing은 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해주는 기술이다. 

- 기본타입과 그에 대응하는 박싱된 기본타입의 구분을 흐려주지만 완전히 없애주진 않는다. 성능에서 좋아진다 볼 수 없다.

private static long sum(){

    Long sum = 0L;

    for (long i = 0; i< Integer.MAX_VALUE; i++) {
        sum += i;
    }

    return sum;
}

- sum 변수를 long이 아닌 Long으로 선언하여 불필요한 인스턴스가 sum += i 연산이 이루어질 때마다 생성되는 것이다. 단순히 sum 타입을 long으로 변경해주어도 성능이 개선된다.

- 박싱된 기본타입보다 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의해야 한다.

 

## 6. 본인만의 객체 풀(pool)을 만들지 말자

- 가벼운 객체를 다룰때는 직접 만든 객체 풀보다 훨씬 빠르다. (JVM GC) 데이터베이스 연결하는데 생성비용이 워낙 비싸니 재사용이 낫다.

- 방어적 복사가 필요한 상황에서 객체를 재사용했을 때 피해가 필요없는 객체를 반복 생성했을 때 피해보다 훨씬 크다.

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

# 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

# Dependency Injection

많은 클래스가 하나 이상의 자원에 의존한다. 이때 사용하는 자원에 따라 동작이 달라지는 클래스(하나 이상의 자원에 의존)에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.    

**인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식을 사용하면, 클래스가 여러 자원 인스턴스를 지원하고, 클라이언트가 원하는 자원을 사용할 수 있다.**    

=> **의존 객체 주입**의 한 형태로 유연성과 재사용성, 테스트 용이성을 높였다.    

 

### Dependency Injection

- 의존적인 객체를 직접 생성하거나 제어하는 것이 아니라, 특정 객체에 필요한 객체를 외부에서 결정해서 연결시키는 것을 의미

public class SpellChecker {

    private final Lexicon dictionary;

    // dependency injection
    public SpellChecker(Lexicon dictionary) {
        this.dictionary = Objects.requireNonNull(dictionary); // null이면 NPE 아닌경우 objects 반환
    }

    public boolean isValid(String word) { ... }
    public List<String> suggestions(String typo) { ... }
}

interface Lexicon { ... }
public class MyDictionary implements Lexicon { ... }
Lexicon dic = new MyDictionary();
SpellChecker checker = new SpellChecker(dic);

checker.isValid(word);

 

- **불변을 보장**하여 여러 클라이언트가 의존 객체를 사용할 수 있다.

- 의존 객체 주입은 생성자, 정적 팩터리, Builder 모두 똑같이 적용할 수 있다.

 

## 생성자에 자원을 넘겨주는 방식

### Factory

- 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체를 말하는데, Factory Method Pattern은 의존 객체 주입 패턴을 응용해서 구현한 것이다.

 

### Supplier<T> 인터페이스

@FunctionalInterface

public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

- 팩터리를 표현한 완벽한 예시, 이 방식을 사용하여 클라이언트는 자신이 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩터리를 만들 수 있다.

Mosaic create(Supplier<? extends Tile> tileFactory) { ... }

 

 

- 한정적 와일드 카드 타입(bounded wildcard type)으로 팩터리 타입 매개변수를 제한한다. (ITypeFactory.class)

public class IType {

    private static final int TYPE_Z = 0;
    private static final int TYPE_A = 1;
    private static final int TYPE_B = 2;

    final static Map<Integer, Supplier<? extends ITypeFactory>> map = new HashMap<>();
    static {
        map.put(TYPE_Z, ITypeFactory::new);
        map.put(TYPE_A, A::new);
        map.put(TYPE_B, B::new);
    }
}

class ITypeFactory {}
class A extends ITypeFactory {}
class B extends ITypeFactory {}

 

### 의존객체 주입 프레임워크

- 대거(Dagger), 주스(Guice), 스프링(Spring)

- 의존 객체 주입이 유연성과 테스트 용이성을 개선해주지만, 의존성이 너무 많은 프로젝트에서는 코드를 어지럽게 하며, 의존 객체 주입 프레임워크를 사용해 코드의 어지러움을 해소할 수 있다.

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

# 인스턴스화를 막으려거든 private 생성자를 사용하라 - Private Constructor

 

## 1. 정적 메서드와 정적 필드만을 담은 클래스의 쓰임새

1. 기본 타입 값이나 배열 관련 메서드를 모아 둘 때 (java.lang.Math, java.util.Arrays)

2. 특정 인터페이스를 구현한 객체를 생성해주는 정적 메서드(팩터리)를 모아둘 때 (java.util.Collections)

3. final 클래스와 관련한 메서드: final 클래스를 상속해서 하위 클래스에 메서드를 넣는 것은 불가능하므로, final 클래스와 관련 메서드들을 모아놓을 때도 사용

public class Arrays {

    /**
     * The minimum array length below which a parallel sorting
     * algorithm will not further partition the sorting task. Using
     * smaller sizes typically results in memory contention across
     * tasks that makes parallel speedups unlikely.
     */
    private static final int MIN_ARRAY_SORT_GRAN = 1 << 13;

    // Suppresses default constructor, ensuring non-instantiability.
    private Arrays() {}
}

 

 

## 2. 인스턴스화를 막는 방법

정적 멤버만 담을 때는 인스턴스로 쓰려고 설계한 것이 아님    

- 문제) 생성자를 명시하지 않을 때 자동으로 기본 생성자가 만들어짐.    

- 해결) private 생성자를 추가해서 클래스의 인스턴스화를 막는다.

- 상속이 불가능 (하위가 상위 생성자 접근을 할 수 없다.)

- 직관적이지 않을 수 있으니 적절한 주석을 달 것

public class ImageUtility {

    // 기본 생성자가 만들어지는 것을 방어(인스턴스화 방지용)
    private ImageUtility() {
        throw new AssertionError();
    }
}
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !