Object.finalize ()를 재정의하는 것이 실제로 좋지 않습니까?


34

재정의에 대한 주요 두 가지 주장 Object.finalize()은 다음과 같습니다.

  1. 언제 호출되는지 결정할 수 없습니다.

  2. 전혀 호출되지 않을 수 있습니다.

내가 이것을 올바르게 이해한다면, 나는 그것이 Object.finalize()그렇게 미워할만한 충분한 이유라고 생각하지 않습니다 .

  1. 객체를 할당 해제하기위한 적절한 시간이 개발자가 아닌시기를 결정하는 것은 VM 구현과 GC에 달려 있습니다. 언제 Object.finalize()부름을받는 것이 중요 합니까?

  2. 일반적으로, 내가 틀렸다면 나를 수정하십시오 Object.finalize().GC를 실행할 기회를 얻기 전에 응용 프로그램이 종료 된 때만 호출되지 않습니다. 그러나 응용 프로그램 프로세스가 종료되면 객체가 할당 해제됩니다. 그래서 Object.finalize()그것을 호출 할 필요가 없기 때문에 전화를받을하지 않았다. 개발자가 관심을 갖는 이유는 무엇입니까?

파일 핸들 및 연결과 같이 수동으로 닫아야하는 객체를 사용할 때마다 매우 실망합니다. 객체에 구현이 있는지 지속적으로 확인해야하며 close()과거에 어떤 시점에서 객체에 대한 호출을 놓친 것으로 확신합니다. 왜 close()구현을 넣어서 이러한 객체를 처리하기 위해 VM과 GC에 맡기는 것이 더 간단하고 안전하지 Object.finalize()않습니까?


1
또한 참고 : Java 1.0 시대의 많은 API와 마찬가지로 스레드 의미 finalize()는 약간 엉망입니다. 구현 한 경우 동일한 객체의 다른 모든 메소드와 관련하여 스레드로부터 안전해야합니다.
billc.cn

2
사람들이 파이널 라이저가 나쁘다는 말을 들었다고해서 프로그램이있는 경우 프로그램 작동이 중지되는 것은 아닙니다. 그것들은 마무리의 전체 아이디어가 꽤 쓸모 없다는 것을 의미합니다.
user253751

1
이 질문에 +1 아래의 답변 대부분은 파일 설명자와 같은 리소스가 제한되어 있으며 수동으로 수집해야한다고 말합니다. 메모리에 대해서도 마찬가지입니다. 따라서 메모리 수집이 약간 지연되면 파일 디스크립터 및 / 또는 다른 리소스에 메모리 컬렉션을 허용하지 않겠습니까?
mbonnin

마지막 단락을 해결하면 파일 핸들 및 연결과 같은 닫는 부분을 거의 처리하지 않고 Java로 남겨 둘 수 있습니다. try-with-resources 블록을 사용하십시오-이미 답변과 의견에 몇 번 언급되었지만 여기에 넣을 가치가 있다고 생각합니다. 이에 대한 오라클 튜토리얼은 docs.oracle.com/javase/tutorial/essential/exceptions/…
Jeutnarg

답변:


45

내 경험에는 재정의의 단 하나의 이유가 Object.finalize()있지만 매우 좋은 이유입니다 .

finalize()호출을 잊어 버린 경우 알려주 는 오류 로깅 코드를 배치합니다 close().

정적 분석은 사소한 사용 시나리오에서만 누락을 포착 할 수 있으며 다른 답변에 언급 된 컴파일러 경고에는 사소한 작업을 수행하기 위해 실제로 사용하지 않도록 설정해야하는 간단한 뷰가 있습니다. (알거나 알고있는 다른 프로그래머보다 훨씬 많은 경고가 활성화되어 있지만 어리석은 경고는 활성화되어 있지 않습니다.)

마무리는 리소스가 방해받지 않도록하는 좋은 메커니즘 인 것처럼 보이지만 대부분의 사람들은이를 완전히 잘못된 방식으로보고 있습니다. 즉, 대체 폴백 메커니즘, "두 번째 기회"보호 수단으로 생각합니다. 그들이 잊어 버린 자원을 처분함으로써 하루. 이것은 잘못되었습니다 . 주어진 일을 수행하는 한 가지 방법 만 있어야합니다. 항상 모든 것을 닫거나 마무리는 항상 모든 것을 닫습니다. 그러나 마무리는 신뢰할 수 없으므로 마무리는 불가능합니다.

그래서, 거기에 내가 부르는이 제도입니다 필수 처리를 하고, 프로그래머가 담당하는 규정 항상 명시 적으로 모든 닫는 구현 Closeable또는 AutoCloseable. (.은 try-와-자원 문 여전히 명시 적 폐쇄로 계산) 물론은, 프로그래머는, 그래서 마무리가 활동하기 시작하지만,의 잊을 수 없는 경우에는 마무리 발견한다 : 마술 오른쪽 결국 일을 할 것입니다 마법의 요정 등을 것을 close()호출되지 않은, 그것은 수행 하지(수학적 확실성으로) 그들이 너무 게 으르거나 마음이 결여 된 일을하기 위해 그것에 의존하는 n00b 프로그래머들이 있기 때문에 정확하게 그것을 호출하려고 시도하십시오. 따라서 강제 처리를 통해 최종화 close()가 호출되지 않은 것을 발견 하면 밝은 빨간색 오류 메시지를 기록하여 프로그래머에게 큰 뚱뚱한 모든 대문자를 사용하여 프로그래머에게 자신의 물건을 고칠 것을 지시합니다.

추가 이점으로 소문에 따르면 "JVM은 사소한 finalize () 메소드 (예 : Object 클래스에 정의 된 것과 같은 작업을 수행하지 않고 리턴하는 메소드)를 무시하므로 강제로 폐기하면 모든 마무리를 피할 수 있습니다. 다음 과 같이 메소드 를 코딩 하여 전체 시스템의 오버 헤드 ( 이 오버 헤드가 얼마나 끔찍한 지에 대한 정보는 alip의 답변 참조 ) finalize():

@Override
protected void finalize() throws Throwable
{
    if( Global.DEBUG && !closed )
    {
        Log.Error( "FORGOT TO CLOSE THIS!" );
    }
    //super.finalize(); see alip's comment on why this should not be invoked.
}

이것 뒤에 숨겨진 아이디어 는 컴파일 타임에 값을 알 수 Global.DEBUG있는 static final변수이므로 false컴파일러는 전체 if명령문에 대해 전혀 코드를 생성하지 않으므로 사소한 (빈) 종결자가됩니다. 클래스가 마치 파이널 라이저가없는 것처럼 취급됩니다. (C #에서는 이것이 멋진 #if DEBUG블록 으로 수행 되지만, 우리가 할 수있는 일은 뇌에서 추가 오버 헤드가있는 코드에서 명백한 단순성을 지불하는 Java입니다.)

필수 처분에 대한 자세한 내용, 닷넷에서의 자원 처분에 대한 추가 논의와 함께 여기에 : michael.gr : 강제 처분과 "처분 처분"가증


2
: @MikeNakis는 닫기 가능한이 두 번째라고하면 아무것도하지 않고 같이 정의된다, 잊지 마세요 docs.oracle.com/javase/7/docs/api/java/io/Closeable.html을 . 수업이 두 번 닫히면 경고가 기록되는 경우가 있지만 기술적으로는 그렇게하지 않아도됩니다. 기술적으로 Closable에서 .close ()를 두 번 이상 호출하면 완벽하게 유효합니다.
Patrick M

1
@usr 테스트를 믿거 나 테스트를 믿지 않는지에 따라 요약됩니다. 당신이 당신의 테스트를 신뢰하지 않는 경우, 물론, 가서으로 마무리 오버 헤드를 고통 close() 경우에 대비,. 테스트를 신뢰할 수 없으면 시스템을 프로덕션에 공개하지 않는 것이 좋습니다.
마이크 나 키스

3
@Mike는 if( Global.DEBUG && ...구문이 작동하기 때문에 JVM이 finalize()메소드를 사소한 것으로 무시하므로 Global.DEBUG컴파일 타임 (주입 등과 달리)에서 설정해야하므로 다음 코드는 죽은 코드가됩니다. super.finalize()if 블록 외부에 대한 호출 은 JVM Global.DEBUG이 수퍼 클래스 #finalize()도 사소한 경우에도 (최소한 HotSpot 1.8 의 값에 관계없이) 사소한 것으로 취급하기에 충분합니다 !
alip

1
@ 마침내 그럴 까봐 두렵습니다. 링크 된 기사 에서 (약간 수정 된 버전의) 테스트로 테스트했으며 장황한 GC 출력 (놀랍게도 성능이 저하됨)이 객체가 생존자 / 이전 세대 공간에 복사되었으며 전체 힙 GC가 필요하다는 것을 확인했습니다. 을 제거하다.
alip

1
종종 간과되는 것은 예상보다 일찍 개체를 수집하여 종료 자에서 리소스를 해제하는 것을 매우 위험한 작업으로 만드는 위험입니다. Java 9 이전에는 종료자가 아직 사용중인 동안 종료자가 자원을 닫지 않도록하는 유일한 방법은 종료 자 및 자원을 사용하는 메소드 모두에서 오브젝트를 동기화하는 것입니다. 그것이에서 작동하는 이유입니다 java.io. 만약 그러한 종류의 스레드 안전이 위시리스트에 없다면, 그것은 다음에 의해 유발되는 오버 헤드에 추가됩니다 finalize().
Holger

28

파일 핸들 및 연결과 같이 수동으로 닫아야하는 객체를 사용할 때마다 매우 실망합니다. [...] 왜 close()구현을 넣어서 이러한 객체를 처리하기 위해 VM과 GC에 그대로 두는 것이 더 간단하고 안전한 Object.finalize()가요?

파일 핸들 및 연결 ( Linux 및 POSIX 시스템의 파일 디스크립터 )은 매우 드문 자원이므로 일부 시스템에서는 256 개로 제한되거나 다른 시스템에서는 16384로 제한 될 수 있습니다 (setrlimit (2) 참조 ). 이러한 제한된 자원의 고갈을 피하기 위해 GC가 충분히 자주 (또는 적시에) 호출 될 것이라는 보장은 없습니다. 그리고 GC가 충분히 불려지지 않으면 (또는 마무리가 적시에 실행되지 않으면) 그 한계에 도달하게 될 것입니다.

결말은 JVM에서 "최선의 노력"입니다. 호출되지 않거나 상당히 늦게 호출 될 수 있습니다. 특히 RAM이 많거나 프로그램이 많은 개체를 할당하지 않은 경우 (또는 대부분 오래된 개체로 전달되기 전에 대부분의 개체가 죽는 경우) 복사 세대 GC에 의한 생성)의 경우 GC를 거의 호출 할 수 없으며 최종 결과가 자주 실행되지 않거나 전혀 실행되지 않을 수도 있습니다.

따라서 close가능한 경우 명시 적으로 파일 디스크립터를 파일 화하십시오. 누출을 두려워하는 경우 최종 조치를 기본 조치가 아닌 추가 조치로 사용하십시오.


7
스트림을 파일이나 소켓에 닫으면 일반적으로 플러시한다고 덧붙입니다. 스트림을 열어두면 전원이 꺼지고 연결이 끊어지면 (네트워크를 통해 액세스 한 파일의 위험도) 데이터 손실의 위험이 불필요하게 증가합니다.

2
실제로 허용되는 파일 디스크립터 수가 적을 수는 있지만 실제로 GC의 신호로 사용할 있기 때문에 그다지 어려운 점은 아닙니다 . 정말 문제가되는 것은 a) GC가 관리하지 않는 리소스가 얼마나 많은지 GC에 완전히 불투명하고, b) 많은 리소스가 고유하기 때문에 다른 리소스는 차단되거나 거부 될 수 있습니다.
중복 제거기

2
파일을 열어두면 파일을 사용하는 다른 사람을 방해 할 수 있습니다. (Windows 8 XPS 뷰어,보고 있습니다!)
Loren Pechtel

2
"[파일 디스크립터]가 유출되는 것을 두려워하는 경우, 마무리를 주된 방법이 아닌 추가 수단으로 사용하십시오." 이 말은 비린내 들린다. 코드를 잘 디자인하면 여러 위치에 정리를 분산시키는 중복 코드를 실제로 도입해야합니까?
mucaho

2
@BasileStarynkevitch 당신의 요점은 이상적인 세계에서, 예, 중복성은 나쁘지만 실제로는 모든 관련 측면을 예측할 수없는 곳에서 미안보다 안전하다고 생각 하는가?
mucaho

13

이 방법으로 문제를보십시오 : 당신은 (a) 정확하고 (그렇지 않으면 프로그램이 잘못되었습니다) (b) 필요한 (그렇지 않으면 코드가 너무 커서 더 많은 RAM이 필요하고 더 많은 사이클을 소비하는 코드 만 작성해야합니다 쓸모없는 것들, 이해하기위한 더 많은 노력, 그것을 유지하는데 더 많은 시간을 소비하는 등

이제 파이널 라이저에서 원하는 것이 무엇인지 고려하십시오. 하나 는 필요가있다. 이 경우 호출 여부를 알 수 없으므로 종료 자에 넣을 수 없습니다. 충분하지 않습니다. 또는 필요하지 않습니다-처음에는 쓰지 않아야합니다! 어느 쪽이든, 파이널 라이저에 넣는 것은 잘못된 선택입니다.

(주 해당 파일 스트림, 폐쇄와 같은 당신의 이름 예, 모양을 그들이 정말 필요하지,하지만 그들은 인 경우로. 그것은 당신이 시스템에 열린 파일 핸들의 한계를 칠 때까지, 당신은하지 않습니다의 통보 당신의 그러나이 제한은 운영 체제의 기능이므로 종료 자에 대한 JVM의 정책보다 예측할 수 없으므로 파일 핸들을 낭비하지 않는 것이 중요합니다.)


"필요한"코드 만 작성해야한다면 모든 GUI 응용 프로그램에서 모든 스타일을 피해야합니까? 반드시 그 중 어느 것도 필요하지 않습니다. GUI는 스타일링없이 잘 작동하며 끔찍하게 보일 것입니다. 종료 자에 대해서는 무언가를 할 필요가 있지만, 종료 자에 넣는 것은 괜찮습니다. 종료자는 오브젝트 GC 중에 호출되기 때문입니다. 당신은 자원을 종료해야하는 경우 구체적으로 는 가비지 컬렉션 준비가되면, 다음 파이널 최적입니다. 프로그램이 리소스 해제를 종료하거나 종료자가 호출됩니다.
Kröw

RAM이 무겁고 작성 비용이 높고 닫을 수있는 객체가 있고 필요하지 않을 때 지울 수 있도록 약하게 참조하기로 결정한 finalize()경우 gc 사이클을 실제로 해제 해야하는 경우 사용할 수 있습니다. 램. 그렇지 않으면 객체를 사용해야 할 때마다 객체를 재생성하고 닫는 대신 RAM에 유지합니다. 물론 개체가 GC가 될 때까지 개체가 열릴 때까지 개체가 해제되지는 않지만 특정 시간에 리소스가 해제되도록 보장 할 필요는 없습니다.
Kröw

8

파이널 라이저에 의존하지 않는 가장 큰 이유 중 하나는 파이널 라이저에서 정리하려고하는 대부분의 리소스가 매우 제한되어 있기 때문입니다. 가비지 컬렉터는 무언가를 릴리스 할 수 있는지 여부를 결정하는 참조를 순회하기 때문에 너무 자주 실행됩니다. 이것은 물체가 실제로 파괴되기 전에 '시간이 걸릴 수 있음'을 의미합니다. 예를 들어, 수명이 짧은 데이터베이스 연결을 여는 많은 오브젝트가있는 경우, 종료자를 사용하여 이러한 연결을 정리하면 가비지 콜렉터가 마지막으로 실행되고 완료된 연결이 해제 될 때까지 연결 풀이 소모 될 수 있습니다. 그런 다음 대기로 인해 대기중인 요청의 많은 백 로그가 발생하여 연결 풀이 빨리 소모됩니다. 그것'

또한 try-with-resources를 사용하면 완료시 '닫을 수있는'개체를 쉽게 닫을 수 있습니다. 이 구문에 익숙하지 않은 경우 https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html 을 확인하는 것이 좋습니다.


8

리소스를 릴리스하기 위해 종료 자에게 맡기는 것이 일반적으로 나쁜 생각 인 이유 외에도, 완료 가능한 개체에는 성능 오버 헤드가 있습니다.

에서 자바 이론과 실습 : 쓰레기 수집 및 성능 (브라이언 게츠), 종료 자 당신의 친구되지 않습니다 :

종료자가있는 객체 (사소한 finalize () 메서드가있는 객체)는 종료자가없는 객체와 비교하여 상당한 오버 헤드가 있으므로 드물게 사용해야합니다. 최종화 가능 객체는 할당 속도가 느리고 수집 속도가 느립니다. 할당시 JVM은 완료 가능 오브젝트를 가비지 콜렉터에 등록해야하며 (최소한 HotSpot JVM 구현에서) 완료 가능 오브젝트는 대부분의 다른 오브젝트보다 느린 할당 경로를 따라야합니다. 마찬가지로, 최종화 가능한 객체도 수집 속도가 느립니다. 종료 가능한 객체를 회수하려면 가비지 수집주기 (최상의 경우)가 2 회 이상 필요하며 가비지 수집기는 마무리 작업을 호출하기 위해 추가 작업을 수행해야합니다. 결과적으로 객체를 할당하고 수집하는 데 더 많은 시간이 걸리고 가비지 수집기에 더 많은 압력이 가해집니다. 도달 할 수없는 최종 개체에 사용되는 메모리가 더 오래 유지되기 때문입니다. 이를 파이널 라이저가 예측 가능한 기간 또는 전혀 실행하지 않을 수 있다는 사실과 결합하면 최종화가 올바른 도구로 사용되는 상황이 상대적으로 적다는 것을 알 수 있습니다.


훌륭한 지적입니다. 의 성능 오버 헤드를 피하는 수단을 제공하는 내 대답 을 참조하십시오 finalize().
Mike Nakis

7

내가 피하는 가장 좋아하는 이유 Object.finalize는 객체가 예상 한 후에 완성 될 수는 없지만 예상 하기 전에 완성 될 수 있기 때문입니다. 문제는 Java가 더 이상 도달 할 수 없다고 결정하면 범위가 종료되기 전에 여전히 범위에있는 오브젝트를 완료 할 수 없다는 것입니다.

void test() {
   HasFinalize myObject = ...;
   OutputStream os = myObject.stream;

   // myObject is no-longer reachable at this point, 
   // even though it is in scope. But objects are finalized
   // based on reachability.
   // And since finalization is on another thread, it 
   // could happen before or in the middle of the write .. 
   // closing the stream and causing much fun.
   os.write("Hello World");
}

자세한 내용은 이 질문 을 참조하십시오. 더 재미있는 점은 핫스팟 최적화가 시작된 후에 만이 결정을 내릴 수 있기 때문에 디버깅이 어렵다는 것입니다.


1
문제는 그 HasFinalize.stream자체가 별도로 최종화 가능한 객체 여야한다는 것입니다 . 즉, 마무리 작업을 HasFinalize마무리하거나 정리하려고해서는 안됩니다 stream. 또는 필요한 경우 stream액세스 할 수 없어야합니다 .
acelent


4

객체에 close () 구현이 있는지 지속적으로 확인해야하며 과거에 어떤 시점에서 객체에 대한 호출을 놓친 것으로 확신합니다.

Eclipse에서 Closeable/ 구현하는 것을 닫는 것을 잊을 때마다 경고가 표시 AutoCloseable됩니다. 이것이 Eclipse 일인지 공식 컴파일러의 일부인지 확실하지 않지만 비슷한 정적 분석 도구를 사용하여 도움을 줄 수 있습니다. 예를 들어 FindBugs는 리소스를 닫는 것을 잊었는지 확인하는 데 도움이 될 수 있습니다.


1
좋은 생각 AutoCloseable입니다. 리소스를 사용하여 리소스를 쉽게 관리 할 수 ​​있습니다. 이것은 문제의 몇 가지 주장을 무효화합니다.

2

첫 번째 질문으로 :

객체를 할당 해제하기위한 적절한 시간이 개발자가 아닌시기를 결정하는 것은 VM 구현과 GC에 달려 있습니다. 언제 Object.finalize()부름을받는 것이 중요 합니까?

JVM은 오브젝트에 할당 된 스토리지를 재 확보해야 할 시점을 판별합니다. 이 시간이에 수행하려는 리소스 정리가 반드시 필요한 시간은 아닙니다 finalize(). 이것은 SO에 대한 "Java 8에서 강력하게 도달 가능한 객체에서 호출 된 finalal ()" 질문에 설명되어 있습니다. 거기에서 close()메소드가 메소드를 호출 finalize()했지만 동일한 오브젝트로 스트림에서 읽으려는 시도가 여전히 보류 중입니다. 따라서 finalize()늦게까지 불려지 는 잘 알려진 가능성 외에도 너무 일찍 불릴 가능성이 있습니다.

두 번째 질문의 전제 :

일반적으로, 내가 틀렸다면 나를 수정하십시오 Object.finalize().GC를 실행할 기회를 얻기 전에 응용 프로그램이 종료 된 때만 호출되지 않습니다.

단순히 잘못되었습니다. JVM이 마무리를 전혀 지원할 필요는 없습니다. 응용 프로그램이 종료 될 것이라는 가정하에 여전히 "완료되기 전에 응용 프로그램이 종료되었습니다"로 해석 할 수 있으므로 완전히 잘못된 것은 아닙니다.

그러나 원래 진술의 "GC"와 "완료"라는 용어의 작은 차이점에 유의하십시오. 가비지 콜렉션은 마무리와 다릅니다. 메모리 관리에서 개체에 도달 할 수없는 것으로 감지되면 공간을 회수 할 수 있습니다 (특별한 finalize()방법이 없거나 마무리가 단순히 지원되지 않거나 마무리를 위해 개체를 큐에 넣을 수 있음). 따라서 가비지 수집주기가 완료되었다고해서 파이널 라이저가 실행되는 것은 아닙니다. 큐가 처리 될 때 또는 전혀 발생하지 않을 때 나중에 발생할 수 있습니다.

이 시점은 또한 마무리 지원 기능이있는 JVM에서도 리소스 정리에 의존하는 것이 위험한 이유이기도합니다. 가비지 콜렉션은 메모리 관리의 일부이므로 메모리 요구에 의해 트리거됩니다. 전체 런타임 동안 충분한 메모리가 있기 때문에 가비지 수집이 실행되지 않을 가능성이 있습니다 (여전히 "GC가 실행될 기회를 갖기 전에 응용 프로그램이 종료되었습니다"라는 설명에 해당됨). GC가 실행될 수도 있지만 나중에 메모리가 재 확보되어 파이널 라이저 큐가 처리되지 않을 수도 있습니다.

다시 말해, 이런 방식으로 관리되는 기본 리소스는 여전히 메모리 관리의 외계인입니다. OutOfMemoryError메모리를 확보하기위한 충분한 시도 후에 만을 발생 시키는 것이 보장되지만 기본 자원 및 마무리에는 적용되지 않습니다. 종료자가 실행 된 경우 종료 큐에 이러한 리소스를 사용할 수있는 개체로 가득 찬 상태에서 리소스가 부족하여 파일을 열지 못할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.