네로개발일기

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

'dev book'에 해당되는 글 32건


반응형

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

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

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

반응형

# private 생성자나 열거타입으로 싱글턴임을 보증하라.

 

## 싱글턴이란?

* 싱글턴(singleton): 인스턴스를 오직 하나만 생성할 수 있는 클래스

- 함수와 같은 무상태(stateless) 객체

- 설계상 유일해야하는 시스템 컴포넌트

- 클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기 어려울 수 있음

 

* Mock 객체란?

- 실제 객체를 다양한 조건으로 인해 제대로 구현하기 어려울 경우 **가짜 객체를 만들어 사용**하는데, 이를 Mock 객체라 한다.

 

* Mock 객체가 필요한 경우

- 테스트 작성을 위한 환경 구축이 어려운 경우

- 테스트가 특정 경우나 순간에 의존적인 경우

- 시간이 걸리는 경우

 

## 1. public static 멤버가 final 필드

public class Elvis {

    public static final Elvis INSTANCE = new Elvis();

    private Elvis() { ... }
}

 

- private 생성자가 public static final 필드를 초기화할 때 딱 한번만 호출된다.

- public 혹은 protected 생성자가 없으므로, Elvis 클래스가 초기화될 때 만들어진 인스턴스는 하나뿐임이 보장된다.

 

### 장점

1. public 필드 방식은 해당 클래스가 싱글턴임이 API에 명백하게 드러난다. (final이므로 다른 객체 참조 불가)

2. 간결하다.

 

## 2. static factory 방식

public class Elvis {

    private static final Elvis INSTANCE = new Elvis();

    private Elvis() { ... }

    public static Elvis getInstance() {
        return INSTANCE;
    }
}

 

- Elvis.getInstance()는 항상 같은 객체의 참조를 반환하므로 인스턴스가 하나임을 보장한다.

 

### 장점

1. 현재는 singleton 객체를 리턴하는 정적 메서드지만, 향후에 필요에 따라 변경할 수 있는 확장성이 있다. 유일한 메서드를 반환하는 팩터리 메서드가 호출하는 스레드별로 다른 인스턴스를 넘겨주도록 리턴하는 방법과 같이 확장성이 열려있다.

2. 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다.

3. 정적 팩터리의 메서드 참조를 공급자(supplier)로 사용할 수 있다.

 

- 다음과 같은 장점이 필요하지 않다면, public static final 필드 방식이 더 좋다.

 

### Reflection 방어

- public static final 방식과 static factory 방식은 권한이 있는 클라이언트가 Reflection API인 AccessibleObject.setAccessible을 사용해 private 생성자를 호출할 수 있는 문제점이 있다. 이러한 공격을 방어하려면 두번째 객체가 생성되려 할 때 다음과 같이 예외처리가 가능하다.

public class Elvis {

    public static final Elvis INSTANCE = new Elvis();

    private Elvis() { 
        if( INSTANCE != null) {
            throw new RuntimeException("Can't create Constructor");
        }
          //... 
    }

}

 

### singleton class 직렬화

- singleton class를 직렬화하려면 단순히 Serializable을 구현하는 것만으로는 부족하다. 모든 인스턴스 필드를 transient(일시적) 약어를 선언하고 readResolve 메서드를 제공해야 한다. 이렇게 하지 않으면 역직렬화시 새로운 인스턴스가 생성된다.

public class Elvis implements Serializable {

    private static final Elvis INSTANCE = new Elvis();

    private Elvis() { ... }

    public class Elvis getInstance() {
        return INSTANCE;
    }

    private Object readResolve() { // singleton임을 보장
        return INSTANCE; 
        // 역직렬화가 되어 새로운 인스턴스가 생성되더라도 INSTANCE를 반환하여 싱글턴 보장
        // 새로운 인스턴스는 GC에 의해 UnReachable 형태로 판별되어 제거
    }
}

 

## 3. Enum 방식

public enum Elvis {

    INSTANCE;
}

- 원소가 하나인 Enum 타입을 선언해 singleton을 만들 수 있다.

 

### 장점

1. public static 방식보다 더 간결하다.

2. 추가 코드 없이 직렬화 가능(기본적으로 직렬화되어있음.)

3. Reflection 공격과 아주 복잡한 직렬화 상황에도 제 2의 인스턴스가 생기는 일을 완벽히 방어

 

- 대부분의 상황에서 원소가 하나뿐인 열거 타입이 singleton을 만드는 가장 좋은 방법이다. 하지만, 만들려는 singleton이 Enum 이외의 클래스를 상속해야하는 경우 이 방법은 사용할 수 없다.

728x90
반응형
blog image

Written by ner.o

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