ホームページ  >  記事  >  Java  >  Java のガベージ コレクション メカニズムと Finalize メソッドの役割を簡単に理解する

Java のガベージ コレクション メカニズムと Finalize メソッドの役割を簡単に理解する

高洛峰
高洛峰オリジナル
2017-01-17 15:45:491250ブラウズ

ガベージ コレクターがオブジェクトをリサイクルしたい場合は、最初にこのクラスの Finalize メソッドを呼び出す必要があります (この結論を検証するプログラムを作成できます)。通常、純粋な Java で書かれたクラスは、Object がこのメソッドをオーバーライドする必要はありません。特別な関数を実装したくない場合 (これには、オブジェクト スペース ツリーなどの多くのものが関係します) は、すでにデフォルトが実装されています。
ただし、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() メソッドは、undo 関数の機能に近いだけです。 Java の経験が増えると、Java はガベージ コレクション サブシステムを使用するため、元に戻す機能を使用する必要性がほとんどないことがわかります。


finalize の動作原理は次のようになります。ガベージ コレクターがオブジェクトによって占有されているストレージ領域を解放する準備ができると、最初に Finalize() が呼び出され、次のガベージ コレクション プロセス中にのみ、オブジェクトのメモリが実際に解放されます。したがって、finalize() を使用すると、ガベージ コレクション中に重要なクリーニング作業を実行できます。

finalize() は、次の 3 つの状況で呼び出されます。 System When .gc() の実行など。


finalize メソッドは、プログラムの終了時にオブジェクトごとに 1 回呼び出されます。

明示的に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() メソッドは、undo 関数の機能に近いだけです。 Java の経験が増えると、Java はガベージ コレクション サブシステムを使用するため、元に戻す機能を使用する必要性がほとんどないことがわかります。

ガベージ コレクターはメモリ以外のものを処理しないため、ガベージを収集するときにオブジェクトの Finalize メソッドを自動的に呼び出して、ユーザー定義のメモリ以外のクリーンアップ作業を実行します。したがって、場合によっては、ファイルやポートなどのメモリ以外のリソースの処理など、いくつかのクリーニング方法を定義する必要があります。

1. JVMのgcの概要

ガベージコレクションメカニズムであるGCとは、使用されなくなったオブジェクトを解放するために使用されるjvmが占有するメモリを指します。 Java 言語では、JVM に gc が必要ではなく、gc の動作方法も指定されていません。ただし、一般的に使用される JVM には GC があり、ほとんどの GC は同様のアルゴリズムを使用してメモリを管理し、コレクション操作を実行します。

ガベージコレクションのアルゴリズムと実行プロセスを完全に理解した後でのみ、そのパフォーマンスを効果的に最適化できます。ガベージ コレクションの中には、特殊なアプリケーションに特化したものもあります。たとえば、リアルタイム アプリケーションは主にガベージ コレクションの中断を回避することに重点を置いていますが、ほとんどの OLTP アプリケーションは全体的な効率に重点を置いています。アプリケーションのワークロードと JVM でサポートされるガベージ コレクション アルゴリズムを理解したら、ガベージ コレクターを最適化して構成できます。

ガベージコレクションの目的は、使用されなくなったオブジェクトを削除することです。 gc は、オブジェクトがライブ オブジェクトによって参照されているかどうかを判断することによって、オブジェクトを収集するかどうかを決定します。 GC では、まずオブジェクトが収集の準備ができているかどうかを判断する必要があります。よく使用される 2 つの方法は、参照カウントとオブジェクト参照トラバーサルです。

1.1. 参照カウント

参照カウントは、特定のオブジェクトへのすべての参照の数を保存します。つまり、アプリケーションが参照を作成し、その参照がスコープ外になった場合、jvm は参照数を適切に増減する必要があります。 。オブジェクトへの参照の数が 0 に達すると、ガベージ コレクションが発生する可能性があります。

1.2. オブジェクト参照トラバーサル

初期の JVM は参照カウントを使用していましたが、現在ではほとんどの JVM がオブジェクト参照トラバーサルを使用しています。オブジェクト参照トラバーサルは、オブジェクトのセットから開始され、オブジェクト グラフ全体の各リンクに沿って到達可能なオブジェクトを再帰的に決定します。これらのルート オブジェクトの 1 つ (少なくとも 1 つ) からオブジェクトに到達できない場合、そのオブジェクトはガベージ コレクションされます。オブジェクト トラバーサル フェーズ中、GC は、到達不可能なオブジェクトを削除するために、どのオブジェクトが到達可能であるかを記憶する必要があります。これは、オブジェクトのマーキングと呼ばれます。

次のステップは、gc が到達不能なオブジェクトを削除することです。削除する場合、一部の GC は単純にスタックをスキャンし、マークされていないオブジェクトを削除し、メモリを解放して新しいオブジェクトを生成します。これはスイープと呼ばれます。このアプローチの問題は、メモリが多数の小さなセグメントに分割され、新しいオブジェクトを処理できるほど大きくないにもかかわらず、その組み合わせが大きいことです。したがって、多くの GC はメモリ内のオブジェクトを再編成し、それらを圧縮して使用可能な領域を形成できます。

そのためには、GCは他のアクティビティを停止する必要があります。このアプローチは、アプリケーション関連のすべての作業が停止し、gc のみが実行されることを意味します。その結果、応答時間中に多くの混合リクエストが追加および削除されます。さらに、アプリケーションの割り込みを軽減またはクリアするために、より複雑な GC が継続的に追加または同時に実行されます。一部の gcs はこの作業を完了するためにシングル スレッドを使用しますが、他の gc は効率を高めるためにマルチスレッドを使用します。

2. いくつかのガベージコレクションメカニズム

2.1. マークスイープコレクター

このコレクターは、まずオブジェクトグラフを走査して、到達可能なオブジェクトをマークし、次にマークされていないオブジェクトのスタックをスキャンし、それらのメモリーを解放します。このタイプのコレクターは通常、単一のスレッドを使用して動作し、他の操作を停止します。

2.2. Mark-Compact Collector

Mark-Sweet-Compact Collector とも呼ばれ、Mark-Scratch Collector と同じマーキングフェーズを持ちます。第 2 フェーズでは、スタックを圧縮するために、マークされたオブジェクトがスタックの新しい領域にコピーされます。このコレクターは他の操作も停止します。

2.3. コピーコレクター

このコレクターはスタックを 2 つのドメイン (しばしばハーフスペースと呼ばれます) に分割します。毎回スペースの半分だけが使用され、jvm によって生成された新しいオブジェクトはスペースの残りの半分に配置されます。 gc が実行されると、到達可能なオブジェクトがスペースの残りの半分にコピーされ、それによってスタックが圧縮されます。この方法は、存続期間の短いオブジェクトに適しています。存続期間の長いオブジェクトを継続的にコピーすると、効率が低下します。

2.4. インクリメンタルコレクター

インクリメンタルコレクターはスタックを複数のドメインに分割し、一度に 1 つのドメインからのみガベージを収集します。これにより、アプリケーションに軽度の中断が発生します。

2.5. 世代別コレクター

このコレクターは、スタックを 2 つ以上のドメインに分割して、異なるライフタイムを持つオブジェクトを格納します。 jvm によって生成された新しいオブジェクトは、通常、フィールドの 1 つに配置されます。時間が経つにつれて、生き残ったオブジェクトは耐用年数を獲得し、より長く存続するドメインに移されます。世代別コレクターは、ドメインごとに異なるアルゴリズムを使用してパフォーマンスを最適化します。

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 までご連絡ください。