finalize 함수는 Object 클래스에 있는 메서드입니다. 공식 문서에서는 아래와 같이 설명하고 있습니다.

 

 여기서 중요한 것은, gc가 해당 object를 참조하는 레퍼런스가 없을 때 Call 한다는 것입니다. 예제 프로그램을 몇 개 보면서 이해해 보도록 하겠습니다.

 


 1번째 프로그램입니다.

 Obj는 Object를 상속받습니다. 따라서, finalize를 오버라이딩 할 수 있습니다. finalize가 gc에 의해서 호출이 되면, Common.rm이 증가하게 되는데요.

 

 

 새로운 Obj 객체 1000만개를 생성하기만 했습니다. 그리고 80초동안 sleep을 시키고, Common.rm을 출력하였습니다.

 

 

 1000만이 출력되지 않았다는 것이 중요합니다.

 

 

 100개의 Obj를 새로 생성한 다음에 Common.rm을 출력해 봅시다.

 

 

 16번째 줄의 i<10000000을 i<100으로 바꾸면 될 겁니다.

 

 

 그랬더니 이번에는 0이 나왔습니다. 100과 0은 다르다는 것이 중요합니다.

 

 

 호출 스택을 보았더니, 스레드가 몇 개 있는데요. main Thread가 있고, Finalizer Thread가 있다는 것을 알 수 있습니다. 즉, 객체를 정리하기 전에 호출되는 finalizer를 호출하는 T가 Finalizer라는 것을 유추할 수 있습니다. 정말 그런지, Finalizer에서 Thread와 우선 순위를 출력하게 해 보겠습니다.

 

 Thread의 toString은 Thread의 이름, 우선순위, 그룹 이름을 출력합니다.

 

 

 9번째 줄의 System.out.println을 불렀던 주체는 Finalizer임을 알 수 있습니다.

 


 여기까지 내용을 정리해 보면, 문제의 finalize는 Main 스레드가 아닌, 다른 스레드가 호출을 한다는 것입니다. 그리고, 해당 메소드가 언제 호출될 지 모른다는 것입니다. gc가 object가 도달 가능한 객체인지, 그렇지 않은지 판단하는 것은 그리 가볍지 않은데, 그 작업을 0.00000001초 간격으로 계속 하라고 할 수는 없는 노릇이고요. 그러면 System.gc()를 호출하면 안 되나요?

 

 다시 말해서, finalize 메소드가 호출이 될 거라는 가정을 하면 안 된다는 것입니다. 이제 프로그램 1을 조금 바꿔보겠습니다.

 

 

 refObj가 있습니다. 이것은 안에 Obj 참조 변수를 들고 있습니다.

 

 

 다음에, 이것은 Obj 클래스입니다.

 

 

 이제, 100만개의 refObj 객체를 생성해 보겠습니다. 이것을 생성하기 위해서는 또 각각의 refObj를 생성할 때 마다, Obj 객체 하나를 생성해서, refObj 객체의 생성자의 인자로 넣어야 겠군요.

 

 

 여전히 경우에 따라서, F : Thread[...]가 출력됨을 알 수 있는데요.

 

 28번째 줄을 보면, refObj 객체를 생성했고, 그 안의 field가 Obj 객체를 가리키고 있습니다. 그런데, 중요한 것은, 그 어디에서도 새로 생성된 refObj에 대한, 참조값을 저장하지 않습니다.

 

 제가 맨 처음에 언급한, there are no reference to the object의 의미는, root로부터 해당 오브젝트까지 들어오는 참조가 없을 때를 의미합니다. 즉, 위 그림과 같이, refObj가 Obj 객체를 참조하고 있지만, 정작 root에서 도달 가능하지 않은 경우에는 gc의 대상이 되고, gc의 수집대상이 될 수 밖에 없습니다. 이것에 대한 이야기는 이전에 링크에서 언급한 적이 있었습니다.

 

 그러므로, call이 되는 조건을 만족하므로, Obj가 정리 되었다면, F:[...]가 출력이 될 겁니다. 물론 이것은 gc가 '해당 오브젝트가 가비지다' 라고 판단을 했을 때 이야기입니다. 그 판단을 언제 할 지는 모르기 때문에, 자원들을 정리해야 할 때 (ex. stream을 close) 이 메서드를 믿으시면 곤란합니다.