Maison >Java >javaDidacticiel >Comprendre brièvement le mécanisme de récupération de place de Java et le rôle de la méthode finalize
Lorsque le garbage collector veut recycler un objet, il doit d'abord appeler la méthode finalize de cette classe (vous pouvez écrire un programme pour vérifier cette conclusion. Généralement, la classe écrite en Java pur n'a pas besoin de remplacer cette méthode car). Object a déjà implémenté un Par défaut, à moins que nous souhaitions implémenter des fonctions spéciales (ce qui implique beaucoup de choses, comme des arborescences d'espace objet, etc.).
Cependant, pour les classes écrites dans du code autre que Java (comme JNI, mémoire allouée par la nouvelle méthode C), le garbage collector ne peut pas recycler correctement ces parties. Pour le moment, nous devons remplacer la méthode par défaut pour atteindre l'objectif. . Libération et recyclage corrects de cette partie de la mémoire (par exemple, C nécessite une suppression).
En bref, finalize est équivalent au destructeur. C'est la première méthode à être appelée lorsque le garbage collector recycle un objet. Cependant, étant donné que le mécanisme de récupération de place de Java peut effectuer automatiquement ces tâches à notre place, nous n'avons généralement pas besoin de le publier manuellement nous-mêmes.
Parfois, lors de la suppression d'un objet, certaines opérations doivent être effectuées. Par exemple, si un objet gère des ressources non Java, telles que des descripteurs de fichiers ou des polices de caractères de fenêtre, vous devez vous assurer que ces ressources sont libérées avant qu'un objet ne soit détruit. Pour gérer de telles situations, Java fournit un mécanisme appelé finalisation. À l'aide de ce mécanisme, vous pouvez définir des opérations spéciales qui sont effectuées lorsqu'un objet est sur le point d'être libéré par le garbage collector.
Pour ajouter un finaliseur à une classe, il vous suffit de définir la méthode finalize(). Cette méthode est appelée lorsque Java recycle un objet de cette classe. Dans la méthode finalize(), vous spécifiez les actions qui doivent être effectuées avant qu'un objet ne soit détruit. Le garbage collection s'exécute périodiquement, vérifiant que les objets ne sont plus référencés par l'état d'exécution ou indirectement via d'autres objets. Juste avant la publication de l'objet, le système d'exécution Java appelle la méthode finalize( ) de l'objet.
Le format général de la méthode finalize() est le suivant :
protected void finalize( ) { // finalization code here }
Dans lequel, le mot clé protected empêche le code défini en dehors de la classe d'accéder à l'identifiant finalize(). Cet identifiant et d’autres identifiants sont expliqués au chapitre 7.
Il est important de comprendre que finalize() est appelé juste avant le garbage collection. Par exemple, lorsqu'un objet dépasse sa portée, finalize() n'est pas appelé. Cela signifie que vous n'avez aucun moyen de savoir quand - ni même si - finalize( ) est appelé. Par conséquent, votre programme doit fournir d'autres méthodes pour libérer les ressources système utilisées par les objets et ne peut pas compter sur finalize() pour terminer le fonctionnement normal du programme.
Remarque : Si vous connaissez le C, vous savez que C vous permet de définir une fonction d'annulation (destructeur) pour une classe, qui est appelée juste avant que l'objet ne sorte de la portée. Java ne prend pas en charge cette idée et ne fournit pas de fonction d'annulation. La méthode finalize() n'est proche que de la fonctionnalité de la fonction d'annulation. Au fur et à mesure que vous deviendrez plus expérimenté avec Java, vous verrez qu'il n'est plus nécessaire d'utiliser la fonction d'annulation car Java utilise un sous-système de récupération de place.
Le principe de fonctionnement de finalize devrait être le suivant : une fois que le ramasse-miettes est prêt à libérer l'espace de stockage occupé par l'objet, il appelle d'abord finalize(), et seulement lors du prochain processus de ramasse-miettes. , cela va-t-il vraiment récupérer la mémoire de l'objet. Donc, si vous utilisez finalize(), vous pouvez effectuer des travaux de nettoyage ou de nettoyage importants pendant le ramasse-miettes
Quand finalize() est-il appelé ? trois situations
protected void finalize( ) { // finalization code here }Parmi eux, le mot clé protected empêche le code défini en dehors de la classe d'accéder à l'identifiant finalize(). Cet identifiant et d’autres identifiants sont expliqués au chapitre 7.
Il est important de comprendre que finalize() est appelé juste avant le garbage collection. Par exemple, lorsqu'un objet dépasse sa portée, finalize() n'est pas appelé. Cela signifie que vous n'avez aucun moyen de savoir quand - ni même si - finalize( ) est appelé. Par conséquent, votre programme doit fournir d'autres méthodes pour libérer les ressources système utilisées par les objets et ne peut pas compter sur finalize() pour terminer le fonctionnement normal du programme.
Remarque : Si vous êtes familier avec C, vous savez que C vous permet de définir une fonction d'annulation (destructeur) pour une classe, qui est appelée juste avant que l'objet ne sorte de la portée. Java ne prend pas en charge cette idée et ne fournit pas de fonction d'annulation. La méthode finalize() n'est proche que de la fonctionnalité de la fonction d'annulation. Au fur et à mesure que vous deviendrez plus expérimenté avec Java, vous verrez qu'il n'est plus nécessaire d'utiliser la fonction d'annulation car Java utilise un sous-système de récupération de place.
Lors de la collecte des ordures, le ramasse-miettes appellera automatiquement la méthode finalize de l'objet pour effectuer un travail de nettoyage non-mémoire défini par l'utilisateur, car le ramasse-miettes ne traitera pas d'autres choses que la mémoire. Par conséquent, les utilisateurs doivent parfois définir certaines méthodes de nettoyage, telles que le traitement des ressources non mémoire telles que les fichiers et les ports.
1. Présentation du gc de la JVM
GC, le mécanisme de garbage collection, fait référence à la mémoire occupée par jvm utilisée pour libérer les objets qui ne sont plus utilisés. Le langage Java n'exige pas que la JVM ait gc, et ne précise pas non plus comment fonctionne gc. Cependant, les JVM couramment utilisées disposent d'un GC, et la plupart des GC utilisent des algorithmes similaires pour gérer la mémoire et effectuer des opérations de collecte.
Ce n'est qu'après avoir pleinement compris l'algorithme de récupération de place et le processus d'exécution que ses performances peuvent être efficacement optimisées. Certains garbage collection sont dédiés à des applications spéciales. Par exemple, les applications en temps réel visent principalement à éviter les interruptions du garbage collection, tandis que la plupart des applications OLTP se concentrent sur l'efficacité globale. Une fois que vous avez compris la charge de travail de l'application et les algorithmes de garbage collection pris en charge par la JVM, vous pouvez optimiser et configurer le garbage collector.
Le but du garbage collection est de supprimer les objets qui ne sont plus utilisés. Le gc détermine s'il doit collecter un objet en déterminant s'il est référencé par un objet actif. GC doit d’abord déterminer si l’objet est prêt à être collecté. Deux méthodes couramment utilisées sont le comptage de références et le parcours de références d'objets.
1.1. Comptage de références
Le comptage de références stocke le nombre de toutes les références à un objet spécifique, c'est-à-dire que lorsque l'application crée une référence et que la référence sort de la portée, le jvm. doit augmenter ou diminuer le numéro de référence de manière appropriée. Lorsque le nombre de références à un objet atteint 0, un garbage collection peut avoir lieu.
1.2. Traversée de références d'objets
Les premières JVM utilisaient le comptage de références, et maintenant la plupart des JVM utilisent la traversée de références d'objets. Le parcours de référence d'objet commence à partir d'un ensemble d'objets et détermine de manière récursive les objets accessibles le long de chaque lien dans le graphe d'objets. Si un objet n'est pas accessible à partir d'un (au moins un) de ces objets racine, il est récupéré. Pendant la phase de traversée des objets, le GC doit se rappeler quels objets sont accessibles afin de supprimer les objets inaccessibles. C'est ce qu'on appelle le marquage des objets.
À l'étape suivante, gc supprimera les objets inaccessibles. Lors de la suppression, certains GC analysent simplement la pile, suppriment les objets non marqués et libèrent leur mémoire pour générer de nouveaux objets. Le problème avec cette approche est que la mémoire est divisée en plusieurs petits segments, qui ne sont pas assez grands pour le nouvel objet, mais la combinaison est grande. Par conséquent, de nombreux GC peuvent réorganiser les objets en mémoire et les compresser pour former un espace utilisable.
Pour cette raison, gc doit arrêter d'autres activités. Cette approche signifie que tout le travail lié à l'application s'arrête et que seul le gc s'exécute. En conséquence, de nombreuses requêtes mixtes sont ajoutées et supprimées pendant le temps de réponse. De plus, des GC plus complexes sont continuellement ajoutés ou exécutés simultanément pour réduire ou supprimer les interruptions d’application. Certains gc utilisent un seul thread pour effectuer ce travail, tandis que d'autres utilisent le multi-threading pour augmenter l'efficacité.
2. Plusieurs mécanismes de collecte des ordures
2.1. Collecteur de balayage de marquage
Ce collecteur parcourt d'abord le graphique d'objets et marque les objets accessibles, puis analyse la pile pour trouver les objets non marqués. objets et libérer leur mémoire. Ce type de collecteur utilise généralement un seul thread pour fonctionner et arrête les autres opérations.
2.2. Mark-Compact Collector
Parfois aussi appelé Mark-Sweep-Compact Collector, il a la même phase de marquage que Mark-Sweep Collector. Dans un deuxième temps, l'objet marqué est copié dans une nouvelle zone de la pile afin de compresser la pile. Ce collecteur arrête également d'autres opérations.
2.3. Collecteur de copie
Ce collecteur divise la pile en deux domaines, souvent appelés demi-espace. Seule la moitié de l'espace est utilisée à chaque fois, et les nouveaux objets générés par le jvm sont placés dans l'autre moitié de l'espace. Lorsque gc s'exécute, il copie les objets accessibles dans l'autre moitié de l'espace, compressant ainsi la pile. Cette méthode convient aux objets à courte durée de vie. La copie continue d'objets à longue durée de vie entraînera une efficacité réduite.
2.4. Collecteur incrémental
Le collecteur incrémental divise la pile en plusieurs domaines et ne collecte les déchets d'un domaine à la fois. Cela entraîne une perturbation mineure de l’application.
2.5. Collecteur générationnel
Ce collecteur divise la pile en deux domaines ou plus pour stocker des objets avec des durées de vie différentes. Les nouveaux objets générés par jvm sont généralement placés dans l'un des champs. Au fil du temps, les objets survivants acquerront une durée de vie utile et seront transférés vers un domaine à durée de vie plus longue. Les collectionneurs générationnels utilisent différents algorithmes pour différents domaines afin d'optimiser les performances.
2.6. Collecteur simultané
Le collecteur simultané s'exécute en même temps que l'application. Ces collecteurs doivent généralement arrêter d'autres opérations à un moment donné (comme le compactage) pour terminer une tâche spécifique, mais comme d'autres applications peuvent effectuer d'autres opérations en arrière-plan, le temps réel nécessaire pour interrompre d'autres traitements est considérablement réduit.
2.7. Collecteur parallèle
Les collecteurs parallèles utilisent un algorithme traditionnel et utilisent plusieurs threads pour effectuer leur travail en parallèle. L'utilisation de la technologie multithread sur des machines multi-CPU peut améliorer considérablement l'évolutivité des applications Java.
3. Processus de destruction d'objets
在对象的销毁过程中,按照对象的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 其它的对象。
来看看对象的状态转换图。
好大,好晕,慢慢看。
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交互了。
更多Comprendre brièvement le mécanisme de récupération de place de Java et le rôle de la méthode finalize相关文章请关注PHP中文网!