Heim  >  Artikel  >  Java  >  Verstehen Sie kurz den Garbage-Collection-Mechanismus von Java und die Rolle der Finalize-Methode

Verstehen Sie kurz den Garbage-Collection-Mechanismus von Java und die Rolle der Finalize-Methode

高洛峰
高洛峰Original
2017-01-17 15:45:491319Durchsuche

Wenn der Garbage Collector ein Objekt recyceln möchte, muss er zuerst die Finalize-Methode dieser Klasse aufrufen (Sie können ein Programm schreiben, um diese Schlussfolgerung zu überprüfen. Im Allgemeinen muss eine in reinem Java geschriebene Klasse diese Methode nicht überschreiben). Das Objekt hat bereits eine Standardeinstellung implementiert, es sei denn, wir möchten spezielle Funktionen implementieren (was viele Dinge beinhaltet, wie z. B. Objektraumbäume usw.).
Für Klassen, die in anderem Code als Java geschrieben sind (z. B. JNI, Speicher, der durch die neue Methode von C++ zugewiesen wird), kann der Garbage Collector diese Teile jedoch nicht korrekt recyceln. Zu diesem Zeitpunkt müssen wir die Standardmethode überschreiben, um dies zu erreichen der Zweck. Korrekte Freigabe und Wiederverwendung dieses Teils des Speichers (C++ erfordert beispielsweise das Löschen).
Kurz gesagt, finalize entspricht dem Destruktor. Es ist die erste Methode, die aufgerufen wird, wenn der Garbage Collector ein Objekt recycelt. Da der Garbage-Collection-Mechanismus von Java diese Dinge jedoch automatisch für uns erledigen kann, müssen wir ihn im Allgemeinen nicht selbst manuell freigeben.

Manchmal müssen beim Rückgängigmachen eines Objekts einige Vorgänge abgeschlossen werden. Wenn ein Objekt beispielsweise Nicht-Java-Ressourcen wie Dateihandles oder Fensterschriftarten verarbeitet, müssen Sie sicherstellen, dass diese Ressourcen freigegeben werden, bevor ein Objekt zerstört wird. Um mit solchen Situationen umzugehen, bietet Java einen Mechanismus namens Finalisierung. Mithilfe dieses Mechanismus können Sie spezielle Vorgänge definieren, die ausgeführt werden, wenn ein Objekt kurz vor der Freigabe durch den Garbage Collector steht.
Um einer Klasse einen Finalizer hinzuzufügen, müssen Sie nur die finalize()-Methode definieren. Diese Methode wird aufgerufen, wenn Java ein Objekt dieser Klasse recycelt. In der Methode finalize() geben Sie die Aktionen an, die ausgeführt werden müssen, bevor ein Objekt zerstört wird. Die Garbage Collection wird regelmäßig ausgeführt und überprüft, ob Objekte nicht mehr durch den Ausführungsstatus oder indirekt durch andere Objekte referenziert werden. Kurz bevor das Objekt freigegeben wird, ruft das Java-Laufzeitsystem die finalize( )-Methode des Objekts auf.

Das allgemeine Format der finalize()-Methode ist wie folgt:

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

Dabei verhindert das Schlüsselwort protected, dass außerhalb der Klasse definierter Code auf den finalize()-Bezeichner zugreift. Dieser Bezeichner und weitere Bezeichner werden in Kapitel 7 erläutert.

Es ist wichtig zu verstehen, dass finalize() unmittelbar vor der Garbage Collection aufgerufen wird. Wenn beispielsweise ein Objekt seinen Gültigkeitsbereich überschreitet, wird finalize() nicht aufgerufen. Das bedeutet, dass Sie nicht wissen können, wann – oder ob – finalize( ) aufgerufen wird. Daher sollte Ihr Programm andere Methoden zum Freigeben der von Objekten verwendeten Systemressourcen bereitstellen und kann sich nicht auf finalize() verlassen, um den normalen Betrieb des Programms abzuschließen.

Hinweis: Wenn Sie mit C++ vertraut sind, wissen Sie, dass Sie in C++ eine Rückgängig-Funktion (Destruktor) für eine Klasse definieren können, die aufgerufen wird, kurz bevor das Objekt den Gültigkeitsbereich verlässt. Java unterstützt diese Idee nicht und bietet auch keine Rückgängig-Funktion. Die Methode finalize() kommt der Funktionalität der Undo-Funktion nur nahe. Wenn Sie mehr Erfahrung mit Java haben, werden Sie feststellen, dass es kaum nötig ist, die Rückgängig-Funktion zu verwenden, da Java ein Garbage-Collection-Subsystem verwendet.


Das Funktionsprinzip von Finalize sollte wie folgt aussehen: Sobald der Garbage Collector bereit ist, den vom Objekt belegten Speicherplatz freizugeben, ruft er zuerst finalize() auf und erst beim nächsten Garbage Collection-Prozess , wird es wirklich den Speicher des Objekts zurückgewinnen? Wenn Sie finalize() verwenden, können Sie während der Speicherbereinigung einige wichtige Reinigungs- oder Reinigungsarbeiten durchführen.

Es gibt Drei Situationen


Wird automatisch aufgerufen, wenn alle Objekte in der Garbage Collection sind, beispielsweise beim Ausführen von System.gc().

Die finalize-Methode wird einmal für jedes Objekt aufgerufen, wenn das Programm beendet wird.

Rufen Sie die Finalize-Methode explizit auf

Darüber hinaus wird finalize() unter normalen Umständen automatisch aufgerufen, wenn ein Objekt vom System als nutzlose Informationen erfasst wird, die JVM jedoch nicht Es ist garantiert, dass finalize() aufgerufen werden muss, das heißt, der Aufruf von finalize() ist unsicher, weshalb Sun die Verwendung von finalize() nicht befürwortet.

Manchmal ist es beim Rückgängigmachen eines Objekts so Es müssen einige Vorgänge abgeschlossen werden. Wenn ein Objekt beispielsweise Nicht-Java-Ressourcen wie Dateihandles oder Fensterschriftarten verarbeitet, müssen Sie sicherstellen, dass diese Ressourcen freigegeben werden, bevor ein Objekt zerstört wird. Um mit solchen Situationen umzugehen, bietet Java einen Mechanismus namens Finalisierung. Mithilfe dieses Mechanismus können Sie spezielle Vorgänge definieren, die ausgeführt werden, wenn ein Objekt kurz vor der Freigabe durch den Garbage Collector steht.

Um einer Klasse einen Finalizer hinzuzufügen, müssen Sie nur die finalize()-Methode definieren. Diese Methode wird aufgerufen, wenn Java ein Objekt dieser Klasse recycelt. In der Methode finalize() geben Sie die Aktionen an, die ausgeführt werden müssen, bevor ein Objekt zerstört wird. Die Garbage Collection wird regelmäßig ausgeführt und überprüft, ob Objekte nicht mehr durch den Ausführungsstatus oder indirekt durch andere Objekte referenziert werden. Kurz bevor das Objekt freigegeben wird, ruft das Java-Laufzeitsystem die finalize( )-Methode des Objekts auf.

Das allgemeine Format der finalize()-Methode lautet wie folgt:

Unter anderem verhindert das Schlüsselwort protected, dass außerhalb der Klasse definierter Code auf den finalize()-Bezeichner zugreift. Diese und andere Identifikatoren werden in Kapitel 7 erläutert.
protected void finalize( )
{
// finalization code here
}

Es ist wichtig zu verstehen, dass finalize() unmittelbar vor der Garbage Collection aufgerufen wird. Wenn beispielsweise ein Objekt seinen Gültigkeitsbereich überschreitet, wird finalize() nicht aufgerufen. Das bedeutet, dass Sie nicht wissen können, wann – oder ob – finalize( ) aufgerufen wird. Daher sollte Ihr Programm andere Methoden zum Freigeben der von Objekten verwendeten Systemressourcen bereitstellen und kann sich nicht auf finalize() verlassen, um den normalen Betrieb des Programms abzuschließen.

Hinweis: Wenn Sie mit C++ vertraut sind, wissen Sie, dass Sie in C++ eine Rückgängig-Funktion (Destruktor) für eine Klasse definieren können, die aufgerufen wird, kurz bevor das Objekt den Gültigkeitsbereich verlässt. Java unterstützt diese Idee nicht und bietet auch keine Rückgängig-Funktion. Die Methode finalize() kommt der Funktionalität der Undo-Funktion nur nahe. Wenn Sie mehr Erfahrung mit Java haben, werden Sie feststellen, dass es kaum nötig ist, die Rückgängig-Funktion zu verwenden, da Java ein Garbage-Collection-Subsystem verwendet.

Beim Sammeln von Müll ruft der Garbage Collector automatisch die Finalize-Methode des Objekts auf, um einige benutzerdefinierte Bereinigungsarbeiten durchzuführen, die nicht den Speicher betreffen, da der Garbage Collector keine anderen Dinge als den Speicher verarbeitet. Daher müssen Benutzer manchmal einige Reinigungsmethoden definieren, z. B. die Verarbeitung von Nicht-Speicherressourcen wie Dateien und Ports.

1. Übersicht über JVMs GC
 
GC, der Garbage Collection-Mechanismus, bezieht sich auf den von JVM belegten Speicher, der zum Freigeben von Objekten verwendet wird, die nicht mehr verwendet werden. Die Java-Sprache erfordert weder, dass die JVM über GC verfügt, noch gibt sie an, wie GC funktioniert. Häufig verwendete JVMs verfügen jedoch über GC, und die meisten GCs verwenden ähnliche Algorithmen, um den Speicher zu verwalten und Erfassungsvorgänge durchzuführen.
 
Erst wenn der Garbage-Collection-Algorithmus und der Ausführungsprozess vollständig verstanden sind, kann seine Leistung effektiv optimiert werden. Einige Garbage Collections sind auf spezielle Anwendungen spezialisiert. Echtzeitanwendungen konzentrieren sich beispielsweise in erster Linie auf die Vermeidung von Unterbrechungen bei der Garbage Collection, während sich die meisten OLTP-Anwendungen auf die Gesamteffizienz konzentrieren. Sobald Sie die Arbeitslast der Anwendung und die von der JVM unterstützten Garbage-Collection-Algorithmen verstanden haben, können Sie den Garbage Collector optimieren und konfigurieren.
 
Der Zweck der Garbage Collection besteht darin, Objekte zu entfernen, die nicht mehr verwendet werden. Der GC bestimmt, ob ein Objekt erfasst werden soll, indem er bestimmt, ob es von einem Live-Objekt referenziert wird. GC muss zunächst feststellen, ob das Objekt zur Abholung bereit ist. Zwei häufig verwendete Methoden sind Referenzzählung und Objektreferenzdurchquerung.
 
 1.1. Referenzzählung
 
  Referenzzählung speichert die Anzahl aller Referenzen auf ein bestimmtes Objekt, das heißt, wenn die Anwendung eine Referenz erstellt und die Referenz den Gültigkeitsbereich verlässt muss die Referenznummer entsprechend erhöhen oder verringern. Wenn die Anzahl der Verweise auf ein Objekt 0 erreicht, kann eine Speicherbereinigung erfolgen.
 
 1.2. Objektreferenzdurchquerung
 
Frühe JVMs verwendeten die Referenzzählung, und jetzt verwenden die meisten JVMs die Objektreferenzdurchquerung. Die Durchquerung von Objektreferenzen beginnt mit einer Reihe von Objekten und bestimmt rekursiv erreichbare Objekte entlang jeder Verbindung im gesamten Objektdiagramm. Wenn ein Objekt von einem (mindestens einem) dieser Root-Objekte aus nicht erreichbar ist, wird es durch Garbage Collection erfasst. Während der Objektdurchquerungsphase muss sich der GC merken, welche Objekte erreichbar sind, um nicht erreichbare Objekte zu löschen. Dies wird als Markieren von Objekten bezeichnet.
 
Im nächsten Schritt löscht gc nicht erreichbare Objekte. Beim Löschen scannen einige GCs einfach den Stapel, löschen nicht markierte Objekte und geben ihren Speicher frei, um neue Objekte zu generieren. Das Problem bei diesem Ansatz besteht darin, dass der Speicher in viele kleine Segmente unterteilt wird, die nicht groß genug für das neue Objekt sind, die Kombination jedoch groß ist. Daher können viele GCs Objekte im Speicher neu organisieren und komprimieren, um nutzbaren Speicherplatz zu schaffen.
 
Aus diesem Grund muss gc andere Aktivitäten einstellen. Dieser Ansatz bedeutet, dass alle anwendungsbezogenen Arbeiten gestoppt werden und nur der GC ausgeführt wird. Dadurch werden während der Antwortzeit viele gemischte Anfragen hinzugefügt und gelöscht. Darüber hinaus werden kontinuierlich komplexere GCs hinzugefügt oder gleichzeitig ausgeführt, um Anwendungsunterbrechungen zu reduzieren oder zu beseitigen. Einige GCS verwenden einen einzelnen Thread, um diese Arbeit abzuschließen, während andere Multithreading verwenden, um die Effizienz zu steigern.
 
2. Mehrere Garbage-Collection-Mechanismen
 
 2.1. Mark-Sweep-Kollektor
 
  Dieser Kollektor durchläuft zuerst den Objektgraphen und markiert erreichbare Objekte und scannt dann den Stapel, um nicht markierte Objekte zu finden Objekte und geben ihr Gedächtnis frei. Diese Art von Kollektor verwendet im Allgemeinen einen einzelnen Thread zum Arbeiten und stoppt andere Vorgänge.
 
 2.2. Mark-Compact Collector
 
Manchmal auch Mark-Sweep-Compact Collector genannt, hat es die gleiche Markierungsphase wie Mark-Sweep Collector. In der zweiten Phase wird das markierte Objekt in einen neuen Bereich des Stapels kopiert, um den Stapel zu komprimieren. Dieser Kollektor stoppt auch andere Vorgänge.
 
 2.3. Kollektor kopieren
 
  Dieser Kollektor unterteilt den Stapel in zwei Domänen, die oft als Halbraum bezeichnet werden. Es wird jedes Mal nur die Hälfte des Speicherplatzes genutzt, und in der anderen Hälfte des Speicherplatzes werden neue, von der JVM generierte Objekte platziert. Wenn gc ausgeführt wird, kopiert es erreichbare Objekte in die andere Hälfte des Speicherplatzes und komprimiert dadurch den Stapel. Diese Methode eignet sich für kurzlebige Objekte. Das kontinuierliche Kopieren langlebiger Objekte führt zu einer verringerten Effizienz.
 
 2.4. Inkrementeller Kollektor
 
  Der inkrementelle Kollektor teilt den Stapel in mehrere Domänen auf und sammelt jeweils nur Müll von einer Domäne. Dies führt zu geringfügigen Anwendungsunterbrechungen.
 
 2.5. Generationssammler
 
  Dieser Sammler unterteilt den Stapel in zwei oder mehr Domänen, um Objekte mit unterschiedlicher Lebensdauer zu speichern. Neue von JVM generierte Objekte werden im Allgemeinen in einem der Felder platziert. Mit der Zeit erhalten die überlebenden Objekte eine Nutzungsdauer und werden in einen langlebigeren Bereich überführt. Generationskollektoren verwenden unterschiedliche Algorithmen für unterschiedliche Domänen, um die Leistung zu optimieren.
 
 2.6. Gleichzeitiger Kollektor
 
  Der gleichzeitige Kollektor wird gleichzeitig mit der Anwendung ausgeführt. Diese Kollektoren müssen im Allgemeinen irgendwann andere Vorgänge (z. B. die Komprimierung) stoppen, um eine bestimmte Aufgabe abzuschließen. Da jedoch andere Anwendungen andere Hintergrundvorgänge ausführen können, wird die tatsächliche Zeit zum Unterbrechen anderer Verarbeitungen erheblich verkürzt.
 
 2.7. Parallelkollektor
 
  Parallelkollektoren verwenden einen traditionellen Algorithmus und verwenden mehrere Threads, um ihre Arbeit parallel auszuführen. Der Einsatz von Multithreading-Technologie auf Multi-CPU-Maschinen kann die Skalierbarkeit von Java-Anwendungen erheblich verbessern.

3. Objektzerstörungsprozess

在对象的销毁过程中,按照对象的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 其它的对象。

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

Verstehen Sie kurz den Garbage-Collection-Mechanismus von Java und die Rolle der Finalize-Methode

好大,好晕,慢慢看。

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交互了。 

更多Verstehen Sie kurz den Garbage-Collection-Mechanismus von Java und die Rolle der Finalize-Methode相关文章请关注PHP中文网!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn