ruby - pasa - Los objetos ActiveRecord en hashes no se recolectan como basura, ¿un error o una especie de función de almacenamiento en caché?
es bueno borrar la memoria cache (2)
Tengo un modelo simple de ActiveRecord llamado Student
con 100 registros en la tabla. Hago lo siguiente en una sesión de consola de rieles:
ObjectSpace.each_object(ActiveRecord::Base).count
# => 0
x = Student.all
ObjectSpace.each_object(ActiveRecord::Base).count
# => 100
x = nil
GC.start
ObjectSpace.each_object(ActiveRecord::Base).count
# => 0 # Good!
Ahora hago lo siguiente:
ObjectSpace.each_object(ActiveRecord::Base).count
# => 0
x = Student.all.group_by(&:last_name)
ObjectSpace.each_object(ActiveRecord::Base).count
# => 100
x = nil
GC.start
ObjectSpace.each_object(ActiveRecord::Base).count
# => 100 # Bad!
¿Alguien puede explicar por qué sucede esto y si existe una forma inteligente de resolverlo sin conocer la estructura de hash subyacente? Sé que puedo hacerlo:
x.keys.each{|k| x[k]=nil}
x = nil
GC.start
y eliminará todos los objetos de Student de la memoria correctamente, pero me pregunto si hay una solución general (mi problema de la vida real está muy extendido y tiene estructuras de datos más complejas que el hash que se muestra arriba).
Estoy usando Ruby 1.9.3-p0 y Rails 3.1.0.
ACTUALIZACIÓN (RESUELTOS)
Según la explicación de Oscar Del Ben a continuación, algunos objetos ActiveRecord :: Relation se crean en el fragmento de código problemático (en realidad se crean en ambos fragmentos de código, pero por alguna razón se "portan mal" solo en el segundo. ¿Alguien puede arrojar luz sobre el ¿por qué?). Estos mantienen las referencias a los objetos ActiveRecord a través de una variable de instancia llamada @records. Esta variable de instancia se puede establecer en nula mediante el método de "restablecimiento" en ActiveRecord :: Relation. Debe asegurarse de realizar esto en todos los objetos de relación:
ObjectSpace.each_object(ActiveRecord::Base).count
# => 100
ObjectSpace.each_object(ActiveRecord::Relation).each(&:reset)
GC.start
ObjectSpace.each_object(ActiveRecord::Base).count
# => 0
Nota: También puede usar Mass.detach (usando la gema de ruby-mass referencia Oscar Del Ben), aunque será mucho más lento que el código anterior. Tenga en cuenta que el código anterior no elimina algunos objetos ActiveRecord :: Relation de la memoria. Sin embargo, estos parecen ser bastante insignificantes. Puedes intentar hacer:
Mass.index(ActiveRecord::Relation)["ActiveRecord::Relation"].each{|x| Mass.detach Mass[x]}
GC.start
Y esto eliminaría algunos de los objetos ActiveRecord :: Relation, pero no todos (no estoy seguro de por qué, y los que quedan no tienen referencias Mass. Raras).
Yo no se la respuesta
Pero intenté inspeccionar el montón como se muestra en http://blog.headius.com/2010/07/browsing-memory-jruby-way.html
He adjuntado una captura de pantalla en https://skitch.com/deepak_kannan/en3dg/java-visualvm fue un programa simple
class Foo; end
f1 = Foo.new
f2 = Foo.new
GC.start
Luego usé jvisualvm como se indica arriba. Estaba ejecutando esto en irb.
Parece como si jruby está rastreando el alcance del objeto. El objeto no obtendrá GC''ed si hay alguna referencia no débil a ese objeto
Creo que sé lo que está pasando. El GC de Ruby no libera objetos inmutables (¡como símbolos!). Las claves devueltas por group_by son cadenas inmutables, por lo que no se recogerán en la basura.
ACTUALIZACIÓN :
Parece que el problema no es con Rails en sí. Intenté usar group_by solo, y algunas veces los objetos no se recolectaban basura:
oscardelben~/% irb
irb(main):001:0> class Foo
irb(main):002:1> end
=> nil
irb(main):003:0> {"1" => Foo.new, "2" => Foo.new}
=> {"1"=>#<Foo:0x007f9efd8072a0>, "2"=>#<Foo:0x007f9efd807250>}
irb(main):004:0> ObjectSpace.each_object(Foo).count
=> 2
irb(main):005:0> GC.start
=> nil
irb(main):006:0> ObjectSpace.each_object(Foo).count
=> 0
irb(main):007:0> {"1" => Foo.new, "2" => Foo.new}.group_by
=> #<Enumerator: {"1"=>#<Foo:0x007f9efb83d0c8>, "2"=>#<Foo:0x007f9efb83d078>}:group_by>
irb(main):008:0> GC.start
=> nil
irb(main):009:0> ObjectSpace.each_object(Foo).count
=> 2 # Not garbage collected
irb(main):010:0> GC.start
=> nil
irb(main):011:0> ObjectSpace.each_object(Foo).count
=> 0 # Garbage collected
He explorado los componentes internos de GC (que son sorprendentemente fáciles de entender), y esto parece ser un problema de alcance. Ruby recorre todos los objetos en el alcance actual y marca los que cree que aún se están utilizando, luego pasa por todos los objetos del montón y libera los que no han sido marcados.
En este caso, creo que el hash todavía se está marcando a pesar de que está fuera del alcance. Hay muchas razones por las que esto puede suceder. Seguiré investigando.
ACTUALIZACIÓN 2:
He encontrado lo que guarda referencias de objetos. Para ello he usado la gema ruby-mass . Resulta que la relación de registro activo realiza un seguimiento de los objetos devueltos.
User.limit(1).group_by(&:name)
GC.start
ObjectSpace.each_object(ActiveRecord::Base).each do |obj|
p Mass.references obj # {"ActiveRecord::Relation#70247565268860"=>["@records"]}
end
Desafortunadamente, el reset
llamadas en la relación no pareció ayudar, pero espero que esta sea suficiente información por ahora.