Aspnetcore: Asp.net Core ne collecte pas les déchets

Créé le 21 mars 2017  ·  136Commentaires  ·  Source: dotnet/aspnetcore

Je ne comprends pas pourquoi le noyau Asp.net ne semble pas collecter les ordures. La semaine dernière, j'ai laissé un service Web fonctionner pendant quelques jours et mon utilisation de la mémoire a atteint 20 Go. GC ne semble pas fonctionner correctement. Donc, pour tester cela, j'ai écrit une méthode Web très simple qui renvoie une grande collection de chaînes. L'application a démarré en utilisant seulement 124 Mo, mais à chaque fois que j'appelais la méthode Web, l'utilisation de la mémoire augmentait de plus en plus jusqu'à ce qu'elle atteigne 411 Mo. Il serait allé plus haut si j'avais continué à appeler la méthode Web. Mais j'ai décidé d'arrêter les tests.

Mon code de test était celui-ci :

`

[HttpGet]
Tâche asynchrone publique> TestGC() {
message de chaîne const = "TEST" ;
return Enumerable.Repeat(message, 100000000);
}
`

Bien que je puisse oublier quelque chose... À ma connaissance, l'utilisation de la mémoire ne devrait pas augmenter à chaque appel à cette méthode. Une fois l'objet créé et envoyé à l'interface utilisateur, la mémoire doit avoir été libérée.

Comme vous pouvez le voir sur la capture d'écran ci-dessous, même après l'appel du GC, la mémoire n'a pas été libérée.
netcorescreenshot

Merci pour l'aide!
Geai

investigate

Commentaire le plus utile

Des nouvelles à ce sujet? J'utilise Core 2 sur Net Framework et cela se produit toujours. Chaque appel à un _Controller_ augmente la mémoire utilisée, mais elle ne diminue jamais. (_J'ai utilisé le modèle WebApi_)

Tous les 136 commentaires

@rynowak

Bien que je puisse oublier quelque chose... À ma connaissance, l'utilisation de la mémoire ne devrait pas augmenter à chaque appel à cette méthode. Une fois l'objet créé et envoyé à l'interface utilisateur, la mémoire doit avoir été libérée.

Le gros tas d'objets vous mord probablement ici. Si vous allouez des objets > 85 Ko de taille, ils seront placés dans le LOH et seront très rarement compactés . Voir http://stackoverflow.com/questions/8951836/why-large-object-heap-and-why-do-we-care pour plus de détails (ou https://github.com/dotnet/coreclr/blob/master /Documentation/botr/garbage-collection.md#design-of-allocator si vous voulez aller plus loin).

Je ne comprends pas pourquoi le noyau Asp.net ne semble pas collecter les ordures. La semaine dernière, j'ai laissé un service Web fonctionner pendant quelques jours et mon utilisation de la mémoire a atteint 20 Go

Que fait votre service pour créer des objets aussi volumineux ? Vous devriez essayer de faire un vidage de mémoire avant qu'il ne devienne trop volumineux, cela vous montrera clairement quels objets restent et pourquoi il est retenu (vous pouvez utiliser visual studio pour regarder les vidages ou un outil plus avancé comme windbg ou perfview).

Essayez d'allouer le tableau depuis le début plutôt que d'appeler Enumerable.Repeat
ou compactez la mémoire à l'aide de GCSettings.LargeObjectHeapCompactionMode (pris en charge dans .Net Standard)

Merci @davidfowl et @MhAllan pour les réponses. Mais cet exemple était artificiel. Je voulais juste quelque chose qui utiliserait une quantité notable de mémoire pour que je puisse prendre une capture d'écran. La vérité est que cela se produit avec n'importe quelle application, quelle que soit la taille de l'objet en question. Et pour répondre à votre question @davidfowl , mon service extrayait simplement des données de la base de données avec dapper et renvoyait l'objet résultant. Il s'agissait d'une ligne de données pour chaque appel. Il a donc fallu quelques jours pour que la mémoire atteigne ce montant. J'essayais en fait de tester la DB quand je suis tombé sur cette particularité. J'avais écrit une petite application console pour appeler la méthode encore et encore.

@zorthgo sûr que ce n'est pas Dapper ? Si vous créez vos scripts en injectant des paramètres directement dans vos scripts SQL comme en PHP, vous vous retrouverez avec beaucoup de scripts SQL en cache. Voici comment Dapper le fait
https://github.com/StackExchange/Dapper/blob/fe5c270aceab362c936456087a830e6fe1603cac/Dapper/SqlMapper.cs
Vous devez utiliser un profileur de mémoire pour savoir ce qui conserve les références à la mémoire allouée. Visual Studio 2017 devrait pouvoir vous aider à prendre quelques instantanés de la mémoire avant et après plusieurs appels à votre application et à les comparer.

@zorthgo Oui, j'ai aussi vu ça. J'utilisais servicestack dans mon application de console .net core et chaque fois que j'appelais l'API, l'utilisation de la mémoire augmentait de 50 Mo. J'ai supposé qu'il s'agissait d'un bogue VS2017, mais j'ai ensuite confirmé l'utilisation élevée dans le gestionnaire de tâches. Comme l'a déclaré zorthgo en faisant simplement de simples appels à api, l'utilisation de la mémoire augmente considérablement et ne semble pas libérer de mémoire.

Cela se produit-il uniquement sur ASP.NET Core sur .NET Core ou s'agit-il également d'un problème avec ASP.NET Core sur .NET Framework ?

Pouvez-vous écrire une application similaire en utilisant MVC 5 (sur System.Web) et vérifier que vous ne voyez pas le même comportement ?

Je ne peux faire aucun progrès à partir de ce problème dans son état actuel.

Dans mon cas, j'utilisais uniquement le framework cible .NetCoreApp 1.1 et mon application de console faisait référence à un modèle d'objet dans un projet partagé.

Je ne sais pas si cela aidera quelqu'un. un exemple d'application qui appelle hellorequest. Dans cette application, la mémoire de démarrage est de 85 Mo, puis par requête répétitive, j'ai réussi à augmenter l'utilisation de la mémoire à environ 145 Mo. Il retombe parfois à 125 Mo mais y reste ensuite. Je ne sais pas s'il s'agit d'un comportement normal car je ne suis pas habitué aux applications de console .Net Core. J'ai toujours supposé que je faisais quelque chose de mal ou que je n'instanciais pas correctement.

https://drive.google.com/open?id=0B0Gm-m-z_U84TU5keWFTMzc1ZWc

Face au même problème ici sur une application Asp.Net Core déployée en production avec 3000 à 5000 utilisateurs actifs... la mémoire sur le serveur est passée à 15 Go hier... j'ai dû configurer IIS pour recycler l'AppPool toutes les 3 heures pendant essayez de comprendre quel est le problème.

Quelqu'un a-t-il effectué un vidage de mémoire et examiné ce qui occupe toute la mémoire de vos applications particulières ?

@davidfowl @Pinox
Je travaille dans une grande entreprise et nous voulons démarrer un nouveau projet avec ASP.NET Core, mais quand j'ai vu ce problème, j'ai eu peur :inquiet: . il s'agit d'un problème critique qui peut bloquer le cycle de vie de notre projet.

Alors s'il vous plaît, est-ce lié à ASP.NET Core ou .NET Core (CoreCLR) ? nous ciblerons Full .NET (4.6) c'est pourquoi je demande.

@ikourfaln dans mon cas, j'utilisais l'application de console .net core, servicestack (version .net core) et crécerelle. Ce qui est étrange, c'est que l'utilisation de la mémoire monte à un certain niveau, puis s'arrête soudainement et ne remonte plus. Je suppose que le mieux est de le tester de votre côté avec un petit échantillon et de vérifier le comportement.

Peut-être que @zorthgo peut vérifier de son côté s'il voit un comportement similaire dans cette mémoire est utilisé à un certain niveau, puis cesse d'augmenter car c'est le comportement que je vois. J'ai mis à jour mon exemple d'application pour inclure l'exemple @zorthgo et je ne vois pas la mémoire s'enfuir. Il monte mais finit par s'arrêter.

J'ai légèrement modifié la source :

objet public Any (requête TestGC)
{
message de chaîne const = "TEST" ;
return Enumerable.Repeat(message, 100000);
}

@Pinox
Merci, je vais vérifier le comportement de mon côté.

Qu'en est-il de ce bug dans la 2.0 ?

Des nouvelles à ce sujet? J'utilise Core 2 sur Net Framework et cela se produit toujours. Chaque appel à un _Controller_ augmente la mémoire utilisée, mais elle ne diminue jamais. (_J'ai utilisé le modèle WebApi_)

Salut,

J'ai le même problème avec ASP.NET Core 2. J'ai pris un vidage de mémoire et essayé d'analyser. D'après ce que je vois, le problème est exactement comme l'OP l'a dit. Mon application commence par une allocation d'environ 75 Mo, et très rapidement elle va jusqu'à ~ 750 Mo, dont 608 Mo sont "Mémoire inutilisée allouée à .NET".

Premier instantané au démarrage de l'application :
image

Deuxième instantané après 3 minutes et 100 requêtes :
image

nous sommes également confrontés au même problème, notre contrôleur traite une grande quantité de données (ce qui est une mauvaise conception et sera bientôt remplacé), chaque appel à ce contrôleur entraîne une augmentation de la mémoire. La mémoire ne diminue que de 40 à 50 % (gain de 50 Mo, réduction de 30 à 35 Mo), chaque appel augmente la mémoire dans une plage de 10 à 15 Mo à chaque fois. Le service est hébergé dans la structure de service.

Il semble que j'ai un problème similaire dans notre service de production (20-100 req/s) en utilisant une combinaison de :

  • ASP.NET Core 2.0.4
  • ServiceStack.Core 1.0.44
  • SkiaSharp 1.59.2
  • Docker v17.09.0-ce, build afdb6d4 sur Ubuntu 16.04 x64
  • Serveurs x4 @ 40 CPU, 128 Go de mémoire
  • Le GC du serveur est vrai
  • chaque conteneur Docker est défini sur 12 000 partages de processeur (mhz), 8 Go de RAM

L'application dispose d'un serveur Web frontal et d'un travailleur (illustrés respectivement dans les graphiques ci-dessous).

serveur web (dernières 6h)
screen shot 2018-01-13 at 1 06 24 pm

travailleur (dernières 6h)
screen shot 2018-01-13 at 1 07 55 pm

Ils utilisent tous deux de grands tableaux d'octets car le service agit comme un proxy de stockage d'objets et place par conséquent des objets dans le LOH. Ma question est la suivante : s'agit-il d'une limitation connue de .NET Core pour le moment ? Il semble que le LOH n'ait jamais été complètement nettoyé ou fragmenté.

Cela dit, SOH semble fonctionner correctement, car les objets API Web typiques sont nettoyés. Aucune suggestion? Y a-t-il un problème avec ma configuration ? J'ai analysé le code et je ne trouve aucune fuite de mémoire flagrante, et je n'utilise rien de spécial en dehors de la bibliothèque ServiceStack.

@sebastienros - des idées à ce sujet ? Avons-nous observé un comportement similaire dans nos systèmes ?

  • Nous ne mesurons cela que sur 2.1, je vais envisager d'ajouter la même chose pour 2.0
  • Tous les commentaires semblent être liés au problème de LOH qui a été mentionné, qui devrait être pris en compte dans l'application et regrouper autant que possible les grandes baies

@sebastienros , plusieurs questions :

  1. J'ai utilisé le profileur Ants pour mesurer l'utilisation de la mémoire, selon lui, aucune fragmentation LOH n'a été détectée. Pouvez-vous indiquer comment puis-je vérifier si mon application souffre de problèmes de fragmentation LOH ?
  2. Quels sont les résultats sur .net core 2.1 ? Le problème est-il résolu car Kestrel utilise Span?
  3. Que se passe-t-il si nous ne pouvons pas regrouper les baies ? Pouvez-vous proposer une solution de contournement ? Devrions-nous utiliser GCSettings.LargeObjectHeapCompactionMode.CompactOnce ?

Quels sont les résultats sur .net core 2.1 ? Le problème est-il résolu car Kestrel utilise Span ?

Personnellement, nous n'avons vu aucune preuve que le problème est dans Kestrel. Cela ressemble toujours à un problème d'application.

Que se passe-t-il si nous ne pouvons pas regrouper les baies ? Pouvez-vous proposer une solution de contournement ? Devrions-nous utiliser GCSettings.LargeObjectHeapCompactionMode.CompactOnce ?

@jkotas @Maoni0 Des conseils ici ?

comment puis-je enquêter si je souffre du même problème ? le profileur de mémoire LOH selon redgate est presque vide comme le décrit @sinapis mais utilise toujours plus de 1 Go pour un seul utilisateur

Collectez la trace et analysez-la à l'aide de perfview. Il existe un certain nombre de tutoriels de Vance et d'autres sur la façon de retracer les fuites de mémoire .NET : https://www.bing.com/search?q=.NET%20memory%20leak%20perfview .

https://github.com/dotnet/coreclr/blob/master/Documentation/project-docs/linux-performance-tracing.md contient des instructions spécifiques à Linux pour la collecte des traces.

Si vous pensez qu'il n'y a pas de fuite de mémoire et que GC conserve simplement plus de mémoire que vous le souhaitez, vous pouvez essayer :

  • Utilisation des limites de mémoire de l'objet de travail Windows ou du conteneur Docker pour limiter la quantité de mémoire que le processus peut utiliser. Le GC tient compte de ces limites et fonctionne plus agressivement lorsqu'il en est proche.
  • Ou, passer de Server GC à Workstation GC. Il n'est pas rare que le serveur GC ait un ensemble de travail de pointe beaucoup plus élevé. La station de travail a un ensemble de travail de pointe plus faible, mais également un débit inférieur.

Salut,

Je pense que j'ai le même genre de problème avec une API Web .Net Core en production.

L'application s'exécute sur Windows Server 2016 avec .Net Core 2.0.3. La machine est une machine virtuelle Hyper-V avec 28 cœurs de processeur et 24 Go de RAM. Si nous ne recyclons pas assez souvent le pool d'applications IIS, nous finirons par utiliser toute la mémoire disponible. Lorsque l'application commence à utiliser beaucoup de mémoire (>=95 % de la mémoire système totale), l'utilisation du processeur augmente également fortement (de 2 % à 70 % parfois). Je ne sais pas si une exception OOM est déclenchée ou non, nous recyclons toujours le pool avant que cela ne se produise (l'utilisation maximale de la mémoire que j'ai vue était de 98% de la mémoire utilisée par dotnet.exe).

Analyser un dump mémoire de production avec ".Net Memory Porfiler" (SciTech Software) voici ce que j'ai trouvé :
image

Si cette analyse est correcte, environ 95% de la mémoire est en "overhead > inutilisé". Voici comment cet éditeur de profileur de mémoire décrit cette catégorie (sur leur forum) :
_"Overhead->Unused" est la mémoire validée par le runtime .NET pour le tas géré. Il est actuellement inutilisé, mais il est disponible pour de futures allocations d'instances. Le runtime utilise de nombreuses règles pour décider de conserver la mémoire validée ou de la libérer pour le système d'exploitation. Cela dépend de facteurs tels que la mémoire disponible, les modèles d'allocation, le nombre de processeurs, si le serveur GC est utilisé, etc._

@jkotas J'appliquerai vos recommandations (objet de travail Windows et passage au poste de travail GC) et je vous ferai part du résultat. Veuillez me faire savoir si je peux extraire d'autres informations utiles des vidages de mémoire de production que j'ai.

Merci

@sinapis @ronald7
Est-ce que l'un d'entre vous serait en mesure de partager une application qui montre le problème ? Si je pouvais le reproduire, nous serions en mesure de trouver la raison, ou au moins de supprimer du code morceau par morceau et d'isoler une reproduction minimale.

@sebastienros Je ne peux pas partager l'application, mais je peux partager la session depuis PerfView session + memory dump .
Une description : j'ai une API Web ASP.NET Core 2, j'ai créé un test de charge de 200 utilisateurs envoyant tous la même requête en 10 secondes. Au total, 775 demandes ont été traitées.

Cette application est passée à près de 1 Go d'utilisation de la mémoire dans le gestionnaire de tâches et est restée comme ça. En regardant le dump, je peux compter environ 18 Mo:

image

La question est donc où est passé presque 1 Go ?

@sinapis Merci

Le comportement que vous décrivez n'est pas inattendu, le GC allouera de la mémoire selon les besoins lors de la charge maximale et la libérera simplement au fil du temps. C'est le mode GC Server, et attendez généralement des périodes d'inactivité pour le libérer et ne pas affecter les performances de votre application. La quantité de mémoire qu'il réservera dépend de la mémoire totale disponible sur le système.

Nous verrions certainement un problème s'il continuait à augmenter. Je suppose que si vous n'envoyez plus de requêtes et que vous laissez votre application s'exécuter, vous verrez l'utilisation de la mémoire diminuer.

Pourriez-vous exécuter la même chose jusqu'à ce qu'elle consomme la majeure partie de votre mémoire système ? Ou au moins assez longtemps avec la même charge pour qu'il le montre en croissance continue ? Je vais quand même jeter un œil à vos dumps actuels.

Vous pouvez également effectuer des décharges pendant et à la fin des travaux, afin que nous puissions voir les détails.

Salut @sebastienros

Malheureusement, je ne peux pas partager l'application ni les vidages mémoire, mais je vais créer une application factice (avec la même architecture et les mêmes dépendances), l'exécuter sur la même machine, si je peux reproduire ce comportement, je partagerai celle-ci avec vous. S'il vous plaît, faites-moi savoir s'il y a des informations utiles que je pourrais extraire pour vous des vidages de mémoire.

J'ai mis à jour le mode GC de _server_ à _workstation_ sur un serveur de production, je vous ferai savoir dans quelques heures si cela change quelque chose sur l'utilisation de la mémoire.

J'ai également effectué un autre test : nous exécutons notre application derrière un équilibreur de charge, sur 4 machines virtuelles. Après avoir supprimé l'une des machines du pool d'équilibrage de charge, la mémoire utilisée par dotnet.exe n'a pas diminué et est restée au même niveau même après 30 minutes. (Cependant, l'application traitait encore quelques requêtes : une requête envoyée par SCOM sur un point de terminaison factice toutes les 30 secondes). Aucune mémoire n'a été libérée et renvoyée au système.

Merci

@sinapis J'ai regardé votre trace ETW. cela me laisse perplexe - vous avez très peu survécu au dernier GC gen2 induit, mais nous avons quand même choisi de conserver autant de mémoire. votre application semble un cas limite (vous n'avez principalement fait que des GC en arrière-plan en raison des allocations LOH) - je me demande si nous avons des erreurs de comptabilité là-bas (une autre possibilité est des erreurs dans les chiffres signalés, mais si vous avez déjà vérifié que vous avez autant engagé c'est un petite possibilité). si vous pouviez reproduire quelque chose que vous pouvez partager avec moi, ce serait fantastique ; sinon, s'il est possible d'exécuter votre application avec une journalisation de GC (je peux vous donner un commit qui le fait), cela serait également utile.

@ Maoni0 s'il vous plaît partager comment activer la journalisation GC
S'il y a d'autres données que vous voudriez que je fournisse afin de réfuter une erreur comptable, veuillez me faire savoir ce que je dois vous fournir et comment (peut-être dire à perfview de collecter plus de données ?)
Je vais essayer de créer une reproduction minimale, mais je ne suis pas sûr d'y arriver car je ne sais pas d'où vient le problème.

@sebastienros j'espère que je fournirai un autre dump avec plus de consommation de mémoire aujourd'hui

Salut @sebastienros @Maoni0 ,

J'ai exécuté notre application avec le mode GC du poste de travail pendant 12 heures, mais le même résultat. J'ai également recompilé l'application avec .Net 2.1 Preview 2 sur un seul nœud de production pendant 1 heure, je vous ferai part du résultat, mais pour l'instant le processus utilise déjà plus de 2 Go de RAM.

image

J'ai PerfView en cours d'exécution sur cette même machine et je collecte des vidages GC, existe-t-il une adresse e-mail où je pourrais vous envoyer le lien OneDrive, malheureusement je ne peux pas le partager directement dans ce fil.

Si cela peut aider, je peux également collecter plus de métriques ou de journaux GC. Merci

@ronald7 _redacted_ Je peux transmettre à @ Maoni0

Salut @sebastienros @Maoni0 Je viens de vous envoyer un e-mail avec deux gcdump PerfView et un fichier VMMap, j'espère que cela pourra vous aider. De mon côté, j'essaie toujours de reproduire ce comportement d'utilisation élevée de la mémoire avec une application factice.

Merci!

Je rencontre également le même problème. Le ramassage des ordures n'a jamais lieu ! La capture d'écran montre l'utilisation de la mémoire après avoir effectué environ 50 requêtes à l'aide d'une application d'api Web dotnet core assez simple.

memory-profile2

Je viens de mettre à niveau une application ASP.NET Core exécutée sur Ubuntu 16.04 de 1.1 à 2.0 et j'ai rencontré ce problème. C'est assez grave, ce qui fait que le noyau tue fréquemment l'application en raison d'erreurs OOM, et j'envisage de revenir à 1.x. Il y a certaines pages que nous ne pouvons pas charger du tout - même après un redémarrage de Kestrel, l'application épuise immédiatement la mémoire disponible après une seule requête ! J'ai pensé à mettre à niveau le serveur, mais d'après les commentaires ici sur les applications ASP.NET Core utilisant toute la mémoire disponible, je n'espère pas que cela aidera. Notre pile est essentiellement ASP.NET MVC Core + EF Core... rien d'extraordinaire. Si j'ai un peu de temps, j'essaierai de créer un échantillon pour reproduire le problème - je ne pense pas que cela devrait être si difficile, étant donné la simplicité de notre pile.

FWIW, le système que j'ai mis à niveau a également une application de console .NET Core, et cela ne semble pas avoir de problèmes de mémoire après la mise à niveau 2.0, donc cela semble définitivement être un problème lié à ASP.NET Core.

@danports avez-vous essayé d'appeler GC.Collect() pour voir si l'utilisation de la mémoire diminue considérablement ? cela nous donnerait une idée par où commencer. si GC.Collect() (ou la séquence GC.Collect/GC.WaitingForPendingFinalizers/GC.Collect) n'est pas en mesure de réduire considérablement l'utilisation de la mémoire, cela signifie qu'il y a simplement autant de mémoire qui doit être active pour que GC ne puisse pas la récupérer.

@Maoni0 Je n'ai pas encore essayé ça. Je ne pense pas que mon problème soit lié à GC, car j'ai constaté une baisse de l'utilisation de la mémoire de temps en temps - il semble simplement que mes applications .NET Core 2.0 consomment environ 2 à 3 fois plus de mémoire que lorsqu'elles s'exécutaient sur . NET Core 1.1. 😞

Je suis revenu à .NET Core 1.1 pour le moment et j'y reviendrai plus tard quand j'aurai plus de temps, probablement après la sortie de .NET Core 2.1. (J'ai rencontré un tas de problèmes avec 2.0 et ce n'était que l'un d'entre eux.)

GC.Collect() n'aide pas. J'ai essayé une API Web ASP.NET Core 2.0 et 2.1 très simple qui a un contrôleur qui renvoie un dictionnaire de 200k ints. La mémoire allouée continue d'augmenter à chaque requête, même si l'application n'utilise plus de mémoire.

@Serjster renvoyant des entiers de 200K (4B) prendrait 800Ko. Dans ce cas, vous rencontrez le problème expliqué dans ce commentaire : https://github.com/aspnet/Home/issues/1976#issuecomment -289336916

Dans ce cas, vous devez utiliser un pool de baies pour les réutiliser dans les requêtes.

Il est également bon de savoir que si le code s'exécute en mode 64 bits, les tableaux/listes, etc. contenant des pointeurs sont deux fois plus volumineux qu'en 32 bits. Si je me souviens bien, le framework complet exécute par défaut n'importe quel code CPU 32 bits dans un système d'exploitation 64 bits. Ainsi, les personnes qui migrent leur code peuvent accidentellement rencontrer des problèmes de LOH.

Je travaille avec @Serjster , et voici ce que j'ai trouvé. Si je crée un projet d'api web vanille en utilisant le noyau asp.net (j'ai utilisé 2.1 dans mon dernier test), je remarque que lorsque j'exécute l'outil de diagnostic (ou même vérifie la mémoire de travail du processus définie dans le code), le nombre d'octets retourne continue de grimper alors que j'atteins un point final. Par exemple, si j'ai un seul point de terminaison d'API Web renvoyant un dictionnaireavec 20 000 éléments, voici ce qui se passe :

  1. La première visite à la méthode du contrôleur place la mémoire de processus à 83 Mo.
  2. J'attends quelques secondes, puis deuxième visite, il passe à 86 Mo.
  3. J'attends quelques secondes et la troisième visite passe à 90 Mo.
  4. Encore une fois - 94 Mo.
  5. Je le fais n fois, et il atteint finalement environ 304 Mo. Une fois qu'il fait cela, il se stabilise.

Si l'objet de retour est un objet de taille différente, tous les nombres ci-dessus sont juste plus grands/plus petits (y compris le montant du niveau), mais le modèle de croissance est le même (c'est-à-dire qu'il grandira et grandira jusqu'à ce qu'il se stabilise après de nombreuses demandes) .

Si j'ajoute GC.Collect dans l'appel de méthode (il se produit donc à chaque requête, le niveau de est beaucoup plus bas, mais il y a encore une période de croissance jusqu'à ce qu'il se stabilise.

L'autre point de détail intéressant est le nombre d'objets et la taille du tas lors de la création d'instantanés est en grande partie inchangée à chaque visite. Mais le graphique de la mémoire de processus continue d'afficher un nombre de plus en plus élevé (ceci est également vrai si vous saisissez le processus et retirez la valeur définie de la mémoire de travail).

Je commence à soupçonner que le graphique montre la mémoire allouée (et cette mémoire augmente en fonction de la logique de prévision de l'utilisation/de la demande du noyau asp.net), mais il ne s'agit pas nécessairement de mémoire consommée/fuite. Je n'en sais pas assez pour confirmer, alors je me demande si quelqu'un de plus compétent pourrait intervenir.

EDIT - concernant le commentaire de @davidfowl : concernant votre commentaire sur les choses qui sont rarement collectées... cela pourrait avoir du sens. Mais combien de temps cela prend-il généralement ? Je peux passer plus de 30 secondes entre les demandes, et le GC ne semble jamais ramener ce numéro de mémoire dans le tableau de diagnostic. Je suis sûr que je suis ignorant sur quelque chose ici, mais juste curieux.

EDIT 2 - Maintenant que j'ai lu le lien SO que david a posté ci-dessus plus en détail, je commence à penser que c'est définitivement le problème que nous voyons. Si nous fonctionnons dans un environnement avec une mémoire limitée (ce que nous sommes dans notre environnement de développement où nous voyons cela parce que nous sommes bon marché), nous rencontrons des problèmes avec cela.

Edit 3 - Une question persistante. Pourquoi la mémoire de processus augmente-t-elle constamment, mais la taille du tas n'augmente-t-elle pas s'il s'agit d'un problème LOH ? En fait, je peux comprendre cela maintenant. Le tas est la mémoire utilisée. La mémoire allouée au processeur est la mémoire utilisée plus les blocs de mémoire fragmentés qui ne sont pas utilisés.

@RemyArmstro pouvez-vous changer Dictionary<int, int> en SortedDictionary<int, int> ? Le dictionnaire alloue probablement de la mémoire continue, peut même ajouter des données supplémentaires à chaque entrée. La manière dont SortedDictionary est implémenté fera de nombreuses petites allocations au lieu d'une seule grosse.

Modifier : si vous sérialisez en chaîne et non directement en réponse au flux de sortie, cela peut également entraîner des allocations LOH.

@ wanton7 Votre réponse manque le point. Le dictionnaire n'est que la pointe de l'iceberg. Nous pouvons utiliser des listes, des tableaux, etc. etc. et ils font tous la même chose. Cependant, comme cela a été souligné, si le LOH est à l'origine de cela, comme cela semble être le cas, alors ce comportement est probablement correct ? Sauf que cela pourrait avoir des effets secondaires inquiétants, comme que se passe-t-il lorsque vous manquez de mémoire ? Votre application plante-t-elle simplement ?

@Serjster ok, je pensais que vous n'aviez que de petits cas où cela se produisait. Pour moi, il est très inhabituel d'avoir de grandes listes, des tableaux comme celui-ci et d'envoyer autant de données en un seul appel API si ce n'est pas binaire. Habituellement, lorsque vous avez une sorte d'API Web et que vous en obtenez des données, vous utilisez la pagination. Vous ne devriez pas envoyer 10 000 entrées côté client.
Mais si vous avez beaucoup de problèmes comme celui-ci et qu'il n'y a aucun moyen de changer le fonctionnement de votre API, alors je pense que vous devriez créer vos propres implémentations de liste et de dictionnaire en morceaux. Si vous utilisez vraiment des tableaux de cette taille, vous pouvez les remplacer par vos listes fragmentées ou essayer de les regrouper au démarrage de l'application.

Je souhaite que Microsoft crée des implémentations fragmentées que tout le monde pourrait utiliser dans des situations comme celle-ci.

@ wanton7 encore une fois, vous manquez le point. Peu importe la taille de la liste. Même un seul élément ou une petite liste provoque ce problème.

@Serjster peut-être que je suis juste aveugle mais je ne vois aucun message de votre part dans lequel vous avez dit que l'envoi d'un seul élément ou d'une petite liste provoquerait cela. Vous l'avez supprimé ?

Ou de @RemyArmstro Il parle de dictionnaires de différentes tailles. J'ai vérifié corefx et Dictionary allouera un tableau ou ceux-ci

private struct Entry
{
  public int hashCode;    // Lower 31 bits of hash code, -1 if unused
  public int next;        // Index of next entry, -1 if last
  public TKey key;           // Key of entry
  public TValue value;         // Value of entry
}

L'allocation de 85 000 octets entraînera une allocation LOH, de sorte qu'un dictionnaire avec une capacité de 5313 entrées de clé int et de valeur int entraînera une allocation LOH. La capacité n'est pas la même que le nombre ou les entrées, la capacité semble être étendue par les nombres premiers, vérifiez la méthode Resize privée du dictionnaire privé. Chaque structure peut avoir une allocation supplémentaire plus un remplissage de mémoire, de sorte que des entrées encore plus basses peuvent entraîner une allocation LOH.

Détails de l'implémentation du dictionnaire Dictionary.cs

Edit : URL corrigée

@wanton7 Merci. Je pense que nous comprenons maintenant quel est le problème. C'est juste une déception, il n'y a pas de solution géniale + simple à cela. Il s'agit essentiellement d'en être plus conscient et d'ajuster la façon dont vous écrivez le code. L'inconvénient est que cette mémoire non gérée commence à se sentir un peu plus gérée. :( En fin de compte, nous n'avons peut-être que quelques domaines qui dépassent vraiment cette limite d'allocation, mais l'un des domaines est assez central pour notre application, donc nous le voyons beaucoup actuellement. Je pense que nous devons juste repenser cette pièce, surveiller et essayer de dénicher tous les autres domaines où nous remarquons ce fluage. Merci encore !

Nous avons en fait une situation similaire bientôt et nous devons créer une implémentation fragmentée IList<T> . Je vais utiliser une taille pour les morceaux qui est bitshiftable afin que je puisse simplement utiliser le bitshift et le masque pour l'indexation.

Je voudrais savoir lequel est le plus bénéfique pour GC, gros morceau ou petit morceau ? À partir de tailles comprises entre 1 Ko et 64 Ko. Des morceaux plus petits signifient plus de références pour GC, mais je suppose qu'un gros morceau pourrait être pire pour le compactage et la fragmentation.

votre compréhension est tout à fait correcte - j'irais avec des tailles pas trop grandes ; essayez probablement 4k/8k.

@Maoni0 merci !

J'ai choisi 4 Ko, pour ne pas avoir de mauvaises surprises si jamais nous exécutons notre code sous Mono. Découvert en lisant http://www.mono-project.com/docs/advanced/garbage-collector/sgen/working-with-sgen/ que le seuil LOH n'est que de 8000 octets sous Mono.

Y a-t-il des progrès pour ce problème?

Nous observons le même problème où la mémoire de processus continue de croître même si la taille du tas reste la même (ou diminue).

@sebastienros pouvez-vous jeter un autre coup d'œil à cela ? Peut-être pouvons-nous fournir des instructions détaillées pour aider les gens à approfondir leurs scénarios ?

Voici quelques éléments à considérer avec notre situation :

  • Nous ne renvoyons qu'un objet Dictionary<int, int> avec 1000 valeurs entières stockées. Rien de plus que ça.
  • Nous avons converti notre projet de .NET Core 2.0 --> 2.1
  • Nous rencontrons ce problème dans MS Visual Studio 2017

Coder comme suit :

    public async Task<IActionResult> SampleAction()
    {
        var list = new Dictionary<int, int>();
        for (int n = 0; n < 1000; n++) list.Add(n, n);
        return Ok(list);
    }

Pour reproduire, vous devez simuler une certaine forme de charge modérée. Nous venons de cliquer rapidement en utilisant Postman et avons pu observer ce comportement. Une fois que nous avons arrêté de simuler la charge, nous avons vu la taille du tas diminuer, mais la mémoire du processus est restée la même (même lorsque nous avons forcé GC).

Je le vois également dans l'un de mes projets, mais je peux également le recréer dans une toute nouvelle API .net core ciblant .Net Core 2.1 (SDK 2.1.302).

J'ai joint l'exemple de projet d'API que j'ai créé à l'aide de Visual Studio 15.8.0 Preview 4. Pour montrer l'augmentation de la mémoire, une application de console .net core a atteint les valeurs par défaut GET endpoint toutes les demi-secondes pour obtenir les 2 chaînes. L'utilisation de la mémoire du processus est lente, mais dans un projet qui renvoie plus de données, cela peut augmenter rapidement.

screenshot
WebApplication1.zip

J'ai trouvé ce post sur stack exchange :

https://stackoverflow.com/questions/48301031/why-doesnt-garbage-collector-in-net-core-2-0-free-all-memory

Quelqu'un a-t-il profilé ses applications en mode de publication pour voir si ce comportement est présent ? Je vais essayer aujourd'hui et voir si le problème persiste.

Edit : J'ai essayé le profilage en mode Release et le problème persiste toujours. Je force même un GC pour voir si cela aura un effet.

image

@chrisaliotta merci pour le lien vers ce post, je n'étais pas au courant de ce comportement. Il serait en effet intéressant de voir si cela explique ce que les gens voient.

@Eilon @chrisaliotta Merci mais ce n'est pas lié à cette discussion. .NET ne libérant pas de mémoire en mode débogage est un comportement bien connu et c'est pourquoi nous ne mesurons la consommation de mémoire (et les fuites potentielles) qu'en mode release. De plus, même en mode de publication, vous verrez la mémoire augmenter dans une certaine mesure au fil du temps en raison du mode Server GC. Cet exemple ne prouve donc rien pour deux raisons différentes.

@sebastienros Alors est-ce que le comportement que @beef3333 et moi observons est conforme à ce à quoi on peut s'attendre ? À savoir, où les octets privés restent élevés malgré la diminution de la taille du tas ? Si tel est le cas, il me semble étrange que chaque demande incrémentielle continue de faire croître les octets privés malgré l'espace de tas libre.

En mode Debug oui. Alors, s'il vous plaît, essayez d'utiliser le mode Release et de gérer votre stress pendant une longue période. Si la mémoire augmente indéfiniment, il y a une fuite de mémoire. Si la mémoire est recyclée (même si cela prend une quantité importante de mémoire), alors tout va bien dans votre cas.

Je viens de le tester à nouveau en utilisant le même projet que j'ai attaché en mode Release et je constate exactement le même comportement.

Je vais tester votre application alors, merci.

J'ai fait tourner l'application fournie par @beef3333 en local pendant 2h, avec un taux de 5K RPS, et la mémoire est stable (écart de 1Mo au global, à 400Mo sur une machine de 32Go). Le GC est correctement appelé régulièrement. J'ai également inspecté plusieurs vidages supplémentaires au fil du temps et les différentes instances sont créées et collectées comme prévu. J'utilise 2.1.1.

@sebastienros Merci d'avoir examiné cela. Je pense que ce qu'il faut retenir dans tout ça, c'est que :

  • La gestion de la mémoire se comportera différemment pour une application Web .NET Core par rapport à votre application de bureau ordinaire.
  • Les développeurs doivent se concentrer sur la consommation moyenne de mémoire en tant que requêtes de fonction par seconde (RPS) sur une période donnée.
  • La croissance des octets privés n'est pas toujours indicative d'une fuite de mémoire.

Corrigez-moi si je me trompe, mais il semble que .NET Core augmentera la mémoire allouée en fonction des requêtes moyennes afin d'assurer le temps de réponse le plus rapide ? Si cela est vrai, serait-il prudent de supposer qu'il est probable qu'il ne libérera pas cette mémoire allouée jusqu'à ce que le pool d'applications soit réinitialisé - ou libérera-t-il cette mémoire allouée au fil du temps si le RPS diminue ?

Même problème.
J'ai un service backend webapi asp.net.
Les piles sont asp.net mvc, autofac, automapper, castle.dynamicproxy, entity framework core.
Toute la mémoire sera consommée, puis le service plantera.

La version est 2.1.0

@atpyk Mise à jour vers 2.1.1. Si cela ne vous aide pas, vous devriez vraiment profiler ce qui garde cette mémoire. J'ai utilisé https://www.jetbrains.com/dotmemory/ mais il existe probablement d'autres outils qui peuvent également le faire. Il peut montrer ce qui est réellement alloué dans le Large Object Heap (LOH).

Tu tournes en mode 32bit ? Parce que les allocations de tas d'objets volumineux (plus grandes que ~ 85 000 octets) peuvent provoquer des exceptions de mémoire insuffisante en mode 32 bits en raison de la fragmentation. Vous pouvez dépasser cette limite très facilement en utilisant Dictionary. Vérifiez ce commentaire https://github.com/aspnet/Home/issues/1976#issuecomment -393833505

Si vous exécutez votre code dans son intégralité, le comportement par défaut de .Net Framework consiste à exécuter n'importe quel code CPU en mode 32 bits. Vous devez décocher Préférer 32 bits dans les paramètres du projet ou définir certains paramètres de registre dans le registre de vos serveurs sur 64 bits par défaut.

@wanton7 Merci beaucoup. Je vais essayer vos solutions.

J'ai mis à jour vers 2.1.2 et déployé sur l'application Web Microsoft Azure avec win-x64, mais aucun effet. @wanton7
dotnetcorememory

@atpyk veuillez créer un instantané de la mémoire (dump_ et l'analyser (Visual Studio, MemoScope) pour voir quel objet prend toute la mémoire, ou augmente simplement en nombre. Vous pouvez également en prendre deux et les comparer au fil du temps.

@sabastienros Je pense que suffisamment de personnes ont soulevé une inquiétude à ce sujet pour que vous / MS devriez réellement commencer à analyser cela vous-même. Peut-être que cela fonctionne comme vous l'aviez prévu, mais la conception est défectueuse.

Nos applications finissent par manquer de mémoire et se bloquent, et tout cela s'exécute dans un environnement Azure de production. Ceci est inacceptable.

@atpyk alors cela ressemble à une fuite de mémoire. Profilez votre mémoire pour voir ce qui la garde comme l'a dit @sebastienros .

Une question, utilisez-vous même ASP.NET Core ? Je vous ai relu le premier commentaire et vous mentionnez ASP.NET MVC. ASP.NET MVC et ASP.NET Core sont deux produits complètement différents. Ces problèmes et ce référentiel pour ASP..NET Core.

Edit: Juste à partir des numéros de version, on dirait que vous utilisez ASP.NET Core, mais que vous vouliez vous en assurer.

Nous utilisons .net core MVC. @wanton7
Je fais analyser la mémoire. Peut-être que le Castle Dynamic Proxy a conduit à une fuite de mémoire.
memroy1
memroy2
memroy3
dynamicproxy

@Serjster Vos programmes s'exécutent-ils dans .NET Core 32 bits et se bloquent-ils avec des exceptions de mémoire insuffisante ? Si votre code fait beaucoup d'allocations LOH, la fragmentation de la mémoire en est probablement la raison.
Si vous exécutez dans un environnement 32 bits, vous avez deux choix, corrigez votre code pour éviter LOH ou passez à un environnement 64 bits.

Je ne connais pas très bien Azure, mais après quelques recherches sur Google, j'ai trouvé ceci https://blogs.msdn.microsoft.com/webdev/2018/01/09/64-bit-asp-net-core-on-azure-app -service/

@Serjster Je pense que tous les rapports ici ne sont pas égaux et je préfère vérifier la validité de chaque cas différent. Quelque chose comme "mon application a une fuite de mémoire" ne signifie pas que c'est à cause du framework, donc je préfère m'assurer que chaque cas est réel.

Prenant votre cas par exemple, "Je crois" que j'ai répondu à la raison pour laquelle votre mémoire augmente. Avez-vous pu le réparer après mon explication? Ou cela n'a pas résolu le problème et dans ce cas, pouvez-vous donner une application que je peux exécuter localement pour reproduire le problème ?

@sebastienros Ce que nous avons fini par découvrir, c'est que l'allocation de mémoire ne cessait d'augmenter, même si la consommation de mémoire ne le faisait pas. Je sais que le noyau ASP.NET fait quelques heuristiques pour lui dire qu'il devrait en saisir plus, mais il semblait constamment allouer de plus en plus à chaque demande. Il me paraît presque gourmand à cet égard, mais je peux me tromper.

Quoi qu'il en soit, je pense que le point de @Serjster est que ce fil ne cesse de croître car il y a clairement une certaine confusion ici. Dans l'ancien pays ASP.NET (avant le noyau 1, je crois), nous n'avions pas besoin de voir ce comportement (du moins pas à cette ampleur. Ce n'est peut-être pas vraiment un bogue/problème, mais c'est certainement quelque chose qui cause beaucoup de gens poser sans cesse la même question.

Ce serait bien s'il y avait un article officiel qui traite vraiment de ce fil de haut en bas, au lieu de tourner en rond comme il l'a été. J'espère que cela aide à clarifier.

Ce serait bien s'il y avait un article officiel qui traite vraiment de ce fil de haut en bas, au lieu de tourner en rond comme il l'a été. J'espère que cela aide à clarifier.

Je suis d'accord. Il serait bon que nous sachions pourquoi .NET Core 2.1 est plus "opportuniste" en matière de gestion de la mémoire que les versions précédentes.

@sebastienros pouvons-nous avoir un résumé des problèmes ici ? il y a 81 commentaires - j'ai l'impression qu'ils ne concernent pas tous les problèmes (bien que je ne les ai pas du tout lus attentivement, je peux donc me tromper). si oui, pouvons-nous énumérer tous les problèmes distincts ici et voir si nous avons des repros pour chacun d'eux ? il y a suffisamment de gens qui ont mentionné l'augmentation de la mémoire, je pense que cela nous justifie d'obtenir des reproches et de déterminer s'il s'agit de problèmes généraux ou non.

Sûr. J'essaie actuellement d'identifier tous ces fils et de voir quel est le statut de chacun d'eux. Je finirai par fermer ce problème et rouvrir des problèmes plus spécifiques qui se concentrent sur chaque rapport, car ce fil unique n'est plus durable. Je vais les lier ici pour ceux sur ce fil qui veulent les suivre.

En parallèle je vais travailler sur un document qui liste toutes les recommandations, les problèmes de mémoire connus (LOB, HttpClient, ...) et les moyens d'analyser et de signaler ces problèmes.

Juste pour vous assurer que nous nous soucions de ce problème et afin de détecter les fuites de mémoire de manière préventive, au cours des 6 derniers mois, nous avons exécuté une application ASP.NET en continu sur Azure, sur Linux et Windows, pendant 24h et 7 jours. Ces tests récupèrent la dernière source ASP.NET à chaque itération (quotidienne ou hebdomadaire). Nous mesurons le RPS, la latence, l'utilisation du processeur et de la mémoire. L'application utilise EF Core, MVC et Razor, et est maintenue à 50 % du processeur pour simuler une charge importante.

Vous pouvez voir les résultats ici publiquement sur ce site (voir pour trouver le rapport quotidien): https://msit.powerbi.com/view?r=eyJrIjoiYTZjMTk3YjEtMzQ3Yi00NTI5LTg5ZDItNmUyMGRlOTkwMGRlIiwidCI6IjcyZjk4OGJmLTg2ZjEtNDFhZi05MWFiLTJkN2NkMDExZGI0NyIsImMiOjV9&pageName=ReportSectioneeff188c61c9c6e3a798

Cela nous a permis de résoudre certains problèmes par le passé et d'être sûrs qu'il n'y a pas de fuites fondamentales dans le système en ce moment. Mais c'est loin d'utiliser tous les composants que nous expédions, et il peut y avoir des problèmes que nous devons identifier. Fournir des vidages et des applications qui reproduisent les problèmes sont les principales façons dont vous pouvez nous aider.

@sebastienros Merci pour les mises à jour. Je sais que dans notre cas, le problème concernait davantage un nouveau problème "d'allocation de mémoire gourmande", que nous avions initialement confondu avec une fuite de mémoire. Je ne suis même pas sûr qu'il y ait un problème maintenant, il se pourrait simplement que les nouvelles heuristiques d'optimisation soient beaucoup plus agressives. Pas sûr... mais je pense que vous êtes sur la bonne voie en évaluant vraiment ce fil et en proposant des explications/résumés consolidés sur ce que les gens voient/incompréhension. Bonne chance!

Tous les rapports individuels ont été isolés et recevront des suivis individuels, et seront fermés s'ils sont déjà résolus. N'hésitez pas à vous y abonner pour être tenu au courant.

En parallèle je vais travailler sur un document qui liste toutes les recommandations, les problèmes de mémoire connus (LOB, HttpClient, ...) et les moyens d'analyser et de signaler ces problèmes.

C'est un énorme +1 de ma part. Je pense que l'un des plus gros problèmes ici est la difficulté de recueillir des informations, puis d'essayer d'aider à déterminer _quel_ est le problème. Avoir de bons documents qui peuvent nous permettre de suivre certaines instructions pour (i) rassembler, (ii) tenter un auto-diagnostic et (iii) publier nos vidages/résultats d'une manière efficace pour l'équipe MS peut vraiment aider les deux côtés de la clôture.

Si nous (les développeurs) pouvons être mieux à même de diagnostiquer et/ou de fournir des informations, alors c'est un gagnant-gagnant pour tous.

Merci encore d'avoir écouté @sebastienros - très apprécié, mon pote !

Que pensez-vous de la situation dans l'image ci-dessous ?

Nous exécutons 4 WebApps dans le même plan. Initialement, c'était B1, mis à l'échelle jusqu'à S2 et la mémoire n'a cessé de croître jusqu'à ce que je définisse l'application Web affamée csproj :

<ServerGarbageCollection>false</ServerGarbageCollection>

et également désactiver Application Insights

  1. Je crois que puisque la mémoire peut être contrôlée avec le réglage ci-dessus, il n'y a pas de perte de mémoire. Correct?
  2. Le comportement présenté est-il normal ?

memory eaten up

Même situation ici que @alexiordan , nous avions une console .net core 2.1, qui exécutait certains IHostedServices hébergés dans Kube, À PARTIR de la base AS microsoft/ dotnet:2.1-runtime . Nous voulions activer HealthChecks, nous avons donc ajouté asp.net avec uniquement le middleware HealthChecks et changé l'image de base en microsoft/ dotnet:2.1-aspnetcore-runtime. Le résultat était OOM. Nous avons réussi à stabiliser l'allocation mémoire en ajoutantfaux dans csproj.

Notre analyse a montré que dans une application asp.net, le GC collecte moins fréquemment, et la file d'attente du finaliseur est parcourue moins fréquemment.

De plus, si nous forcions le GC à collecter et parcourir la file d'attente du finaliseur en ajoutant ce qui suit dans notre pipeline,

System.GC.Collect();
System.GC.WaitForPendingFinalizers();
System.GC.Collect();

l'allocation mémoire est restée stable.

Que pensez-vous de la situation dans l'image ci-dessous ?

Nous exécutons 4 WebApps dans le même plan. Initialement, c'était B1, mis à l'échelle jusqu'à S2 et la mémoire n'a cessé de croître jusqu'à ce que je définisse l'application Web affamée csproj :

<ServerGarbageCollection>false</ServerGarbageCollection>

et également désactiver Application Insights

  1. Je crois que puisque la mémoire peut être contrôlée avec le réglage ci-dessus, il n'y a pas de perte de mémoire. Correct?
  2. Le comportement présenté est-il normal ?

memory eaten up

Salut @alexiordan

Nous constatons également un profil de mémoire très similaire lors de l'utilisation de l'IA (application Web sur net core 2.1), avez-vous progressé pour résoudre ce problème ? Évidemment, nous voulons garder l'IA dans les applications.

Il est étrange que l'utilisation augmente à chaque demande, mais définir ce qui précède sur false semble l'arrêter pour moi ? bizarre parce que vous penseriez que true serait la valeur qui l'active, mais il semble que ce soit l'inverse ...

J'ai oublié de mentionner que peu de temps après avoir annoncé mon intention d'écrire un article sur les problèmes décrits dans ce fil, je l'ai fait. Vous pouvez le voir ici : https://github.com/sebastienros/memoryleak

Il est livré avec une petite application qui restitue les modèles sur un graphique, en direct.

mais définir ce qui précède sur false semble l'arrêter pour moi? bizarre parce que vous penseriez que true serait la valeur qui l'active, mais il semble que ce soit l'inverse ...

La récupération de place du client (optimisée pour le partage de mémoire avec de nombreuses applications et la préservation de la mémoire libre) est plus agressive que la récupération de place du serveur (optimisée pour le débit et la simultanéité).

En définissant SGC sur false, mon API principale asp.net est passée de 150 Mo à 48 Mo et n'a pas augmenté à chaque demande par la suite. Donc, en réalité, est-ce le meilleur cadre pour la production, pour l'instant ?

@kgrosvenor en fait, ça dépend. Citant l'excellent article de @sebastienros :

Dans un environnement de serveur Web typique, la ressource CPU est plus critique que la mémoire, c'est pourquoi l'utilisation du serveur GC est mieux adaptée. Cependant, certains scénarios de serveur pourraient être plus adaptés pour un Workstation GC, par exemple sur une haute densité hébergeant plusieurs applications Web où la mémoire devient une ressource rare.

Merci, c'est très pratique, je garderai à l'esprit - je suivrai ce fil pour d'autres mises à jour, cela étant dit, j'adore le noyau asp.net :)

Souffrez également de cela avec une application console .net core 2.1. croissance constante de la mémoire. ont défini les conteneurs docker à un maximum bas pour qu'il le frappe et redémarre, ce qui fonctionne bien, mais c'est moche.

Des nouvelles de ce côté ? Nous avons également le même comportement dans ASP.Net Core v2.1. D'après ce que je peux voir dans le commit https://github.com/aspnet/AspNetCore/commit/659fa967a1900653f7a82f02624c7c7995a3b786 , il semble qu'il y ait eu un problème de gestion de la mémoire qui va être résolu dans la v3.0 ?

@flo8 avez-vous essayé de passer à la version 2.2 et vérifié le comportement ? https://dotnet.microsoft.com/download

Exécuter la dernière version 2.2 et avoir également ce problème - créer simplement une liste de 1 million d'ints et renvoyer un emptyResult() augmentera mon tas de quelques centaines de Mo à chaque requête jusqu'à ce que je manque de mémoire. Définir ServerGarbageCollection sur false l'a corrigé, bien que cela ne semble pas être le bon correctif ...

@dre99gsx comme il semble que vous ayez une reproduction facile, pourriez-vous partager un projet et des étapes pour que je puisse faire la même chose localement ?

Dans ce cas, il doit remplir le LOB mais ils doivent être collectés sur gen2. Veuillez également partager l'environnement sur lequel je peux reproduire cela, le système d'exploitation, la mémoire, la charge.

Désolé pour la réponse cryptée. Assez facile:
(Windows 7 64 bits, 16 Go de RAM, Google Chrome pour les requêtes http, communauté VS2017) - Je m'habitue toujours à ajouter du code à ces threads, pardonnez l'apparence)

  • démarrer une nouvelle application Web .NET Core 2.2
  • Injecter une classe de service, étendue (rien de spécial ..) dans le constructeur du contrôleur
  • Demandez à l'action Index() du contrôleur d'appeler une méthode de cette classe de service
  • Créez une classe de modèle (DumbClass) avec une propriété : public int ID {get ; ensemble;}
  • Dans la méthode de classe de service, instanciez une liste et remplissez-la :
    var lst = nouvelle liste();
    pour (i=0; i<10000000; i++) <--- note : 10 millions d'itérations
    {
    lst.add(new DumbClass(){ID=i});
    }
  • retour de la méthode, vous n'avez rien à renvoyer, mais vous pouvez également renvoyer cette liste ...
  • index () renvoie un nouveau résultat vide ();

Maintenant, appelez simplement l'action toutes les 8 secondes et regardez la mémoire du profileur augmenter. Sur mon système, voici ce que je vois sur Private Bytes :

Application de démarrage : 155 Mo
1ère demande d'obtention http : 809 Mo
2e : 1,2 Go
3ème : 1,4 Go
4ème : 1,8 Go
5ème : 2,1 Go... 2,3 Go... 2,6 Go...

Maintenant, à un moment donné, GC semble se déclencher, mais il ne descend jamais en dessous de 3 Go dans cet exemple. Comme mentionné, si je tourne le ServerGC sur false, il ne dépasse jamais 1 Go, bien qu'il grimpe toujours là-haut et oscille à 1 Go.

Faites-moi savoir si cela aide, et si vous pouvez le reproduire. Oh, j'ai lu votre message github : "Gestion de la mémoire et modèles dans ASP.NET Core", une rédaction fantastique, merci de votre contribution !

@dre99gsx 👋

Je m'habitue encore à ajouter du code à ces fils, pardonnez l'apparence)

Aucun problème :) Question : pourriez-vous réellement mettre l'intégralité de l'exemple d'application sur GitHub (repo gratuit) ou quelque part similaire ? C'est le moyen le plus simple pour quelqu'un d'autre de cloner/télécharger rapidement _l'intégralité_ de l'application/du référentiel exact avec lequel vous avez joué.

De plus, des captures d'écran de l'utilisation de la mémoire à l'aide TaskManager aideraient (si sur Windows - ou l'équivalent sur *nix .. qui est la commande top ??)

Grand effort jusqu'à présent!

/me revient à regarder silencieusement ce fil avec sérieux.

Pour rappel, vous pouvez voir des démos et des explications pour tous les symptômes décrits dans le fil ici : https://github.com/sebastienros/memoryleak

De plus, chacun de ces problèmes a été traité individuellement et aucun ne s'est avéré être un bogue dans le noyau dotnet __jusqu'à présent__, mais un comportement attendu.

@ dre99gsx Revenons maintenant au commentaire récent, je vous exhorte à déposer un problème distinct de ce fil. Étant sur mon téléphone je n'avais pas réalisé que c'était "celui-là" ;). Dès votre premier commentaire, vous déclarez

jusqu'à ce que je manque de mémoire

Je m'attendrais donc à une exception OutOfMemory, c'est pourquoi j'ai demandé une reproduction. Mais dans votre commentaire suivant, vous déclarez :

Maintenant, à un moment donné, GC semble démarrer, mais il ne descend jamais en dessous de 3 Go dans cet exemple

Il n'y a donc pas de problème de mémoire. Ceci est typique d'une machine avec beaucoup de cœurs et beaucoup de mémoire disponible. Le GC libérera les tas gérés, mais la mémoire sera toujours validée car il n'y a aucune raison de l'annuler (beaucoup de mémoire disponible). Il s'agit d'un comportement standard dans .NET, et je le démontre dans l'article que j'ai indiqué. Vous pouvez également lire ceci : https://blogs.msdn.microsoft.com/maoni/2018/11/16/running-with-server-gc-in-a-small-container-scenario-part-0/

Je sais que l'équipe d'exécution travaille actuellement sur des moyens de limiter les applications .NET exécutées dans des conteneurs afin qu'elles n'utilisent pas autant de mémoire, ciblant 3.0, pour résoudre certains scénarios de microservices.

Et comme vous l'avez découvert par vous-même, si votre application ne peut pas utiliser toute la mémoire disponible sur le serveur, vous devez utiliser le mode GC du poste de travail.

Correct, lorsque j'ai déclaré "manque de mémoire", je me base sur le fait qu'aucune mémoire n'est disponible pour aucune autre application via Windows Private Working Set (Gestionnaire de tâches); pas une "exception de mémoire insuffisante".

Tu sais quoi, tu as raison. Cela ressemble de plus en plus à un comportement attendu, et s'il y a autant de mémoire disponible, pourquoi dépenser des ressources pour la libérer si personne d'autre n'en a besoin ! Je comprends. Tant que GC est assez intelligent pour libérer de la mémoire afin que les autres applications ne soient pas limitées , je vais le garder tel quel et le laisser faire son travail. Merci encore!

J'ai développé mon application dans Asp.net core 2.2, faisant également face au même problème lié à la libération de mémoire.
chaque appel augmente la mémoire dans la plage de 40 à 50 Mo à chaque fois qu'il n'obtient jamais de libération.

J'ai également ajouté la balise mentionnée ServerGarbageCollection>false
toujours confronté au même problème pour 50 utilisateurs, il utilise environ 2 Go de RAM en mode In-Process (processus de travail w3wp iis)

S'il vous plaît aider.

Aidez-moi s'il vous plait !! le même problème que ankitmori14

@ankitmori14 , @ikourfaln - si vous avez lu le commentaire de @sebastienros sur https://github.com/aspnet/AspNetCore/issues/1976#issuecomment -449675298 et pensez toujours qu'il y a un problème de mémoire, veuillez créer un nouveau problème avec des étapes détaillées pour reproduire le problème, ainsi que toute autre information et trace dont vous disposez sur le comportement. N'oubliez pas qu'à moins que l'application/le processus ait réellement des erreurs, il est peu probable (mais pas impossible) qu'il y ait un bogue. Le Garbage Collector 'Server' par défaut n'essaie pas d'utiliser le moins de mémoire possible ; il se déclenche plutôt quand il le faut , par exemple lorsque la mémoire s'épuise. Ainsi, même une petite application peut utiliser 2 Go de mémoire et ce n'est pas un problème car il reste encore 4 Go de libre.

Salut,

Nous rencontrons ce problème : https://stackoverflow.com/questions/53868561/dotnet-core-2-1-hoarding-memory-in-linux

Fondamentalement, la mémoire augmente au fil des jours du processus jusqu'à ce que Kubernetes la tue car elle atteint la limite configurée de 512 Mo. Assez drôle, la mémoire a considérablement diminué lors d'un vidage de mémoire, sans que le processus ne redémarre ou quoi que ce soit. En examinant le vidage de la mémoire, nous avons vu beaucoup d'objets sans racine.

Nous avons également désactivé le GC concurrent (c'est-à-dire: GC en arrière-plan) hier et cela semble aller mieux maintenant, mais nous devrons attendre au moins une semaine pour confirmer.

<PropertyGroup>
  <ServerGarbageCollection>false</ServerGarbageCollection>
  <ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>
</PropertyGroup>

Question @vtortola , lorsque vous avez configuré la limite de 512 Mo pour votre application, aviez-vous une idée du nombre de requêtes simultanées que vous vouliez gérer ou avez-vous testé le nombre de requêtes simultanées que votre application pouvait gérer avant de tomber ??

Nous avons effectué des tests préliminaires et approximatifs et vérifié que nous pouvions gérer 500 websockets simultanés par pod en utilisant 512 Mo. Nous avons couru pendant des heures avec 2 pods et 1000 connexions simultanées avec une mémoire inférieure à 150 Mo. L'application déployée, avec 2 pods, a entre 150 et 300 connexions simultanées à tout moment, et la mémoire varie de moins de 100 Mo les premiers jours, jusqu'à atteindre les 512 Mo en environ 2 semaines. Il ne semble pas y avoir de corrélation entre le nombre de connexions et la mémoire utilisée. Plus de 70% des connexions durent 10 minutes.

Pourriez-vous partager un vidage mémoire lorsqu'il est à 100 Mo et 512 Mo pour voir quelles instances sont encore actives ?

Je crains de ne pas pouvoir partager un vidage, car il contient des cookies, des jetons et de nombreuses données privées.

Pouvez-vous comparer alors localement alors? En termes de quel objet prend le plus de mémoire, et si leur nombre n'est pas corrélé à votre charge. Par exemple, si vous avez 300 connexions, il ne devrait pas y avoir d'objets de connexion 3K.

Malheureusement, le test de réglage de <ConcurrentGarbageCollection>false</ConcurrentGarbageCollection> n'a pas aidé et le processus accumule toujours de la mémoire.

Nous avons un vidage Linux du processus, le seul outil dont nous disposons pour l'analyser est lldb et nous sommes très novices dans ce domaine.

Voici quelques données au cas où ça sonnerait une cloche:

(lldb) eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x00007F8481C8D0B0
generation 1 starts at 0x00007F8481C7E820
generation 2 starts at 0x00007F852A1D7000
ephemeral segment allocation context: none
         segment             begin         allocated              size
00007F852A1D6000  00007F852A1D7000  00007F853A1D5E90  0xfffee90(268430992)
00007F84807D0000  00007F84807D1000  00007F8482278000  0x1aa7000(27947008)
Large object heap starts at 0x00007F853A1D7000
         segment             begin         allocated              size
00007F853A1D6000  00007F853A1D7000  00007F853A7C60F8  0x5ef0f8(6222072)
Total Size:              Size: 0x12094f88 (302600072) bytes.
------------------------------
GC Heap Size:            Size: 0x12094f88 (302600072) bytes.

Et le résultat de dumpheap -stat : https://pastebin.com/ERN7LZ0n

Vous pouvez trouver comment Grafana signalait l'état de la mémoire dans https://stackoverflow.com/questions/53868561/dotnet-core-2-1-hoarding-memory-in-linux

Nous avons un autre groupe de services dans le noyau dotnet qui fonctionnent parfaitement, bien qu'ils n'utilisent pas de websockets.

@vtortola cela vous dérangerait-il de créer un nouveau problème afin que nous ne bifurquions pas celui-ci. Je pense que le fait qu'il utilise WebSockets le rend unique et nous devons créer au moins un exemple d'application qui se comporterait comme le vôtre et le stresserait sur une longue période. Pourriez-vous décrire ce que font vos clients avec le websocket, comment ils s'y connectent et pendant combien de temps ? Je pourrais configurer une application comme celle-ci et l'exécuter pendant des jours, et prendre des vidages de mémoire si je vois la mémoire augmenter.

Il peut également s'agir d'un conflit entre le GC et Kubernetes, de sorte que le GC ne soit pas averti qu'il est proche de la limite du pod.

@richlander travaille sur un problème similaire. Rich est-ce que ce cas (lire les 6 derniers commentaires uniquement) pourrait être lié à ce sur quoi vous travaillez ?

si cela fait une différence, j'ai le même problème (ou très similaire). J'ai une douzaine d'applications de console .net core 2.1 et 2.2 exécutées sur docker - chaque conteneur avec une limite de mémoire de 512 Mo, utilisant également des websockets (client), et les applications atteignent la limite de mémoire du conteneur tous les ~ 3 jours. Le conteneur s'arrête alors et redémarre.

Tout utilise des services authentifiés, donc je ne peux pas fournir ce que j'ai, mais je pourrais peut-être mettre en place quelque chose qui utilise un site de test pour reproduire le problème.

Quels paramètres utilisez-vous pour définir les limites de mémoire ? Juste --memory ou aussi --memory-swap ?

Quand je le teste, je vois qu'il est très bien pris en compte, ne dépassant jamais la limite que j'ai fixée, même avec une limite super basse comme 16MiB, cependant il s'échange sur disque comme un fou. Et je teste avec une application qui effectue des requêtes db (EF) et le rendu des vues Razor.

@sebastienros absolument, j'ai créé https://github.com/aspnet/AspNetCore/issues/6803

Faites-moi savoir si vous avez besoin de plus d'informations.

Je rencontre actuellement des problèmes de mémoire.

Après une heure de production, toute la mémoire est utilisée par le processus w3wp.exe (exécutant .NET Core InProcess). Changer le GC en Workstation, n'a pas fait l'affaire pour moi.

Après avoir analysé un vidage de mémoire, j'ai trouvé ce problème similaire, https://github.com/aspnet/AspNetCore/issues/6102.

J'espère le tester en production plus tard dans la journée, après la mise à niveau vers le dernier environnement d'exécution .NET Core, actuellement 2.2.3. Je vous ferai savoir comment ça se passe.

J'ai des problèmes similaires avec l'utilisation de la mémoire. Si je fixe des limites sur mes conteneurs Linux, cela rend simplement le MOO plus rapide. Cela se produit même en utilisant les modèles de base de Visual Studio. Une chose que j'ai trouvée - Core 2.1 et 2.2 sont affectés. Core 2.0 n'est pas - mais c'est EOL :(

Voir au dessus

Je sais que l'équipe d'exécution travaille actuellement sur des moyens de limiter les applications .NET exécutées dans des conteneurs afin qu'elles n'utilisent pas autant de mémoire, ciblant 3.0, pour résoudre certains scénarios de microservices.

Et comme vous l'avez découvert par vous-même, si votre application ne peut pas utiliser toute la mémoire disponible sur le serveur, vous devez utiliser le mode GC du poste de travail.

Malheureusement, le mode station de travail n'aide pas du tout - malheureusement, je ne suis pas vraiment en mesure d'essayer l'un des aperçus 3.0 ou similaires.

@PrefabPanda assurez-vous que vous utilisez réellement le mode GC du poste de travail en vérifiant que System.Runtime.GCSettings.IsServerGC est false .

Cela pourrait alors être une véritable fuite de mémoire. Ouvrez un problème séparé peut-être la meilleure étape.

@ wanton7 - merci, j'ai revérifié et c'est définitivement réglé.

@DAllanCarr - fera l'affaire

Je suis presque certain que ce n'est pas une fuite de mémoire. Plus comme les paramètres Docker peuvent ne pas être appliqués correctement et le GC ne démarre pas avant que la limite ne soit atteinte. Je sais que la 3.0 introduit des correctifs dans ce sens.

@richlander ce problème ressemble-t-il à quelque chose qui est connu pour ne pas fonctionner dans 2.2 ?

@PrefabPanda cela vous dérangerait-il de partager la version exacte de docker que vous utilisez et le fichier de composition de docker ? J'essaie de reproduire mais entre les versions docker et docker-compose, j'ai du mal à reproduire ce problème localement.

@sebastienros , @richlander - Merci de m'avoir répondu. J'apprécie vraiment cela.

Mes versions de Docker :

Communauté de bureau Docker
Édition 2.0.0.2 (30215)

Moteur : 18.09.1
Composer : 1.23.2

Voir ci-joint le projet de test complet :
WebApplication1.zip

tester les requêtes curl.zip

Juste au cas où, mon Dockerfile :

DE mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base
RÉP TRAVAIL /app
EXPOSER 80
EXPOSER 443

DE mcr.microsoft.com/dotnet/core/ sdk:2.2 AS build
REPTRAVAIL /src
COPIER ["ApplicationWeb1/ApplicationWeb1.csproj", "ApplicationWeb1/"]
RUN dotnet restore "WebApplication1/WebApplication1.csproj"
COPIE . .
WORKDIR "/src/WebApplication1"
RUN dotnet build "WebApplication1.csproj" -c Release -o /app

DE construire COMME publier
RUN dotnet publie "WebApplication1.csproj" -c Release -o /app

DE base AS finale
RÉP TRAVAIL /app
COPIER --from=publier /app .
ENTRYPOINT ["dotnet", "WebApplication1.dll"]

docker-compose.yml :

Version : '2.4'

prestations de service:
application Web1 :
image : ${DOCKER_REGISTRY-}application Web1
mem_réservation : 128 m
mem_limit : 256 m
memswap_limit : 256 m
processeur : 1
construire:
le contexte: .
fichier docker : WebApplication1/Dockerfile

docker-compose-override.yml :

Version : '2.4'

prestations de service:
application Web1 :
environnement:
- ASPNETCORE_ENVIRONMENT=Développement
- ASPNETCORE_URLS=https://+:443;http://+:80
-ASPNETCORE_HTTPS_PORT=44329
- DOTNET_RUNNING_IN_CONTAINER=vrai
- DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=vrai
- ASPNETCORE_preventHostingStartup=true
ports :
- "50996:80"
- "44329:443"
tomes :
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/ https:ro
- ${APPDATA}/Microsoft/UserSecrets :/root/.microsoft/us ersecrets :ro

@sebastienros - même s'il existe un moyen de placer une variable d'environnement distincte dans le conteneur pour que le GC l'examine, cela fonctionnerait très bien pour moi.

J'ai essayé de l'appeler explicitement, mais cela n'aide pas - je suppose que même en l'appelant dans le code, il voit toujours la mauvaise taille de mémoire du conteneur/machine.

@PrefabPanda lorsque vous avez dit "J'ai essayé de l'appeler explicitement", pourriez-vous préciser ce que signifie exactement ? GC voit la limite de mémoire de conteneur correcte si vous en avez spécifié une, qu'elle soit déclenchée naturellement ou induite. si vous avez appelé GC.Collect(), il effectuera une collection de blocage complète ; et si vous faites GC.Collect(2, GCCollectionMode.Default, true, true) il fera un GC de compactage complet, lorsque cela retournera le tas est à la plus petite taille possible, quelle que soit la limite du conteneur ou toute autre chose.

Salut @Maoni0 - J'ai essayé GC.Collect(2, GCCollectionMode.Default, true, true)

Je viens de voir un autre commentaire disant que 256 Mo est trop petit pour 2.2, mais pourrait être "fixé" dans 3.0. Il me semble qu'il va falloir que j'expérimente un peu plus...

si vous avez essayé GC.Collect(2, GCCollectionMode.Default, true, true) et que la mémoire n'a pas diminué comme prévu, cela signifie que vous avez une véritable fuite de mémoire. pas sûr de la quantité d'outils disponibles dans votre environnement. pouvez-vous exécuter des commandes sos du tout? si c'est le cas, vous pouvez toujours regarder ce qui reste sur le tas après votre GC induit

Merci @ Maoni0 - gardez à l'esprit que j'utilise un modèle dans Visual studio - il n'y a littéralement aucun code personnel dans ce projet de test que j'ai joint. J'ai d'autres commentaires à traiter, je vous contacterai. Merci beaucoup.

@ Maoni0 - J'ai essayé de définir la limite supérieure, aucune différence. On dirait que je vais devoir essayer l'aperçu 3.0

J'aimerais verrouiller les commentaires sur ce problème car il s'agit d'un problème très ancien dont tous les cas ont été traités dans des problèmes distincts. J'ai commenté celui-ci par erreur en pensant que je commentais https://github.com/aspnet/AspNetCore/issues/10200

Merci

Cette page vous a été utile?
0 / 5 - 0 notes