꾸준한 스터디
article thumbnail

결론적으로 두 방법을 사용하지 말라고 권장되고 있다.

 

finalizer란

Finalizer 클래스가 있지만 document에서도 안나오고..

사용하는 방법은 Object 클래스에 정의된 finalize 메서드를 사용하는 것.

메서드 블록안에는 아무런 기능구현이 없고 모든 객체는 Object를 상속받고 있으니 메서드 오버라이딩하여 사용하게 되는데 가비지 컬렉션 대상이 되어 객체를 없애기 전 JVM이 호출하는 메서드로  해당 객체 내에서 리소스 정리, io나 네트워크 같이 반드시 사용 후 종료가 되어야 하는 일이 필요할 때 사용하기 위해 만들어진 메서드이다.

 

finalizer의 문제점

fanalize 메서드는 언제 실행시점이 되는지 알 수 없는 문제가 있다.

보통 Heap 메모리 영역에 Eden 메모리 공간이 다 차거나 Old Generation 영역이 다 차면 일어나게 되는데 언제 일어날지 명확히 알 수 없고 명시적으로 System.gc() 메서드를 명시적으로 사용하는 것은 시스템 성능에 큰 영향을 끼쳐서 사용하지 않는다. 즉시 수행된다는 보장이 없기 때문에 제때 실행되어야 하는 작업을 할 수 없다.

 

fianlize 메서드 내에 발생한 예외는 무시된다.

보통의 메서드에서는 예외발생시 별도 try~catch 구문이 없다면 실행이 중단되고 에러메세지가 콘솔에 출력되지만 finalize 메서드 내부에서 예외가 발생한다면 예외 발생이 무시된다.

보통의 메서드 내부에서는 str에 null을 넣었으니 NullPointerException이 나지만

finalize 메서드 내부에는 NPE 예외 발생이 되어야 하는데도 무시되고 10번의 루프가 돌아간다.

예외가 발생될만한 코드가 내부에 있을 때 처리할 작업이 남아있더라도 finalize메서드는 그 순간 종료되어 해당 객체는 마무리가 덜 된 상태로 남아있게 된다.

 

인스턴스 반납이 지연된다.

finalize메서드를 오버라이딩 하면 해당 객체는 참조 해제되고 가비지 컬렉션 대상이 되면 finalize메서드가 실행되면서 Finalizer에 ReferenceQueue에 들어가게 된다.

ReferenceQueue에 들어간 인스턴스들이 가비지 컬렉션이 제대로 이루어 지지않고 대기열에서 회수되기를 기다리는데 작업을 하는 finalizer Thread가 우선순위가 낮아서 실행될 기회를 제대로 얻지 못한다.

나는 왜 OutOfMemoryError가 난걸까..

객체를 새로 생성하는 Thread가 더 우선순위가 되면서 레퍼런스 큐에 있는 인스턴스들을 가비지 컬렉션이 정리하지 못하고 에러가 났다. 어떤스레드가 finalizer를 수행하는지 모르기 때문에 예방할 수 있는 해법이 없다. finalizer가 가비지 컬렉터의 효율을 떨어뜨려 객체 생성과 파괴의 시간이 오래걸리기 때문에 심각한 성능 문제도 동반된다.

 

객체가 부활할 가능성

finalize 메서드 안에 메서드 유효범위가 끝나서 참조 해제되어야 하는 객체나 자기 자신을 참조하여 다른 일을 하게 된다면 종료되어야 할 객체들이 다시 부활할 가능성이 있다.

 

 

Cleaner란

finalizer보다 덜 위험하지만 마찬가지로 언제 실행될지 예측할 수 없고, 느리고, 일반적으로 불필요하다.

finalizer가 java 8버전부터 deprecated 된 후 java 9버전부터 나온 클래스이다.

그나마 cleaner는 자신을 수행할 스레드를 제어할 수 있다는 점이 finalizer보다 났지만 가비지 컬렉터 통제하에 있어서 즉각 수행되리란 보장이 없는건 여전하다.

 

사용법

package item8.cleaner;

import java.util.List;

public class BigObject {

	// BigObject가 없어질 때 정리가 되어야 하는 리소스
	private List<Object> resource;
	
	public BigObject(List<Object> resource) {
		this.resource = resource;
	}
	
	// resource 정리하는 객체 별도의 Runnable task로 정의 finalize의 역할
	// static inner class로 정의
	// 해당 객체 안에 BigObject 참조 금지. 삭제하려고 할 때 참조를 하면 부활하게 될 수 있다.
	public static class ResourceCleaner implements Runnable {
		
		private List<Object> resourceToClean;
		
		public ResourceCleaner(List<Object> resourceToClean) {
			this.resourceToClean = resourceToClean;
		}

		@Override
		public void run() {
			resourceToClean = null;
			System.out.println("clened up.");
		}
		
	}// end of inner class
	
}// end of class

정리 작업이 이루어질 클래스 내에 별도의 정적 클래스 Runnable 구현. (static class가 아니라면 BigObject에 종속적인 클래스가 되어 BigObject를 다시 참조)

run 메서드 안에 실질적 정리 기능 구현

 

package item8.cleaner;

import java.lang.ref.Cleaner;
import java.util.ArrayList;
import java.util.List;

public class CleanerIsNotGood {

	public static void main(String[] args) throws InterruptedException {

		// Cleaner 생성
		Cleaner cleaner = Cleaner.create();
		
		// 사용할 객체 생성
		List<Object> resourceToCleanUp = new ArrayList<Object>();
		BigObject bigObject = new BigObject(resourceToCleanUp);
		
		// Cleaner에 등록 (Cleaner gc 대상 객체, 자원 해제 기능 Task)
		cleaner.register(bigObject, new BigObject.ResourceCleaner(resourceToCleanUp));
		
		bigObject = null;
		System.gc();
		Thread.sleep(3000L);
		
	}// end of main

}// end of class

Cleaner 객체 생성 후, 사용할 객체 참조.

Cleaner 객체에 가비지 컬렉션 대상과 가비지 컬렉션 대상이 됐을 때 수행될 업무를 알려줌

해당 객체 참조가 null이 되고 가비지 컬렉션 대상이 되었을 때 정리작업을 수행한다.

 

 

대체할 만한 방법은 AutoCloseable을 구현한 객체를 try with resources 방법으로 사용하는 방법이 있으나

만약 이 방법을 사용하지 않았을 때에 최후에 가비지 컬렉션이 일어나는 때라도 리소스 정리를 해주기 위한 안전망으로 Cleaner를 사용하는 방법이 있다.

 

대체 사용법

네이티브 피어 객체를 회수할 때. 네이티브 피어는 C나 C++로 만들어진 객체. OS에 특화된 기능들을 사용할 때 쓰는데 자바 객체가 아니어서 가비지 컬렉터가 존재를 알지 못하니 네이티브 객체 회수를 못하는데 그 때 cleaner나 finalizer를 사용하여 회수할 수 있다.

 

package item8.cleaner_safe;

import java.lang.ref.Cleaner;

// try with resource와 cleaner를 안전망으로 사용하는 클래스
public class Room implements AutoCloseable {
	
	// Cleaner 생성
	private static final Cleaner cleaner = Cleaner.create();
	
	// 청소가 필요한 자원 및 자원 정리 작업 Task. Room 참조 금지
	private static class State implements Runnable {
		
		int numJunkPiles;
		
		State(int numJunkPiles) { this.numJunkPiles = numJunkPiles; }
		
		// close 메서드나 cleaner가 호출
		@Override
		public void run() {
			System.out.println("Cleaning room");
			numJunkPiles = 0;
		}
	}
	
	// 방의 상태. cleanable과 공유
	private final State state;
	
	//cleanable 객체. 수거 대상이 되면 방을 청소한다.
	private final Cleaner.Cleanable cleanable;
	
	public Room(int numJunkPiles) {
		state = new State(numJunkPiles);
		cleanable = cleaner.register(this, state);
	}

	@Override
	public void close() {
		cleanable.clean();		
	}
	
}

 

 

참고

https://www.inflearn.com/course/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-1

 

이펙티브 자바 완벽 공략 1부 - 인프런 | 강의

자바 학습의 끝판왕 “이펙티브 자바”를 보다 쉽고 완벽하게 학습하세요. 이 강의를 수강하시면 여러분의 자바 프로그래밍 지식과 실력뿐 아니라 학습 능력 또한 향상 시킬 수 있습니다., - 강

www.inflearn.com

 

profile

꾸준한 스터디

@StudyRecord

포스팅이 유익하셨다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!