web-dev-qa-db-fra.com

C #: exception de mémoire insuffisante

Aujourd'hui, ma demande aujourd'hui a jeté un OutOfMemoryException. Pour moi, cela était toujours presque impossible puisque j'ai 4 Go RAM et beaucoup de mémoire virtuelle aussi. L'erreur s'est produite lorsque j'ai essayé d'ajouter une collection existante à une nouvelle liste.

List<Vehicle> vList = new List<Vehicle>(selectedVehicles);  

À ma connaissance, il n’ya pas beaucoup de mémoire allouée ici puisque les véhicules que ma nouvelle liste devrait contenir existent déjà dans la mémoire. Je dois admettre que Vehicle est une classe très complexe et j'ai essayé d'ajouter environ 50 000 éléments à la fois à la nouvelle liste. Mais comme tous les Vehicles de l'application proviennent d'une base de données de seulement 200 Mo: je n'ai aucune idée de ce qui peut causer un OutOfMemoryException à ce stade.

63
TalkingCode

Deux points:

  1. Si vous utilisez Windows 32 bits, vous n’avez pas tous les 4 Go accessibles, seulement 2 Go.
  2. N'oubliez pas que l'implémentation sous-jacente de List est un tableau. Si votre mémoire est très fragmentée, il se peut que l'espace alloué ne soit pas suffisant pour allouer votre List, bien qu'au total, vous disposiez de beaucoup de mémoire libre.
72
Tudor

Sujet de 3 ans, mais j'ai trouvé une autre solution de travail. Si vous êtes certain de disposer de suffisamment de mémoire libre, d’un système d’exploitation 64 bits et d’obtenir des exceptions, accédez à la section Project properties -> Build et assurez-vous de définir x64 comme un Platform target.

enter image description here

. Net4.5 n'a plus de limite de 2 Go pour les objets. Ajoutez ces lignes à App.config

<runtime>
    <gcAllowVeryLargeObjects enabled="true" />    
</runtime>

et il sera possible de créer de très gros objets sans avoir OutOfMemoryException

Veuillez noter que cela ne fonctionnera que sur les systèmes d'exploitation x64!

67
Lendmann

Les données stockées dans la base de données comparées à la mémoire de votre application sont très différentes.

Il n'y a pas moyen d'obtenir la taille exacte de votre objet mais vous pouvez le faire:

GC.GetTotalMemory() 

Après le chargement d’une certaine quantité d’objets, voyez combien de mémoire est en train de changer pendant le chargement de la liste.

Si c'est la liste qui cause l'utilisation excessive de la mémoire, nous pouvons trouver des moyens de la minimiser. Par exemple, pourquoi voulez-vous que 50 000 objets soient chargés en mémoire tous en même temps? Ne serait-il pas préférable d'appeler la base de données selon vos besoins?

Si vous regardez ici: http://www.dotnetperls.com/array-memory , vous verrez aussi que les objets dans .NET sont plus grands que leurs données réelles. Une liste générique est encore plus un bourreau de mémoire qu'un tableau. Si vous avez une liste générique dans votre objet, il se développera encore plus rapidement.

13
Adam Pedley

OutOfMemoryException (sur les machines 32 bits) concerne tout autant la fragmentation que les limites réelles de la mémoire. Vous en trouverez beaucoup, mais voici mon premier hit Google qui en parle brièvement: http: //blogs.msdn .com/b/joshwil/archive/2005/08/10/450202.aspx . (@ Anthony Pegram fait référence au même problème dans son commentaire ci-dessus).

Cela dit, il existe une autre possibilité qui vous vient à l’esprit pour votre code ci-dessus: vous utilisez le constructeur "IEnumerable" dans la liste, vous pouvez ne donne aucune indication à l'objet quant à la taille de la collection que vous passez au constructeur List. Si l'objet que vous transmettez n'est pas une collection (n'implémente pas l'interface ICollection, alors, dans les coulisses, l'implémentation de la liste devra être agrandie plusieurs (ou plusieurs) fois, chaque fois en laissant derrière un tableau trop petit qui doit être ramassé. Le ramasse-miettes ne parviendra probablement pas assez rapidement à ces matrices mises au rebut et vous obtiendrez votre erreur.

La solution la plus simple consiste à utiliser le constructeur List(int capacity) pour indiquer au cadre quelle taille de tableau sauvegarder allouer (même si vous estimez et devinez juste "50000" par exemple), puis utilisez le AddRange(IEnumerable collection) méthode pour renseigner votre liste.

Donc, le plus simple "réparer" si j'ai raison: remplacer

List<Vehicle> vList = new List<Vehicle>(selectedVehicles);

avec

List<Vehicle> vList = new List<Vehicle>(50000);  
vList.AddRange(selectedVehicles);

Tous les autres commentaires et réponses s'appliquent toujours en termes de décisions de conception globales - mais ceci pourrait constituer une solution rapide.

Remarque (comme @Alex a commenté ci-dessous), il ne s'agit que d'un problème si selectedVehicles n'est pas une ICollection.

8
Tao

Mon équipe de développement a résolu cette situation:

Nous avons ajouté le script de post-génération suivant dans le projet .exe et nous l'avons compilé à nouveau, en définissant la cible sur x86 et en augmentant de 1,5 Go. La mémoire de la cible x64 Platform augmentait également à l'aide de 3,2 Go. Notre application est en 32 bits.

URL associées:

Scénario:

if exist "$(DevEnvDir)..\tools\vsvars32.bat" (
    call "$(DevEnvDir)..\tools\vsvars32.bat"
    editbin /largeaddressaware "$(TargetPath)"
)
5
Mehmet Kurt

Vous ne devez pas essayer d’apporter toute la liste en même temps, la taille des éléments de la base de données n’est pas la même que celle qu’elle prend en mémoire. Si vous souhaitez traiter les éléments, vous devez utiliser un pour chaque boucle et tirer parti du chargement paresseux du cadre d’entités afin de ne pas conserver tous les éléments en mémoire en même temps. Si vous souhaitez afficher la liste, utilisez la pagination (.Skip () et .take ()).

3
Sr.PEDRO

Alors que le CPG compacte le petit tas d’objets dans le cadre d’une stratégie d’optimisation visant à éliminer les trous de mémoire, le CPG ne compacte jamais le gros tas d’objets pour des raisons de performances ** (le coût du compactage est trop élevé pour les gros objets (plus de 85 Ko) ) **. Par conséquent, si vous exécutez un programme qui utilise de nombreux objets volumineux dans un système x86, vous risquez de rencontrer des exceptions OutOfMemory. Si vous exécutez ce programme sur un système x64, vous pouvez avoir un tas fragmenté.

3
Ali Bayat

Je sais que c’est une vieille question, mais comme aucune des réponses ne mentionne le grand tas d’objets, cela pourrait être utile à d’autres qui trouvent cette question.

Toute allocation de mémoire dans .NET supérieure à 85 000 octets provient du tas d'objets volumineux (LOH) et non du tas d'objets petits normal. Pourquoi est-ce important? Parce que le tas d'objets volumineux n'est pas compacté. Ce qui signifie que le tas d'objets volumineux est fragmenté, ce qui, selon mon expérience, entraîne inévitablement des erreurs de mémoire insuffisante.

Dans la question initiale, la liste contenait 50 000 articles. En interne, une liste utilise un tableau, en supposant que 32 bits nécessitent 50 000 x 4 octets = 200 000 octets (ou le double si 64 bits). Donc, cette allocation de mémoire provient du tas d'objets volumineux.

Alors que pouvez-vous faire à ce sujet?

Si vous utilisez une version .net antérieure à la version 4.5.1, tout ce que vous pouvez faire pour y remédier consiste à prendre conscience du problème et à l'éviter. Ainsi, dans ce cas, au lieu d'avoir une liste de véhicules, vous pourriez avoir une liste de listes de véhicules, à condition qu'aucune liste ne contienne jamais plus de 18 000 éléments. Cela peut conduire à un code laid, mais c’est un travail viable.

Si vous utilisez .net 4.5.1 ou une version ultérieure, le comportement du ramasse-miettes a changé de façon subtile. Si vous ajoutez la ligne suivante à l'endroit où vous êtes sur le point d'allouer de grandes quantités de mémoire:

System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;

cela forcera le ramasse-miettes à compacter le tas d'objets volumineux - la prochaine fois seulement.

Ce n'est peut-être pas la meilleure solution, mais voici ce qui a fonctionné pour moi:

int tries = 0;
while (tries++ < 2)
{
  try 
  {
    . . some large allocation . .
    return;
  }
  catch (System.OutOfMemoryException)
  {
    System.Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;
    GC.Collect();
  }
}

bien sûr, cela n’aide que si vous avez la mémoire physique (ou virtuelle) disponible.

1
Brian Cryer

Au fur et à mesure que .Net progresse, il semble que leur capacité à ajouter de nouvelles configurations 32 bits plaise à tout le monde.

Si vous êtes sur .Net Framework 4.7.2, procédez comme suit:

Aller aux propriétés du projet

Construire

Décocher 'préfère 32 bits'

À votre santé!

1
user2326106