Contando objetos vivos com ObjectSpace
O que você já sabe, em duas frases
A aula "Gerenciamento de memória", de Fundamentos de OS, cobriu o modelo do heap com slots, o mark and sweep, o GC geracional e os campos heap_allocated_pages/heap_live_slots/count do GC.stat. Ela também cobriu find_each e literais de string congelados como padrões de memória no Rails. Nada disso se repete aqui. Este curso supõe que você sabe que o Ruby tem um coletor de lixo e, em linhas gerais, como ele decide o que é lixo. As sete aulas vão aonde aquela não foi: ver o GC trabalhar nos seus próprios objetos e conduzi-lo.
Quantos destes existem agora?
class Widget; end
ObjectSpace.each_object(Widget).count
# 0
10.times { Widget.new }
ObjectSpace.each_object(Widget).count
# 10
ObjectSpace.each_object(SomeClass) percorre todo objeto vivo daquela classe no heap, agora, neste processo. Ele devolve um enumerador que você pode usar com .count, .to_a ou percorrer. Antes de existir algum Widget, a contagem é 0; depois de criar dez, é 10. É uma ferramenta de outro tipo que o GC.stat. O GC.stat dá números agregados, como total de slots e de páginas, enquanto esta dá os objetos vivos de uma classe, que você pode inspecionar um a um.
Por que isso importa mais do que parece
"Quantos objetos Catalog::Course existem agora?" parece pergunta de brinquedo. É a pergunta por trás de um vazamento de memória de verdade, em que um grafo de objetos cresce porque algo guarda uma referência que ninguém queria manter. O ObjectSpace.each_object responde sem chute, perguntando ao próprio heap em vez de raciocinar sobre o código de fora.
ObjectSpace.each_object(String).count
# um número real, provavelmente na casa dos milhares, mesmo num processo novo
Numa sessão Ruby comum, milhares de objetos String já existem antes de o seu código alocar qualquer coisa; a biblioteca padrão e o boot do interpretador os criam. Lembre disso antes de tirar conclusões de uma contagem crua. Um número grande sozinho não quer dizer vazamento. Ele dá uma base para comparar depois que o seu código rodar.
Percorrer, além de contar
O each_object também entrega os objetos vivos:
ObjectSpace.each_object(Widget) { |w| puts w.object_id }
Isso roda o bloco uma vez por Widget vivo neste processo, com o object_id de cada um. Você raramente deixaria isso rodando em produção, porque percorrer o heap inteiro atrás de uma classe tem custo. Como diagnóstico avulso numa sessão de console investigando "por que a memória não para de subir?", é a resposta mais direta que o Ruby dá.
Pontos-chave
ObjectSpace.each_object(SomeClass).countconta toda instância viva de uma classe no heap agora, indo de 0 a 10 depois de criar dezWidgets.- É uma ferramenta diferente do
GC.stat: os objetos vivos de uma classe, e não números agregados do heap. - Uma contagem crua precisa de uma base; milhares de objetos
Stringexistem num processo Ruby novo antes de o seu código rodar. - O
each_objecttambém percorre os próprios objetos, útil para diagnósticos avulsos, e não para uso rotineiro em produção.
Continue com a aula 2
O que ObjectSpace.count_objects revela
Ler o heap por tipo interno com ObjectSpace.count_objects e comparar fotos tiradas antes e depois do código suspeito.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.