Search
Duplicate
📒

[Java Study] 01-2. 의존주입, 객채 생성/파괴

상태
완료
수업
Java Study
주제
4 more properties
참고

의존성 주입의 장점과 사용법

NOTE
소프트웨어 설계에서 의존성 주입(DI)은 객체(의존성)를 클래스의 생성자, 메서드 또는 필드에 전달(주입)하는 패턴입니다. 이 패턴을 사용하면 클래스는 자신의 의존성을 직접 생성하지 않고 외부에서 받아들이기 때문에 높은 수준의 모듈성과 유연성을 달성할 수 있습니다.
의존성 주입은 다형성을 이용하며, 간단하게 설명하면 Object와 같은 추상타입으로 구체 타입을 받아서 다양한 구체타입을 받을 수 있습니다.
public class BookService { // 직접 주입 private static final Book BOOK = new FantasyBook(); private BookService() {} }
Java
복사
정적 유틸 클래스 방식
기존의 방식에는 직접 의존하는 객체를 생성했습니다. 이렇게되면 서비스는 오직 FantasyBook에 대해서만 처리할 수 있게 됩니다.
public class BookService { // 의존성을 주입받을 필드 private final Book book; // 생성자를 통한 의존 객체(BookLibrary) 주입 (판타지 책 이외의 모든 책을 받을 수 있다.) public BookService(Book book) { this.book = book; } }
Java
복사
의존성 주입 형태
의존성 주입을 사용하는 경우, 클래스는 외부에서 의존성을 받아들입니다. 이는 느슨한 결합을 통해 유연성과 모듈성을 제공합니다.
이러한 의존성 주입을 사용함으로서 아래와 같은 이점을 얻을 수 있습니다.
클래스가 특정 구현에 덜 의존적이 되면, 다양한 환경에서 다른 구현체를 쉽게 교체할 수 있습니다.
클래스가 구체적인 의존성을 생성하지 않기 때문에, 단위 테스트 시에 모의객체를 쉽게 주입 할 수 있습니다.

의존 객체 팩토리 패턴

NOTE
Supplier를 활용한 팩토리 패턴은 다양한 상황에서 유용하게 사용될 수 있습니다.
// Book 객체 public class Book { private String title; public Book(String title) { this.title = title;} public String getTitle() { return title; } } // Book 객체를 리스트로 관리 public class BookCollection { private List<Book> books; public BookCollection(List<Book> books) { this.books = books; } // BookCollection에 대한 메서드 추가 가능 public void printTitles() { for (Book book : books) { System.out.println(book.getTitle()); } } }
Java
복사
public class BookFactory { public BookCollection create(Supplier<? extends Book> bookFactory) { return new BookCollection(Arrays.asList( bookFactory.get(), bookFactory.get(), bookFactory.get(), bookFactory.get())); } }
Java
복사
BookFactory를 사용하여 BookColection 생성
BookCollection sciFiBooks = bookFactory.create(() -> new Book("Science Fiction")); BookCollection historyBooks = bookFactory.create(() -> new Book("History"));
Java
복사
선택된 주제의 책이 여러개 만들어진다.

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

NOTE
똑같은 기능의 객체를 매번 생성하기 보다는 객체 하나를 재사용하는 편이 좋습니다.
생성자를 통해 생성하는 것 대신에 정적 팩토리 메소드를 제공하는 불변 클래스에서는 재사용할 수 있는 객체 생성을 할 수 있습니다.
String s = new String("bikini"); // 사용시 새로 생성 String s = "bikini"; // 사용시 재사용
Java
복사
생성자는 매번 새로운 값을 만들어 낸다.
Integer i1 = new Integer(50); Integer i2 = new Integer(50); System.out.println(i1 == i2); // false Integer i3 = Integer.valueOf(50); // JAVA 8부터 =50과 동일 Integer i4 = Integer.valueOf(50); System.out.println(i3 == i4); // true
Java
복사
Wrapper Class의 valueOf()가 대표적인 정적 팩토리 메서드 예시

핵심

불필요한 객체 생성을 피하라는 말이 가벼운 객체 생성까지 금지한다는 뜻은 아니다. JVM에서는 가벼운 객체를 생성하는 것이 큰 부담이 되지 않는다. 주로 비용이 많이 드는 객체의 재사용성을 고려해야한다는 것을 기억하자.

생성 비용이 비싼 객체 캐싱

NOTE
public class RegexUtil { // 캐싱을 위한 정규표현식 패턴 객체 private static final Pattern EMAIL_PATTERN = Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$"); public static boolean isValidEmail(String email) { return EMAIL_PATTERN.matcher(email).matches(); } }
Java
복사
정규표현식 패턴의 경우 컴파일의 비용이 높기 때문에 객체를 캐싱한다.

불필요한 객체 만들지 않기

NOTE
Long sum = 0L; for (long i = 0; i <= Integer.MAX_VALUE; i++) { sum += i; }
Java
복사
오토박싱/언박싱 과정에서 객체생성이 발생

Java에서 메모리 누수를 방지하는 방법

NOTE
Java에서는 JVM의 메모리를 GC가 자동으로 관리해주지만, 이것이 메모리 관리에 대해 더 이상 신경 쓰지 않아도 된다는 의미는 아닙니다. 실제로 잘못된 코드 작성으로 인해 메모리 누수가 발생할 수 있습니다.
public Object pop() { if (size == 0) { throw new EmptyStackException(); } Object result = elements[--size]; elements[size] = null; // 참조 해제 return result; }
Java
복사
elements[--size]에서 실제 값은 삭제되지 않고, 인덱스만 이동하므로 메모리 누수가 발생한다.
객체를 가리키는 참조를 적절히 해제하지 않으면 메모리 누수가 발생할 수 있습니다.
메모리 누수의 주 원인은 사용하지 않는 참조(다 쓴 참조)를 계속 유지하는 것입니다.
다 쓴 참조 ⇒ 더 이상 사용되지 않는 객체의 참조
GC는 실수로 유지되는 객체의 메모리 누수를 찾기 어렵습니다. 하나의 객체 참조를 유지하는 경우, GC는 그 객체 뿐만 아니라 그 객체가 참조하는 모든 객체들을 회수하지 못하므로 성능에 영향을 줍니다.
그러나 이는 모든 객체를 사용 후 즉시 null 처리하는 것을 적극적으로 추구해야 한다는 의미는 아닙니다. 객체 참조를 null로 처리하는 것은 특별한 상황에 한정되어야 합니다. 사용이 끝난 참조를 해제하는 가장 효과적인 방법은 해당 참조를 포함하는 변수를 유효 범위 밖으로 이동시키는 것입니다.

강한/부드러운/약한 참조

NOTE
캐시, 리스너, 콜백 메모리 누수를 해결하기 위한 콜렉션의 약한 참조 해시맵에 대해서 알아보자.
// 강한 참조 예시 Object strongReference = new Object(); // 'strongReference'는 Object 객체를 강하게 참조합니다. // 이 객체는 'strongReference'가 가리키고 있는 한 가비지 컬렉터에 의해 회수되지 않습니다.
Java
복사
강한참조 - 일반적인 참조방법(GC수집 대상이 아니다)
// 객체에 대한 강한 참조 생성 String strong = new String("I am a strong reference"); // 객체에 대한 부드러운 참조 생성 SoftReference<String> soft = new SoftReference<>(strong); // 강한 참조 제거 (StoreReference만 존재) strong = null; // 부드러운 참조를 통해 객체에 접근 String value = soft.get(); if (value != null) { System.out.println(value); } else { System.out.println("The object was garbage collected."); }
Java
복사
부드러운 참조 - 더 이상 원본이 없고 참조객체만 있는경우 (메모리 여분에따라 GC여부 갈림)
// 약한참조 예시 Object weakReferencedObject = new Object(); WeakReference<Object> weakReference = new WeakReference<>(weakReferencedObject); // 'weakReferencedObject'에 대한 약한 참조 생성 / null qusghks weakReferencedObject = null; // 강한 참조 제거
Java
복사
약한 참조 - GC 수집

WeakHashMap & LinkedHashMap

NOTE
Java 컬렉션 프리엠워크는 다양한 상황에서 메모리 관리를 최적화하고, 메모리 누수를 방지하기 위해 WeakHashMap, LinkedHashMap 구현체를 제공합니다.
weakHashMap은 약한 참조를 키로 사용하는 해시 테이블 기반의 Map 구현입니다. 이 구현체는 주로 임시적인 항목을 저장하는데 사용합니다.
public static void main(String[] args) throws InterruptedException { WeakHashMap<Object, String> weakHashMap = new WeakHashMap<>(); Object key = new Object(); weakHashMap.put(key, "example"); // 키에 대한 강한 참조 존재 System.out.println("Before nullifying key: " + weakHashMap.containsKey(key)); // true key = null; // 강한참조 제거 System.gc(); Thread.sleep(1000); // 약한 참조로 값이 제거되었는가? System.out.println("After nullifying key and GC: " + weakHashMap.isEmpty()); // true }
Java
복사
Key가 약한참조로 변하면 삭제됨
예를 들어 어떤 객체의 메타데이터를 캐시하는 경우, 객체에 대한 참조가 사라지면 자동으로 메타데이터도 제거됩니다.
LinkedHashMap은 HashMap에 순서 개념을 추가한 구현체로, 삽입 순서 또는 접근 순서를 유지할 수 있습니다.
public static void main(String[] args) { int capacity = 100; LinkedHashMap<Integer, Integer> lruCache = new LinkedHashMap<>(capacity, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) { return size() > capacity; } }; for (int i = 0; i < 150; i++) { lruCache.put(i, i); } for (Integer i : lruCache.keySet()) { System.out.println("i = " + i); // 50부터 출력된다. } }
Java
복사
입력된 순서에 따라 출력된다.
LRU 캐시방식(가장 오래된 요소 제거)를 구현할 수 있으며, 이를 통해 메모리 사용량을 제한하면서 캐시의 효율을 극대화할 수 있습니다.

Java의 메모리 누수 예제

NOTE
Integer, Long과 같은 Wrapper 클래스를 이용한 무의미한 객체 생성
static field로 인한 메모리 누수
리스너(Listener), 콜백(Callback)과
외부 클래스를 참조하는 내부 클래스
리스너(Listener), 콜백(Callback)