[이펙티브 자바] 객체 파괴
by rowing0328※ 책 내용을 바탕으로 제 관점에서 풀어 쓴 글입니다. 일부 내용이 다를 수 있습니다.
아이템 6 - 불필요한 객체 생성 금지
// Boxing type
public static long sum() {
Long sum = 0L;
for(long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
}
// Primitive Type
public static long sum() {
long sum = 0L;
for(long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
}
- Boxing type 대신 Primitive Type 을 권장한다.
반복문이나 연산에서는 성능 저하를 유발할 수 있으므로 항상 Primitive Type을 우선적으로 사용하자.
public class PhonePatternUtil {
private final String pattern;
public boolean isValid(String phone) {
...
}
}
- Util Class 에서 또한 Primitive type을 권장한다.
참 거짓을 response 하는데 Boxing type을 사용하는 것은 낭비일 수 있다.
// Primitive Type 사용
int price; // 가격이 0원 (예: 증정품)
// Boxing Type 사용
Integer price = null; // 가격이 아직 정해지지 않음
- 상황에서는 Boxing Type(Wrapper Class)이 더 적합할 수 있다.
- Primitive Type (int)
- 사용 조건 : 기본값이 명확하고, 성능 최적화가 필요한 경우
- 예 : 가격이 0원으로 설정된 증정품
- Boxing Type (Integer)
- 사용 조건 : null 값을 통해 설정되지 않은 상태를 명확히 표현해야하는 경우
- 예 : 가격이 아직 정해지지 않은 상태
- Primitive Type (int)
// 비효율적인 코드: Pattern instance가 매번 생성됨
static boolean isEmailValid(String s) {
return s.matches("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$");
}
// String.matches 내부 구현(String.java)
public boolean matches(String regex) {
return Pattern.matches(regex, this);
}
// Pattern.matches 내부 구현 (Pattern.java)
public static boolean matches(String regex, CharSequence input) {
Pattern p = Pattern.compile(regex); // Pattern 객체 생성
Matcher m = p.matcher(input);
return m.matches();
}
// 효율적인 코드: Pattern instance를 한 번만 생성
public class EmailUtil {
private static final Pattern EMAIL = Pattern.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$");
static boolean isEmailValid(String s) {
return EMAIL.matcher(s).matches();
}
}
- 주의해야 할 내장 Method
자주 사용하는 내장 메서드는 성능과 메모리 사용을 고려해, 재사용 가능한 구조로 변경하자.
아이템 7 - 다 쓴 객체 참조를 해제하라
Map<Object, String> cache = new HashMap<>();
Object key = new Object();
cache.put(key, "Value");
// key가 더 이상 필요 없음
key = null; // 하지만 cache에는 여전히 key와 value가 남아 있음
- 메모리 누수 발생
사용이 끝난 객체가 참조 상태로 남아 있으면 GC가 해당 객체를 회수하지 못한다. - 성능 저하
불필요한 객체가 메모리를 차지하면서 시스템 성능이 떨어진다. - OutOfMemoryError 발생 위험
메모리 누수가 쌓이면 자원이 부족해지고, 심각한 경우 애플리케이션이 멈출 수 있다.
import java.util.WeakHashMap;
public class WeakHashMapExample {
public static void main(String[] args) {
WeakHashMap<Object, String> cache = new WeakHashMap<>();
Object key = new Object();
cache.put(key, "Value");
key = null; // 참조 제거
System.gc(); // GC 실행 요청
System.out.println("Cache size: " + cache.size()); // 0
}
}
- 명시적 참조 제거
객체 사용이 끝난 후 컬렉션에서 제거해야한다. - 약한 참조 활용
WeakHashMap으로 다 쓴 객체를 자동으로 정리할 수 있다. - 캐시 관리
유효 기간을 설정해 오래된 항목을 자동으로 제거한다.
불필요한 항목은 주기적으로 정리하는게 좋다.
아이템 8 - finalizer, cleaner를 피하라
public class Item8 {
@Override
protected void finalize() {
System.out.println("call finalize");
}
}
public class MainApplication {
private void run() {
Item8 item8 = new Item8();
}
public static void main(String[] args) {
MainApplication mainApplication = new MainApplication();
mainApplication.run();
System.gc(); // GC를 강제로 트리거 (힌트 제공)
}
}
- Finalizer의 문제점
- 예측 불가능 : GC 실행 시점을 알 수 없어, 정리 작업이 지연될 수 있다.
- 성능 저하 : GC 성능에 영향을 미쳐 시스템 효율성을 떨어뜨린다.
- 안전성 문제 : finalize()에서 예외 발생 시 자원 누수가 발생할 수 있다.
- System.gc()의 한계
- 강제성이 없음 : JVM에 GC를 실행하라는 힌트일 뿐이며, 실제 실행 여부는 JVM의 구현과 상황에 따라 달라진다.
- 비권장 : GC 타이밍은 JVM이 최적화하도록 맡겨야하며, System.gc()에 의존하는 것은 비효율적이다.
import java.lang.ref.Cleaner;
public class CleanObject implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
private static class CleanData implements Runnable {
@Override
public void run() {
System.out.println("Cleaning resources...");
}
}
private final CleanData cleanData;
private final Cleaner.Cleanable cleanable;
public CleanObject() {
this.cleanData = new CleanData();
this.cleanable = cleaner.register(this, cleanData);
}
@Override
public void close() {
cleanable.clean(); // 명시적으로 자원 정리
System.out.println("CleanObject closed.");
}
}
- GC 의존성
Cleaner는 GC에 의존적으로 동작하므로 자원의 해제가 GC 실행 시점에 좌우된다. - 즉시성 부족
Cleaner는 비동기적으로 실행되기 때문에 정리 작업이 즉시 이루어질 것이라는 보장이 없다.
아이템 9 - try-finally 대신 try-with-resources
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[100];
int n;
while ((n = in.read(buf)) > 0) {
out.write(buf, 0, n);
}
} finally {
out.close(); // 자원 수동 해제
}
} finally {
in.close(); // 자원 수동 해제
}
}
- 코드 복잡성 증가
try-finally를 사용할 경우, 자원을 명시적으로 닫아야 하므로 코드가 장황해지고 가독성이 떨어진다. - Stack Trace 어려움
예외가 발생하면 스택 트레이스에서 원인 추적이 어려워질 수 있다.
public class Resource implements AutoCloseable {
@Override
public void close() throws Exception {
throw new Exception("inside Resource exception");
}
}
try (Resource r1 = new Resource();
Resource r2 = new Resource()) {
throw new Exception("Main exception");
}
- 코드 간결화
AutoCloseable을 구현한 객체를 사용하여, 자원을 자동으로 해제한다. - Stack Trace 개선
여러 예외를 다룰 때도 스택 트레이스에서 예외의 원인을 명확히 추적할 수 있다.
마무리
이번 장을 통해, GC가 모든 메모리 문제를 자동으로 해결해주는 것은 아니라는 사실을 인지했다.
편의성에만 안주하지 않고, 코드의 동작을 깊이 이해하며 책임감 있게 관리하는 것이 얼마나 중요한지 다시 한번 깨달았다.
'📚Book Archive > Effective Java' 카테고리의 다른 글
[이펙티브 자바] Generic 으로 만들어 사용하기 (0) | 2025.01.03 |
---|---|
[이펙티브 자바] Interface와 Class 설계 원칙 (0) | 2024.12.30 |
[이펙티브 자바] Class와 상속 (4) | 2024.12.20 |
[이펙티브 자바] 모든 객체의 공통 메서드 (0) | 2024.12.13 |
[이펙티브 자바] 객체 생성 (0) | 2024.12.06 |
블로그의 정보
코드의 여백
rowing0328