58 votes

Trouver la cause d'une fuite de mémoire dans Ruby

J'ai découvert une fuite de mémoire dans mon Rails de code, c'est-à-dire, j'ai trouvé ce code fuites, mais pas pourquoi il ya des fuites. Je l'ai réduite à un cas de test qui ne nécessite pas de Rails:

require 'csspool'
require 'ruby-mass'

def report
    puts 'Memory ' + `ps ax -o pid,rss | grep -E "^[[:space:]]*#{$$}"`.strip.split.map(&:to_i)[1].to_s + 'KB'
    Mass.print
end

report

# note I do not store the return value here
CSSPool::CSS::Document.parse(File.new('/home/jason/big.css'))

ObjectSpace.garbage_collect
sleep 1

report

ruby-masse soi-disant me permet de voir tous les objets en mémoire. CSSPool est un CSS analyseur basé sur racc. /home/jason/big.css est un 1.5 MO fichier CSS.

Ce sorties:

Memory 9264KB

==================================================
 Objects within [] namespace
==================================================
  String: 7261
  RubyVM::InstructionSequence: 1151
  Array: 562
  Class: 313
  Regexp: 181
  Proc: 111
  Encoding: 99
  Gem::StubSpecification: 66
  Gem::StubSpecification::StubLine: 60
  Gem::Version: 60
  Module: 31
  Hash: 29
  Gem::Requirement: 25
  RubyVM::Env: 11
  Gem::Specification: 8
  Float: 7
  Gem::Dependency: 7
  Range: 4
  Bignum: 3
  IO: 3
  Mutex: 3
  Time: 3
  Object: 2
  ARGF.class: 1
  Binding: 1
  Complex: 1
  Data: 1
  Gem::PathSupport: 1
  IOError: 1
  MatchData: 1
  Monitor: 1
  NoMemoryError: 1
  Process::Status: 1
  Random: 1
  RubyVM: 1
  SystemStackError: 1
  Thread: 1
  ThreadGroup: 1
  fatal: 1
==================================================

Memory 258860KB

==================================================
 Objects within [] namespace
==================================================
  String: 7456
  RubyVM::InstructionSequence: 1151
  Array: 564
  Class: 313
  Regexp: 181
  Proc: 113
  Encoding: 99
  Gem::StubSpecification: 66
  Gem::StubSpecification::StubLine: 60
  Gem::Version: 60
  Module: 31
  Hash: 30
  Gem::Requirement: 25
  RubyVM::Env: 13
  Gem::Specification: 8
  Float: 7
  Gem::Dependency: 7
  Range: 4
  Bignum: 3
  IO: 3
  Mutex: 3
  Time: 3
  Object: 2
  ARGF.class: 1
  Binding: 1
  Complex: 1
  Data: 1
  Gem::PathSupport: 1
  IOError: 1
  MatchData: 1
  Monitor: 1
  NoMemoryError: 1
  Process::Status: 1
  Random: 1
  RubyVM: 1
  SystemStackError: 1
  Thread: 1
  ThreadGroup: 1
  fatal: 1
==================================================

Vous pouvez voir le mémoire va moyen . Certains des compteurs, mais pas d'objets spécifiques à CSSPool sont présents. J'ai utilisé ruby-masse "index" méthode pour inspecter les objets qui ont des références comme suit:

Mass.index.each do |k,v|
    v.each do |id|
        refs = Mass.references(Mass[id])
        puts refs if !refs.empty?
    end
end

Mais encore une fois, cela ne me donne pas tout ce qui est lié à CSSPool, juste gem info et autres.

J'ai aussi essayé de la sortie "GC.stat"...

puts GC.stat
CSSPool::CSS::Document.parse(File.new('/home/jason/big.css'))
ObjectSpace.garbage_collect
sleep 1
puts GC.stat

Résultat:

{:count=>4, :heap_used=>126, :heap_length=>138, :heap_increment=>12, :heap_live_num=>50924, :heap_free_num=>24595, :heap_final_num=>0, :total_allocated_object=>86030, :total_freed_object=>35106}
{:count=>16, :heap_used=>6039, :heap_length=>12933, :heap_increment=>3841, :heap_live_num=>13369, :heap_free_num=>2443302, :heap_final_num=>0, :total_allocated_object=>3771675, :total_freed_object=>3758306}

Si je comprends bien, si un objet n'est pas référencée et la collecte des ordures se passe, alors que l'objet doit être effacé de la mémoire. Mais cela ne semble pas être ce qui se passe ici.

J'ai aussi lu sur le C-niveau de fuites de mémoire, et depuis CSSPool utilise Racc qui utilise le code en C, je pense que c'est une possibilité. J'ai couru mon code par Valgrind:

valgrind --partial-loads-ok=yes --undef-value-errors=no --leak-check=full --fullpath-after= ruby leak.rb 2> valgrind.txt

Les résultats sont ici. Je ne suis pas sûr si cela se confirme C au niveau de la fuite, comme je l'ai également lu que Ruby fait les choses avec la mémoire que Valgrind ne pas comprendre.

Les Versions utilisées:

  • Ruby 2.0.0-p247 (c'est ce que mon application Rails pistes)
  • Ruby 1.9.3-p392-ref (pour les tests avec ruby-masse)
  • ruby-masse 0.1.3
  • CSSPool 4.0.0 à partir d' ici
  • CentOS 6.4 et Ubuntu 13.10

40voto

mudasobwa Points 5530

On dirait que vous êtes entrer dans Le Monde Perdu ici. Je ne pense pas que le problème est avec le c-les liaisons en racc .

Ruby gestion de la mémoire est à la fois élégante et lourd. Il stocke des objets (nommé en RVALUEs) dans ce qu'on appelle des tas de la taille d'environ de 16 ko. Sur un faible niveau de, RVALUE est une c-structure, contenant un union de norme différente ruby objet de représentations.

Donc, des tas de stocker RVALUE objets, dont la taille n'est pas plus de 40 octets. Pour de tels objets comme String, Array, Hash etc. cela signifie que les petits objets qui peuvent s'adapter dans le tas, mais dès qu'ils atteignent un certain seuil, une mémoire supplémentaire à l'extérieur de l'Ruby tas sera alloué.

Cette mémoire supplémentaire est flexible, est sera libéré dès qu'un objet est devenu GC ed. C'est pourquoi votre cas de test avec big_string indique la mémoire de haut en bas de comportement:

def report
  puts 'Memory ' + `ps ax -o pid,rss | grep -E "^[[:space:]]*#{$$}"`
          .strip.split.map(&:to_i)[1].to_s + 'KB'
end
report
big_var = " " * 10000000
report
big_var = nil 
report
ObjectSpace.garbage_collect
sleep 1
report
# ⇒ Memory 11788KB
# ⇒ Memory 65188KB
# ⇒ Memory 65188KB
# ⇒ Memory 11788KB

Mais le tas (voir GC[:heap_length]) eux-mêmes ne sont pas libérés de retour à l'OS, une fois acquise. Regardez, je vais faire un quotidien de la modifier à votre cas de test:

- big_var = " " * 10000000
+ big_var = 1_000_000.times.map(&:to_s)

Et voilá:

# ⇒ Memory 11788KB
# ⇒ Memory 65188KB
# ⇒ Memory 65188KB
# ⇒ Memory 57448KB

La mémoire n'est pas libérée de retour à des OS plus, parce que chaque élément du tableau I présente les costumes de l' RVALUE taille et est stocké dans le ruby heap.

Si vous examinez la sortie de l' GC.stat après la GC a terme, vous verrez qu' GC[:heap_used] valeur est diminuée comme prévu. Ruby a maintenant beaucoup de vide tas, prêt.

Le résumé: je ne pense pas, l' c code de fuites. Je pense que le problème est dans base64 représentation de l'image énorme dans votre css. Je n'ai aucune idée, ce qui se passe à l'intérieur de l'analyseur, mais il semble que l'immense chaîne des forces de l'ruby heap comte d'augmenter.

Espérons que cela aide.

15voto

Joe Edgar Points 447

Bon, j'ai trouvé la réponse. Je quitte mon autre réponse, parce que l'information a été très difficile de recueillir, il est lié, et il pourrait aider quelqu'un d'autre à la recherche pour un problème connexe.

Votre problème, cependant, semble être dû au fait que Ruby ne fait pas de libérer de la mémoire sur le Système d'Exploitation une fois qu'il a acquis.

L'Allocation De La Mémoire

Tandis que les programmeurs de Ruby ne s'inquiètent souvent de l'allocation de mémoire, parfois, la question suivante apparaît:

Pourquoi mon Ruby processus de séjour si grand, même après que j'ai effacé toutes les références à des objets? Je suis /assurez-vous/ GC a exécuté plusieurs fois et libéré mon gros objets et je ne suis pas une fuite de mémoire.

Un programmeur C pourrait poser la même question:

J'ai free()-ed beaucoup de mémoire, pourquoi mon processus de toujours tellement grand?

L'allocation de la mémoire utilisateur de l'espace à partir du noyau est moins cher en gros morceaux, ainsi l'utilisateur de l'espace évite d'interaction avec le noyau en faisant plus de travail lui-même.

De l'espace utilisateur des bibliothèques/runtimes de mettre en œuvre un programme d'allocation de mémoire (par exemple: malloc(3) de la libc) qui prend de gros morceaux de noyau memory2 et les divise en petits morceaux pour les applications de l'espace utilisateur à utiliser.

Ainsi, plusieurs utilisateurs de l'espace allocations de mémoire peut se produire avant que l'utilisateur de l'espace a besoin de demander le noyau pour plus de mémoire. Donc si vous avez une grande partie de la mémoire du noyau et sont uniquement à l'aide d'une petite partie de cette grande partie de la mémoire reste affecté.

La libération de la mémoire vers le noyau a aussi un coût. De l'espace utilisateur allocateurs de mémoire peut retenir que la mémoire (en privé) dans l'espoir qu'il peut être réutilisé dans le même processus et de ne pas donner vers le noyau pour une utilisation dans d'autres processus. (Ruby Meilleures Pratiques)

Ainsi, vos objets peuvent très bien avoir été nettoyés et remis Ruby est disponible de la mémoire, mais parce que Ruby ne donne jamais le retour de la mémoire inutilisée à l'OS, le flux rss de la valeur pour le processus reste le même, même après la collecte des ordures. C'est en fait par la conception. Selon Mike Perham:

...Et étant donné que l'IRM ne donne jamais le retour de la mémoire inutilisée, notre démon peut facilement prendre 300-400 MO quand il est seulement à l'aide de 100-200.

Il est important de noter que c'est essentiellement par la conception. Ruby et son histoire est la plupart du temps comme un outil de ligne de commande pour un traitement de texte et donc les valeurs de démarrage rapide et une faible empreinte mémoire. Il n'a pas été conçu pour une longue démon/processus du serveur. Java fait une semblable compromis dans sa client et le serveur VMs.

9voto

Joe Edgar Points 447

Cela pourrait être dû à la "Paresseux Balayage" dans Ruby 1.9.3 et au-dessus.

Paresseux de balayage signifie que, lors de la collecte des ordures, Ruby seulement des "opérations de ratissage" loin assez d'objets pour créer de l'espace pour les nouveaux objets qu'il doit créer. Il le fait parce que, tandis que le Rubis garbage collector s'exécute, rien d'autre ne le fait. Ceci est connu comme "Arrêtez le monde," la collecte des ordures.

Essentiellement, paresseux balayage réduit le temps que Ruby doit "arrêter le monde." Vous pouvez en lire plus à propos de paresseux balayage ici.

Quel est votre RUBY_GC_MALLOC_LIMIT variable d'environnement?

Voici un extrait de Sam du Safran du blog concernant les paresseux de balayage et de la RUBY_GC_MALLOC_LIMIT:

La GC de Ruby 2.0 est disponible en 2 saveurs différentes. Nous avons un "plein" de GC qui s'exécute après que nous allouer plus de notre malloc_limit et un paresseux de balayage (partielle GC) qui sera exécuté si l'on court de logements libres dans notre tas.

Le paresseux de balayage prend moins de temps qu'un GC, mais seulement effectue partielle d'un GC. Le but est de réaliser un court GC de plus en plus fréquemment augmentant ainsi le débit global. Le monde s'arrête, mais pour moins de temps.

Le malloc_limit est fixé à 8 mo hors de la boîte, vous pouvez le soulever par la définition de la RUBY_GC_MALLOC_LIMIT plus élevé.

Est votre RUBY_GC_MALLOC_LIMIT très élevée? Le mien est configuré pour 100000000 (100 MO). La valeur par Défaut est autour de 8 MO, mais pour les rails apps de le recommander à être un peu plus élevé. Si le vôtre est trop élevé, il pourrait être la prévention de Ruby à partir de la suppression des ordures objets, car il pense qu'il a beaucoup d'espace pour grandir.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X