IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Découvrir et apprendre l'environnement .Net : première partie : présentation et historique
Un tutoriel de François Dorin

Le , par François DORIN

20PARTAGES

14  0 
Bonjour à toutes et à tous,

Je vous propose une nouvelle série de tutoriels pour approfondir nos connaissances de l'environnement .Net. Ce premier opus est consacré à une rapide présentation et historique de cet écosystème.

Le prochain sera beaucoup plus technique et nous plongera au coeur d'un composant essentiel : le ramasse-miette !

Bonne lecture.

Retrouvez les meilleurs cours et tutoriels pour apprendre le .Net

Une erreur dans cette actualité ? Signalez-nous-la !

Avatar de François DORIN
Expert éminent sénior https://www.developpez.com
Le 06/08/2017 à 16:37
Pour les 10 prochains, ça va être dur. Je ne vois pas aussi loin !

Les sujets que je compte aborder :
  • le fonctionnement du ramasse-miette (implémentation du framework .Net) ;
  • l'anatomie d'un assembly ;
  • le CLS (Common Language Specification) qui permet une interopérabilité entre les différents langages ;
  • l'interopérabilité avec du code natif (non managé) ;
  • une introduction au CIL (quelques exemples d'instructions, comment voir le code généré, comment généré du code dynamiquement)


La liste n'est pas exhaustive, n'est pas ordonnée et est très certainement amenée à évoluer (en fonction de mes envies, des retours que j'aurais, etc...). Mais pour l'instant, c'est ce que j'envisage.
4  0 
Avatar de François DORIN
Expert éminent sénior https://www.developpez.com
Le 08/01/2018 à 14:16
Citation Envoyé par Pol63  Voir le message
Très bon article.

Merci

Citation Envoyé par Pol63  Voir le message
Je pensais que tous les objets étaient finalisables par défaut, d'une part parce que la classe object a un finaliseur, et en vb.net le destructeur s'écrit d'ailleurs par un Protected Overrides Sub Finalize.
via reflector sur la classe System.Object :
Code C# : Sélectionner tout
1
2
3
4
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), NonVersionable, __DynamicallyInvokable] 
protected virtual void Finalize() 
{ 
}

En théorie, tu as raison. Tous les objets disposent d'une méthode Finalize dans la mesure où elle est définie au niveau de la classe Object. Mais cette classe en générale, et cette méthode en particulier, sont gérés de manière particulière. Ainsi, tant que cette méthode n'est pas redéfinie par une classe, les objets de cette classe ne sont pas marqués comme étant "finalisable". On peut retrouver cette information directement au sein de la MSDN :
Citation Envoyé par MSDN
The Object class provides no implementation for the Finalize method, and the garbage collector does not mark types derived from Object for finalization unless they override the Finalize method.

que l'on pourrait traduire
Citation Envoyé par MSDN
La classe Object ne fournit pas d'implémentation pour la méthode Finalize, et le ramasse-miettes ne marque pas les types dérivés de la classe Object pour finalisation sauf s'ils redéfinissent la méthode Finalize.

Petit point vocabulaire, que j'aurais dû préciser dans l'article (et je pense le modifier). Dans l'article je parle de destructeur, et là tu mentionnes la méthode Finalize. Il s'agit bien de la même chose. Quand j'ai écris l'article, j'ai été influencé par mon langage de prédilection : le C#.

En effet, en C#, il n'est pas possible de redéfinir la méthode Finalize, ni même de l'appeler. La seule possibilité pour le faire est de définir un destructeur au sein d'une classe, car lorsque le ramasse-miettes appelle le destructeur d'une classe, il appelle en réalité la méthode Finalize.

En VB.Net, l'approche est différente. Il n'y a pas de notion de destructeur, et la seule manière de procéder est donc de redéfinir cette méthode.

Les deux approches, bien qu'équivalentes dans leur finalité, sont légèrement différentes dans leur mise en oeuvre :
  • le C# a une vision objet : lorsqu'on instancie une classe, toute la hiérarchie des constructeurs est appelée, de la classe de base (Object) à la classe finale. Il en est de même pour le destructeur, mais dans le sens inverse : de la classe finale, à la classe de base. C'est le comportement que l'on retrouve dans des langages sans ramasse-miettes comme le C++ ;
  • le VB.Net a une vision polymorphique. On redéfinie une méthode, et charge au développeur d'appeler ou non la méthode Finalize de la classe parente.


En règle général, lorsqu'on redéfinie une méthode, il faut effectivement se poser la question de savoir si on doit appeler la méthode de la classe mère ou non. Il n'y a pas de réponse toute faite dans la mesure où cela dépend de la situation. Par contre, ici, on ne devrait pas avoir le choix. Une classe redéfinissant la méthode Finalize devrait toujours appeler la méthode de la classe mère. En effet, même avec une connaissance profonde de la manière dont est implémentée une classe, une classe fille ne peut pas accéder à tous les champs de la classe mère : ceux marqués comme private.

Il est donc de la responsabilité d'une classe de libérer correctement toutes les ressources qu'elle possède, c'est à dire :
  • les champs privés ;
  • les champs non privés, mais introduit par la classe. Par exemple, un attribut protégé hérité de la classe mère ne doit pas être géré dans la classe fille. Mais un attribut protégé introduit dans la classe fille doit être géré par cette même classe fille.


Je préfère l'approche du C#, car sur ce point précis, elle ne laisse pas le choix au programmeur pour un détail d'implémentation où il ne devrait de toutes les façons, pas avoir le choix.
2  0 
Avatar de tomlev
Rédacteur/Modérateur https://www.developpez.com
Le 10/01/2018 à 21:02
Très bon article, merci !

Quelques petites remarques/questions :

- Dans "Anatomie d'une allocation mémoire", est-ce que les points 6 et 7 ne devraient pas être inversés ? Si le constructeur est appelé avant la mise à jour de NextObjPtr et qu'il crée lui-mêle d'autres objets, ça peut pas marcher, si ?

- Dans "Objets avec destructeur" : je vois que tu as déjà abordé la question un peu plus haut dans cette discussion, mais je pense qu'il serait bien de mettre à jour l'article pour parler de "finaliseur" et non de "destructeur". Il n'y a pas de destructeur (un terme qui vient de C++) dans le CLR, seulement des finaliseurs ; c'est juste qu'en C# on utilise la syntaxe d'un destructeur pour déclarer les finaliseurs.

- Toujours dans "Objets avec destructeur" : (généralement, des ressources non natives comme une connexion à une base de données par exemple)
2  0 
Avatar de François DORIN
Expert éminent sénior https://www.developpez.com
Le 12/01/2018 à 0:14
J'ai enfin réussi à mettre à jour l'article.
2  0 
Avatar de François DORIN
Expert éminent sénior https://www.developpez.com
Le 09/01/2018 à 12:50
Citation Envoyé par micka132 Voir le message
Un grand merci pour cet article.
Mais de rien

Citation Envoyé par micka132 Voir le message
Content d'avoir appris des choses sans faire des heures (jours?) de recherches
Hmm, je n'ai pas tenu les comptes, mais je penche pour des jours plutôt que des heures
1  0 
Avatar de star
Membre éprouvé https://www.developpez.com
Le 06/08/2017 à 7:07
Pourrait-on avoir un déroulé des 10 prochains chapitres de la série de tutoriels ?
Merci
.
0  0 
Avatar de Pol63
Expert éminent sénior https://www.developpez.com
Le 08/01/2018 à 11:23
Très bon article.

Cependant une phrase me fait me poser quelques questions :
Pour cela, après la phase de marquage, le ramasse-miettes ausculte une de ses structures internes appelée « Finalization list ». Cette liste contient une référence pour chaque objet disposant d'un destructeur.

Je pensais que tous les objets étaient finalisables par défaut, d'une part parce que la classe object a un finaliseur, et en vb.net le destructeur s'écrit d'ailleurs par un Protected Overrides Sub Finalize.
via reflector sur la classe System.Object :
Code C# : Sélectionner tout
1
2
3
4
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), NonVersionable, __DynamicallyInvokable] 
protected virtual void Finalize() 
{ 
}

D'autre part parce qu'à l'époque où je décompilais le Framework en 2008 j'étais tombé sur des classes qui appelait suppressfinalize(this) dans le constructeur.
Après recherche aujourd'hui je ne trouve que des classes implémentant IDisposable utilisant cette optimisation.

De plus en cas d'héritage une classe peut avoir une classe ayant un finaliseur et une classe enfant qui ne l'a pas, donc le GC ne pourrait pas se baser sur le fait qu'il y a un finaliseur sur le dernier niveau d'héritage, et trouverais celui de Object.
Ou alors il est codé spécifiquement pour ignorer celui d'Object ...

Mais en effet d'un autre côté ca ne serait pas logique d'être obligé de mettre des suppressfinalize dans tous les contructeurs de classes managées simples.
Si tu as des éclaircissements là dessus je suis preneur ^^

Merci.
0  0 
Avatar de micka132
Expert confirmé https://www.developpez.com
Le 09/01/2018 à 9:45
Un grand merci pour cet article.
Content d'avoir appris des choses sans faire des heures (jours?) de recherches
0  0 
Avatar de Pol63
Expert éminent sénior https://www.developpez.com
Le 09/01/2018 à 12:52
j'aurais bien voulu plus d'infos sur les "blob", ce qui rentre dans cette catégorie, et comment est gérée leur stack (vis à vis du pointeur d'allocation)
est-ce qu'une grande collection rentre dans ce cas ? ou juste les classes avec des dizaines de string ?

et aussi comment est choisit le nombre de générations à collecter ?
0  0 
Avatar de François DORIN
Expert éminent sénior https://www.developpez.com
Le 09/01/2018 à 14:39
Citation Envoyé par Pol63 Voir le message
j'aurais bien voulu plus d'infos sur les "blob", ce qui rentre dans cette catégorie, et comment est gérée leur stack (vis à vis du pointeur d'allocation)
est-ce qu'une grande collection rentre dans ce cas ? ou juste les classes avec des dizaines de string ?
Il faut véritable que ce soit un objet en lui même qui dépasse une certaine limite (actuellement 85000 octets). Une classe avec des dizaines de string ne risque rien, car la classe n'aura qu'une dizaine de référence vers des strings. Et les références sont sur 4 ou 8 octets en fonction de l'architecture (32 ou 64 bits).

Par contre, une chaîne de caractères peut très bien dépasser cette limite. C'est courant si on charge en mémoire un fichier texte par exemple.

Pour les collections, c'est un peu la même chose. Je vais prendre le cas simple d'un tableau. Un tableau, pour dépasser cette limite, doit dépasser la limite de 85000 octets. Un tableau d'objet n'a que peu de chance de dépasser cette limite, dans la mesure où ce ne sont pas les objets eux-mêmes qui sont stockés au sein du tableau, mais des références vers ces objets. Il y a des cas où cela se fait facilement par contre. On peut citer :
  • chargement d'un fichier en mémoire. Les tableaux de byte[] peuvent très vite grimper !
  • gestion d'un cache ;
  • des tableaux de structures.


Hormis c'est cas là, il faut dépasser le seuil théorie de 85000 / (taille référence) d'objets pour qu'un tableau soit considéré comme un blob, soit 21250 pour les architectures 32 bits et 10625 pour les architectures 64 bits. Ce qui laisse quand même un peu de marge.

Avec les structures, il est aussi possible de dépasser cette limite rapidement. En effet, dans la mesure où une structure est un type valeur, l'objet est directement stocké au sein du tableau. Si la structure est large, cette limite peut être dépassé. Par exemple, pour une structure qui aurait une taille de 1000 octets, il suffirait d'un tableau de 85 éléments pour qu'il soit considéré comme un blob.

Et le tas pour les blob est géré de la même manière que le tas managé "classique", à 2 différences près :
  • les objets ne sont pas déplacés par défaut (car le déplacement de gros objets est coûteux) ;
  • tous les objets sont de génération 2, c'est-à-dire qu'ils ne seront collectés que lorsque le ramasse-miettes fera une collecte complète. Une collecte des générations 0 et 1 n'a aucun incidence sur ce tas.


Citation Envoyé par Pol63 Voir le message
et aussi comment est choisit le nombre de générations à collecter ?
J'admet, c'est un sujet que je n'ai pas abordé dans l'article. J'avais décidé de le squizer pour diverses raisons.

Chaque génération dispose d'une taille seuil, au delà de laquelle la génération sera collectée.

Lorsque le ramasse-miettes se déclenche, il regarde la taille des différentes générations et la compare aux tailles seuils. Prenons la génération 1 et examinons les différents cas.

Si la taille de la génération 1 est inférieure à la taille seuil, alors la génération 1 n'est pas collectée. Seule la génération 0 le sera.

Si la taille de la génération 1 est supérieure à la taille seuil, alors la génération est collectée. Une fois la collecte terminée, le ramasse-miette peut faire évoluer les tailles seuil afin de s'adapter au comportement de l'application.

Cette modification des tailles seuil peut être aussi bien à la hausse qu'à la baisse. Par exemple, si le ramasse-miettes détecte qu'il y a énormément de petits objets à la durée de vie extrêmement courte qui sont créés, il peut décider de réduire le seuil de la génération 0, afin d'effectuer un nettoyage plus souvent, mais qui sera plus rapide (car moins d'objets à examiner à chaque fois).

Au final, je me demande si je ne vais pas écrire un autre article sur le sujet, afin de compléter certains points non abordé dans le premier !
0  0