Ruby 为什么这个未使用的字符串没有被垃圾回收?

Ruby 为什么这个未使用的字符串没有被垃圾回收?,ruby,string,garbage-collection,Ruby,String,Garbage Collection,为什么未使用的_变量_2和未使用的_变量_3会被垃圾收集,而不是未使用的_变量_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 cla

为什么未使用的_变量_2和未使用的_变量_3会被垃圾收集,而不是未使用的_变量_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 = "Object ids of unused_variable_1, @string, unused_variable_2, unused_variable_3, and another @string"
before_gc_message = "Before GC"
after_gc_message = "After 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
运行程序:

$ 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 # Have to use Ruby 1.8 - memprof doesn't work on 1.9
ruby 1.8.7 (2011-06-30 patchlevel 352) [x86_64-linux]
$ ruby -rubygems leaky_boat.rb 
Object ids of unused_variable_1, @string, unused_variable_2, unused_variable_3, and another @string
70178323299180
70178323299320
70178323299100
70178323299060
70178323299320
Before 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
After 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

这种行为是因为ruby for substr版本的字符串实现有一种特殊情况,即在获取源字符串尾部的substr并且字符串长度足够大,无法将字符串值存储在基本对象结构中时,可以节省内存分配

如果跟踪代码,您将看到范围下标
字符串[0…100]
将通过。因此,新字符串将通过分配新对象结构(因此不同的对象id)进行分配,但将字符串值
ptr
字段设置为指向源对象扩展存储的指针,并设置
ELTS\u SHARED
标志以指示新对象与另一个对象共享存储

在代码中,您获取这个新的子字符串对象并将其分配给实例var
@string
,当您运行垃圾收集时,它仍然是一个活动引用。由于存在对原始字符串的已分配存储的实时引用,因此无法收集它

在ruby主干中,这种在兼容的尾子串上共享存储的优化似乎仍然存在

另外两个变量
unused_variable_2
unused_variable_3
没有这种扩展存储共享问题,因为它们是通过确保不同存储的机制设置的,所以当引用超出范围时,它们会按预期进行垃圾收集

字符串#dup运行(via),它用源字符串内容的副本替换源字符串的内容,并确保存储不共享


字符串#new(source_str)在其中运行,类似地确保在提供的初始值上使用rb#u str_replace进行不同的存储。

@NiklasB。不像我读到的那样
beg+len==RSTRING(str)->len
表示子字符串片段的beg+len与源字符串的len相匹配,也就是说,它们在字符串的末尾对齐。出现这种情况的原因是,代码必须在字符串末尾有一个c-null字符,因此可以使用此共享存储的子字符串片只能是具有相同字符串结尾的子字符串片。哦,对不起,我的错误。我被
字符串[0…100]
0
部分误导了。在这种情况下,它既是一个前缀又是一个后缀,但共享确实是基于后缀的。“String#dup运行rb#u str_replace(通过initialize_copy binding)将源字符串的内容替换为源字符串内容的副本,并确保存储不被共享。”-换句话说,它故意避免子字符串方法使用的优化?此外,一篇名为“String#dup and String.new”(source_str)的博客文章声称使用了有问题的优化,尽管我不确定该文章的作者有多可靠。@Andrewgrim好吧,我依赖于阅读实际代码,而不是博客文章。除非源字符串是共享的,否则rb_str_replace显然会生成独立字符串。如果接收方字符串是共享的,它调用str_make_independent来中断共享,如果它已经是独立的,Andrew:我上周的回答没有令人满意地回答你的问题吗?@dbenhur它没有解释为什么
未使用的变量2
未使用的变量3
会被垃圾收集-他们没有特殊的案例来保存内存分配吗?他们没有特殊的共享分配。String#dup和String.new都保证您得到一个不同的新对象。我将在回答中向代码路径添加引用。