>  기사  >  Java  >  Java의 가비지 수집 메커니즘과 finalize 메서드의 역할을 간략하게 이해합니다.

Java의 가비지 수집 메커니즘과 finalize 메서드의 역할을 간략하게 이해합니다.

高洛峰
高洛峰원래의
2017-01-17 15:45:491248검색

가비지 수집기가 객체를 재활용하려면 먼저 이 클래스의 finalize 메서드를 호출해야 합니다(이 결론을 확인하는 프로그램을 작성할 수 있음). 일반적으로 순수 Java로 작성된 클래스는 이 메서드를 재정의할 필요가 없습니다. 객체는 이미 기본적으로 특수 기능(객체 공간 트리 등과 같은 많은 기능을 포함하는)을 구현하려는 경우가 아니면 기본적으로 구현했습니다.
그러나 Java 이외의 코드로 작성된 클래스(예: JNI, C++의 새로운 방법으로 할당된 메모리)의 경우 가비지 수집기가 이러한 부분을 올바르게 재활용할 수 없으므로 이를 달성하려면 기본 방법을 재정의해야 합니다. 목적. 메모리의 이 부분을 올바르게 릴리스하고 재활용합니다(예: C++에서는 삭제가 필요함).
간단히 말하면 finalize는 소멸자와 동일하며 가비지 수집기가 객체를 재활용할 때 호출되는 첫 번째 메서드입니다. 그러나 Java의 가비지 수집 메커니즘은 자동으로 이러한 작업을 수행할 수 있으므로 일반적으로 수동으로 이를 해제할 필요가 없습니다.

개체를 실행 취소할 때 일부 작업을 완료해야 하는 경우가 있습니다. 예를 들어, 객체가 파일 핸들이나 창 문자 글꼴과 같은 비Java 리소스를 처리하는 경우 객체가 삭제되기 전에 이러한 리소스가 해제되는지 확인해야 합니다. 이러한 상황을 처리하기 위해 Java는 마무리라는 메커니즘을 제공합니다. 이 메커니즘을 사용하면 가비지 수집기에 의해 개체가 해제되려고 할 때 수행되는 특수 작업을 정의할 수 있습니다.
클래스에 종료자를 추가하려면 finalize() 메서드만 정의하면 됩니다. 이 메소드는 Java가 이 클래스의 객체를 재활용할 때 호출됩니다. finalize() 메서드에서는 객체가 소멸되기 전에 수행해야 하는 작업을 지정합니다. 가비지 수집은 주기적으로 실행되어 개체가 더 이상 실행 상태에서 참조되지 않거나 다른 개체를 통해 간접적으로 참조되지 않는지 확인합니다. 객체가 해제되기 직전에 Java 런타임 시스템은 객체의 finalize() 메서드를 호출합니다.

finalize() 메소드의 일반적인 형식은 다음과 같습니다.

  protected void finalize( )
{
// finalization code here
}

여기서 protected 키워드는 클래스 외부에 정의된 코드가 finalize() 식별자에 액세스하는 것을 방지합니다. 이 식별자와 기타 식별자는 7장에 설명되어 있습니다.

가비지 컬렉션 직전에 finalize()가 호출된다는 점을 이해하는 것이 중요합니다. 예를 들어 객체가 해당 범위를 초과하면 finalize()가 호출되지 않습니다. 이는 finalize( )가 언제 호출되는지, 심지어 호출되는지 알 방법이 없음을 의미합니다. 따라서 프로그램은 객체가 사용하는 시스템 리소스를 해제하는 다른 방법을 제공해야 하며 finalize()를 사용하여 프로그램의 정상적인 작업을 완료할 수 없습니다.

참고: C++에 익숙하다면 C++에서 객체가 범위를 벗어나기 직전에 호출되는 클래스에 대한 실행 취소 함수(소멸자)를 정의할 수 있다는 것을 알고 계실 것입니다. Java는 이 아이디어를 지원하지 않으며 실행 취소 기능도 제공하지 않습니다. finalize() 메서드는 실행 취소 기능에만 가깝습니다. Java에 대한 경험이 많아지면 Java가 가비지 수집 하위 시스템을 사용하기 때문에 실행 취소 기능을 사용할 필요가 거의 없다는 것을 알게 될 것입니다.


finalize의 작동 원리는 다음과 같아야 합니다. 가비지 수집기가 객체가 차지한 저장 공간을 해제할 준비가 되면 먼저 finalize()를 호출하고 다음 가비지 수집 프로세스 중에만 호출합니다. , 실제로 객체의 메모리를 회수합니까? 따라서 finalize()를 사용하면 가비지 수집 중에 몇 가지 중요한 정리 작업을 수행할 수 있습니다.

finalize()는 언제 호출되나요? 세 가지 상황

System.gc()를 실행할 때와 같이 모든 개체가 Garbage Collection일 때 자동으로 호출됩니다.

finalize 메서드는 프로그램 종료 시 각 개체에 대해 한 번씩 호출됩니다.

명시적으로 finalize 메소드 호출

또한 일반적인 상황에서는 객체가 시스템에 의해 쓸모없는 정보로 수집되면 자동으로 finalize()가 호출되지만 jvm은 그렇지 않습니다. finalize()를 호출해야 한다는 것이 보장됩니다. 즉, finalize() 호출이 불확실합니다. 이것이 바로 sun이 finalize() 사용을 권장하지 않는 이유입니다.

때때로 객체를 실행 취소할 때 일부 작업을 완료해야 합니다. 예를 들어, 객체가 파일 핸들이나 창 문자 글꼴과 같은 비Java 리소스를 처리하는 경우 객체가 삭제되기 전에 이러한 리소스가 해제되는지 확인해야 합니다. 이러한 상황을 처리하기 위해 Java는 마무리라는 메커니즘을 제공합니다. 이 메커니즘을 사용하면 가비지 수집기에 의해 개체가 해제되려고 할 때 수행되는 특수 작업을 정의할 수 있습니다.

클래스에 종료자를 추가하려면 finalize() 메서드만 정의하면 됩니다. 이 메소드는 Java가 이 클래스의 객체를 재활용할 때 호출됩니다. finalize() 메서드에서는 객체가 소멸되기 전에 수행해야 하는 작업을 지정합니다. 가비지 수집은 주기적으로 실행되어 개체가 더 이상 실행 상태에서 참조되지 않거나 다른 개체를 통해 간접적으로 참조되지 않는지 확인합니다. 객체가 해제되기 직전에 Java 런타임 시스템은 객체의 finalize() 메서드를 호출합니다.

finalize() 메서드의 일반적인 형식은 다음과 같습니다.

protected void finalize( )
{
// finalization code here
}

그 중 protected 키워드는 클래스 외부에 정의된 코드가 finalize() 식별자에 액세스하는 것을 방지합니다. 이 식별자와 기타 식별자는 7장에 설명되어 있습니다.

finalize()는 가비지 수집 직전에 호출된다는 점을 이해하는 것이 중요합니다. 예를 들어 객체가 해당 범위를 초과하면 finalize()가 호출되지 않습니다. 이는 finalize( )가 언제 호출되는지, 심지어 호출되는지 알 방법이 없음을 의미합니다. 따라서 프로그램은 객체가 사용하는 시스템 리소스를 해제하는 다른 방법을 제공해야 하며 finalize()를 사용하여 프로그램의 정상적인 작업을 완료할 수 없습니다.

참고: C++에 익숙하다면 C++에서 객체가 범위를 벗어나기 직전에 호출되는 클래스에 대한 실행 취소 함수(소멸자)를 정의할 수 있다는 것을 알고 계실 것입니다. Java는 이 아이디어를 지원하지 않으며 실행 취소 기능도 제공하지 않습니다. finalize() 메서드는 실행 취소 기능에만 가깝습니다. Java에 대한 경험이 많아지면 Java가 가비지 수집 하위 시스템을 사용하기 때문에 실행 취소 기능을 사용할 필요가 거의 없다는 것을 알게 될 것입니다.

가비지 수집기는 메모리 이외의 작업을 처리하지 않기 때문에 가비지 수집기가 사용자 정의 비메모리 정리 작업을 수행하기 위해 객체의 finalize 메서드를 자동으로 호출합니다. 따라서 때때로 사용자는 파일, 포트 등 비메모리 리소스 처리와 같은 일부 정리 방법을 정의해야 합니다.

1. JVM의 GC 개요
 
가비지 수집 메커니즘인 GC는 더 이상 사용되지 않는 객체를 해제하는 데 사용되는 jvm이 차지하는 메모리를 말합니다. Java 언어는 JVM에 gc가 필요하지 않으며 gc 작동 방식도 지정하지 않습니다. 그러나 일반적으로 사용되는 JVM에는 GC가 있으며 대부분의 GC는 유사한 알고리즘을 사용하여 메모리를 관리하고 수집 작업을 수행합니다.
 
가비지 수집 알고리즘과 실행 프로세스를 완전히 이해한 후에만 성능을 효과적으로 최적화할 수 있습니다. 일부 가비지 수집은 특수 애플리케이션에 특화되어 있습니다. 예를 들어, 실시간 애플리케이션은 주로 가비지 수집 중단을 방지하는 데 중점을 두는 반면, 대부분의 OLTP 애플리케이션은 전반적인 효율성에 중점을 둡니다. JVM에서 지원하는 애플리케이션 워크로드와 가비지 수집 알고리즘을 이해하고 나면 가비지 수집기를 최적화하고 구성할 수 있습니다.
 
가비지 수집의 목적은 더 이상 사용하지 않는 개체를 제거하는 것입니다. gc는 객체가 라이브 객체에 의해 참조되는지 여부를 확인하여 객체를 수집할지 여부를 결정합니다. GC는 먼저 객체를 수집할 준비가 되었는지 확인해야 합니다. 일반적으로 사용되는 두 가지 방법은 참조 계산과 객체 참조 순회입니다.
 
 1.1. 참조 카운팅
 
  참조 카운팅은 특정 객체에 대한 모든 참조 수를 저장합니다. 즉, 애플리케이션이 참조를 생성하고 해당 참조가 범위를 벗어나면 jvm이 참조 번호를 적절하게 늘리거나 줄여야 합니다. 객체에 대한 참조 수가 0이 되면 가비지 수집이 발생할 수 있습니다.
 
 1.2.객체 참조 순회
 
초기 JVM은 참조 카운팅을 사용했지만 현재 대부분의 JVM은 객체 참조 순회를 사용합니다. 객체 참조 순회는 객체 세트에서 시작하여 객체 그래프 전체의 각 링크를 따라 도달 가능한 객체를 재귀적으로 결정합니다. 이러한 루트 개체 중 하나(적어도 하나)에서 개체에 연결할 수 없는 경우 해당 개체는 가비지 수집됩니다. 객체 순회 단계에서 GC는 도달할 수 없는 객체를 삭제하기 위해 어떤 객체에 도달할 수 있는지 기억해야 합니다. 이를 객체 표시라고 합니다.
 
다음 단계에서 gc는 연결할 수 없는 개체를 삭제합니다. 삭제할 때 일부 GC는 단순히 스택을 스캔하고 표시되지 않은 개체를 삭제한 다음 메모리를 해제하여 새 개체를 생성합니다. 이 접근 방식의 문제점은 메모리가 여러 개의 작은 세그먼트로 분할되어 새 객체에 비해 충분히 크지 않지만 그 조합이 크다는 것입니다. 따라서 많은 GC는 메모리의 개체를 재구성하고 압축하여 사용 가능한 공간을 형성할 수 있습니다.
 
이러한 이유로 GC는 다른 활동을 중단해야 합니다. 이 접근 방식은 모든 애플리케이션 관련 작업이 중지되고 gc만 실행됨을 의미합니다. 결과적으로 응답 시간 동안 많은 혼합 요청이 추가되고 삭제됩니다. 또한 더 복잡한 GC가 지속적으로 추가되거나 동시에 실행되어 애플리케이션 중단을 줄이거나 제거합니다. 일부 gcs는 단일 스레드를 사용하여 이 작업을 완료하는 반면 다른 gcs는 멀티스레딩을 사용하여 효율성을 높입니다.
 
2. 여러 가지 가비지 수집 메커니즘
 
 2.1. 마크 스윕 수집기
 
  이 수집기는 먼저 객체 그래프를 순회하고 도달 가능한 객체를 표시한 다음 스택을 스캔하여 표시되지 않은 객체를 찾습니다. 객체를 삭제하고 메모리를 해제합니다. 이 유형의 수집기는 일반적으로 단일 스레드를 사용하여 작업하고 다른 작업을 중지합니다.
 
 2.2.Mark-Compact Collector
 
Mark-Sweep-Compact Collector라고도 하며 Mark-Sweep Collector와 동일한 마킹 단계를 갖습니다. 두 번째 단계에서는 스택을 압축하기 위해 표시된 개체가 스택의 새 영역에 복사됩니다. 이 수집기는 다른 작업도 중지합니다.
 
 2.3.복사 컬렉터
 
  이 컬렉터는 스택을 하프스페이스라고도 불리는 두 개의 도메인으로 나눕니다. 매번 공간의 절반만 사용되며 jvm에 의해 생성된 새 객체는 공간의 나머지 절반에 배치됩니다. gc가 실행되면 도달 가능한 객체를 공간의 나머지 절반에 복사하여 스택을 압축합니다. 이 방법은 수명이 짧은 개체에 적합합니다. 수명이 긴 개체를 계속 복사하면 효율성이 떨어집니다.
 
 2.4.증분 수집기
 
  증분 수집기는 스택을 여러 도메인으로 나누어 한 번에 한 도메인에서만 가비지를 수집합니다. 이로 인해 약간의 애플리케이션 중단이 발생합니다.
 
 2.5.세대 컬렉터
 
  이 컬렉터는 스택을 두 개 이상의 도메인으로 나누어 수명이 다른 객체를 저장합니다. jvm에 의해 생성된 새 객체는 일반적으로 필드 중 하나에 배치됩니다. 시간이 지남에 따라 살아남은 개체는 유용한 수명을 얻고 수명이 더 긴 도메인으로 이전됩니다. 세대별 수집기는 성능을 최적화하기 위해 다양한 도메인에 대해 다양한 알고리즘을 사용합니다.
 
 2.6. 동시 수집기
 
  동시 수집기는 애플리케이션과 동시에 실행됩니다. 이러한 수집기는 일반적으로 특정 작업을 완료하기 위해 특정 시점(압축 등)에서 다른 작업을 중지해야 하지만, 다른 애플리케이션이 다른 백그라운드 작업을 수행할 수 있기 때문에 다른 처리를 중단하는 실제 시간이 크게 줄어듭니다.
 
 2.7. 병렬 수집기
 
  병렬 수집기는 일부 기존 알고리즘을 사용하고 여러 스레드를 사용하여 작업을 병렬로 수행합니다. 다중 CPU 시스템에서 다중 스레딩 기술을 사용하면 Java 애플리케이션의 확장성을 크게 향상시킬 수 있습니다.

3. 객체 파기 과정

在对象的销毁过程中,按照对象的finalize的执行情况,可以分为以下几种,系统会记录对象的对应状态: 
unfinalized 没有执行finalize,系统也不准备执行。 
finalizable 可以执行finalize了,系统会在随后的某个时间执行finalize。 
finalized 该对象的finalize已经被执行了。

GC怎么来保持对finalizable的对象的追踪呢。GC有一个Queue,叫做F-Queue,所有对象在变为finalizable的时候会加入到该Queue,然后等待GC执行它的finalize方法。

这时我们引入了对对象的另外一种记录分类,系统可以检查到一个对象属于哪一种。 
reachable 从活动的对象引用链可以到达的对象。包括所有线程当前栈的局部变量,所有的静态变量等等。 
finalizer-reachable 除了reachable外,从F-Queue可以通过引用到达的对象。 
unreachable 其它的对象。

来看看对象的状态转换图。 

Java의 가비지 수집 메커니즘과 finalize 메서드의 역할을 간략하게 이해합니다.

好大,好晕,慢慢看。

1 首先,所有的对象都是从Reachable+Unfinalized走向死亡之路的。

2 当从当前活动集到对象不可达时,对象可以从Reachable状态变到F-Reachable或者Unreachable状态。

3 当对象为非Reachable+Unfinalized时,GC会把它移入F-Queue,状态变为F-Reachable+Finalizable。

4 好了,关键的来了,任何时候,GC都可以从F-Queue中拿到一个Finalizable的对象,标记它为Finalized,然后执行它的finalize方法,由于该对象在这个线程中又可达了,于是该对象变成Reachable了(并且Finalized)。而finalize方法执行时,又有可能把其它的F-Reachable的对象变为一个Reachable的,这个叫做对象再生。

5 当一个对象在Unreachable+Unfinalized时,如果该对象使用的是默认的Object的finalize,或者虽然重写了,但是新的实现什么也不干。为了性能,GC可以把该对象之间变到Reclaimed状态直接销毁,而不用加入到F-Queue等待GC做进一步处理。

6 从状态图看出,不管怎么折腾,任意一个对象的finalize只至多执行一次,一旦对象变为Finalized,就怎么也不会在回到F-Queue去了。当然没有机会再执行finalize了。

7 当对象处于Unreachable+Finalized时,该对象离真正的死亡不远了。GC可以安全的回收该对象的内存了。进入Reclaimed。


对象重生的例子 

class C { 
  static A a; 
} 
  
class A { 
  B b; 
  
  public A(B b) { 
    this.b = b; 
  } 
  
  @Override
  public void finalize() { 
    System.out.println("A finalize"); 
    C.a = this; 
  } 
} 
  
class B { 
  String name; 
  int age; 
  
  public B(String name, int age) { 
    this.name = name; 
    this.age = age; 
  } 
  
  @Override
  public void finalize() { 
    System.out.println("B finalize"); 
  } 
  
  @Override
  public String toString() { 
    return name + " is " + age; 
  } 
} 
  
public class Main { 
  public static void main(String[] args) throws Exception { 
    A a = new A(new B("allen", 20)); 
    a = null; 
  
    System.gc(); 
    Thread.sleep(5000); 
    System.out.println(C.a.b); 
  } 
}

期待输出 

A finalize 
B finalize 
allen is 20

但是有可能失败,源于GC的不确定性以及时序问题,多跑几次应该可以有成功的。详细解释见文末的参考文档。

    3.1对象的finalize的执行顺序

所有finalizable的对象的finalize的执行是不确定的,既不确定由哪个线程执行,也不确定执行的顺序。 
考虑以下情况就明白为什么了,实例a,b,c是一组相互循环引用的finalizable对象。

    3.2何时及如何使用finalize

从以上的分析得出,以下结论。 
(1) 最重要的,尽量不要用finalize,太复杂了,还是让系统照管比较好。可以定义其它的方法来释放非内存资源。 
(2) 如果用,尽量简单。 
(3) 如果用,避免对象再生,这个是自己给自己找麻烦。 
(4) 可以用来保护非内存资源被释放。即使我们定义了其它的方法来释放非内存资源,但是其它人未必会调用该方法来释放。在finalize里面可以检查一下,如果没有释放就释放好了,晚释放总比不释放好。 
(5) 即使对象的finalize已经运行了,不能保证该对象被销毁。要实现一些保证对象彻底被销毁时的动作,只能依赖于java.lang.ref里面的类和GC交互了。 

更多Java의 가비지 수집 메커니즘과 finalize 메서드의 역할을 간략하게 이해합니다.相关文章请关注PHP中文网!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.