4 Stimmen

Warum wird dieser nicht verwendete String nicht vom Garbage Collector eingesammelt?

Warum werden unused_variable_2 und unused_variable_3 gesammelt, aber nicht unused_variable_1?

# leaky_boat.rb
require "memprof"

class Boat
  def initialize(string)
    unused_variable1 = string[0...100]
    puts unused_variable1.object_id
    @string = string
    puts @string.object_id
  end
end

class Rocket
  def initialize(string)
    unused_variable_2 = string.dup
    puts unused_variable_2.object_id
    unused_variable_3 = String.new(string)
    puts unused_variable_3.object_id
    @string = string
    puts @string.object_id
  end
end

Memprof.start

text = "a" * 100
object_id_message = "Objekt-IDs von unused_variable_1, @string, unused_variable_2, unused_variable_3 und ein weiteres @string"
before_gc_message = "Vor GC"
after_gc_message = "Nach GC"
puts object_id_message
boat = Boat.new(text)
rocket = Rocket.new(text)
puts before_gc_message
Memprof.stats
ObjectSpace.garbage_collect
puts after_gc_message
Memprof.stats
Memprof.stop

Programm ausführen:

$ uname -a
Linux [redacted] 3.2.0-25-generic #40-Ubuntu SMP Wed May 23 20:30:51 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux
$ ruby --version # Muss Ruby 1.8 verwenden - memprof funktioniert nicht mit 1.9
ruby 1.8.7 (2011-06-30 patchlevel 352) [x86_64-linux]
$ ruby -rubygems leaky_boat.rb 
Objekt-IDs von unused_variable_1, @string, unused_variable_2, unused_variable_3 und ein weiteres @string
70178323299180
70178323299320
70178323299100
70178323299060
70178323299320
Vor GC
      2 leaky_boat.rb:6:String
      2 leaky_boat.rb:26:String
      1 leaky_boat.rb:9:String
      1 leaky_boat.rb:7:String
      1 leaky_boat.rb:32:Rocket
      1 leaky_boat.rb:31:Boat
      1 leaky_boat.rb:29:String
      1 leaky_boat.rb:28:String
      1 leaky_boat.rb:27:String
      1 leaky_boat.rb:20:String
      1 leaky_boat.rb:18:String
      1 leaky_boat.rb:17:String
      1 leaky_boat.rb:16:String
      1 leaky_boat.rb:15:String
Nach GC
      1 leaky_boat.rb:6:String
      1 leaky_boat.rb:32:Rocket
      1 leaky_boat.rb:31:Boat
      1 leaky_boat.rb:29:String
      1 leaky_boat.rb:28:String
      1 leaky_boat.rb:27:String
      1 leaky_boat.rb:26:String

7voto

dbenhur Punkte 19410

Dieses Verhalten tritt auf, weil die Zeichenfolgenimplementierung Ihrer Ruby-Version für substr einen speziellen Fall hat, um Speicherzuweisungen zu sparen, wenn Sie einen Teilstring nehmen, der das Ende der Ausgangszeichenfolge ist und die Zeichenfolgenlänge groß genug ist, um den Zeichenfolgenwert nicht im Basisträgerobjekt zu speichern.

Wenn Sie den Code nachverfolgen, sehen Sie, dass der Bereichsindex string[0...100] diese Klausel in rb_str_substr durchläuft. Die neue Zeichenfolge wird also über str_new3 allokiert, was eine neue Objektstruktur anlegt (daher die unterschiedliche object_id), aber den Zeichenfolgenwert ptr als Zeiger in den erweiterten Speicher des Quellobjekts setzt und das ELTS_SHARED-Flag setzt, um anzuzeigen, dass das neue Objekt Speicher mit einem anderen Objekt teilt.

In Ihrem Code nehmen Sie dieses neue Unterstring-Objekt und weisen es der Instanzvariablen @string zu, die immer noch eine aktive Referenz ist, wenn der Garbage-Collection-Prozess läuft. Da es eine aktive Referenz auf den allokierten Speicher der ursprünglichen Zeichenfolge gibt, kann dieser nicht eingesammelt werden.

In der Ruby-Hauptversion scheint diese Optimierung zur gemeinsamen Speicherfreigabe bei kompatiblen Enduntermengen immer noch zu existieren.

Die beiden anderen Variablen unused_variable_2 und unused_variable_3 haben dieses Problem der geteilten erweiterten Speicherung nicht, weil sie über Mechanismen gesetzt werden, die unterschiedliche Speicherbereiche versichern, sodass sie wie erwartet eingesammelt werden, wenn ihre Referenzen den Gültigkeitsbereich verlassen.

String#dup führt rb_str_replace aus (über initialize_copy binding), was den Inhalt der Ausgangszeichenfolge durch eine Kopie des Inhalts der Ausgangszeichenfolge ersetzt und sicherstellt, dass der Speicher nicht geteilt wird.

String#new(source_str) verläuft über rb_str_init, was ähnlich wie bei rb_str_replace auf den bereitgestellten Anfangswert einen unterschiedlichen Speicher versichert.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X