ObjectSpace 모듈
ObjectSpace 모듈
objspace 라이브러리는 ObjectSpace 모듈을 확장해서 객체/메모리 관리에 대한 내부 통계 정보를 얻는 여러 메서드를 추가해요.
출처: Ruby 3.3 API
본문
이 확장 모듈을 쓰려면 require 'objspace'가 필요해요.
일반적으로 MRI 구현에 대해 모르면 이 라이브러리를 쓰지 말아야 해요. 주로 (메모리) 프로파일러 개발자와 MRI 메모리 사용량을 알아야 하는 MRI 개발자를 위한 것이에요.
ObjectSpace 모듈은 가비지 컬렉션 기능과 상호작용하는 여러 루틴을 담고 있으며, 반복자로 모든 살아있는 객체를 순회할 수 있게 해 줘요.
ObjectSpace는 객체 파이널라이저(finalizer)도 지원해요. 특정 객체가 GC에 의해 파괴되려 할 때 호출될 proc이죠. 이 메서드를 올바르게 쓰는 법의 중요한 정보는 ObjectSpace.define_finalizer 문서를 보세요.
a = "A"
b = "B"
ObjectSpace.define_finalizer(a, proc {|id| puts "Finalizer one on #{id}" })
ObjectSpace.define_finalizer(b, proc {|id| puts "Finalizer two on #{id}" })
a = nil
b = nil
다음과 같은 출력이 나와요.
Finalizer two on 537763470
Finalizer one on 537763480
Public Class Methods
allocation_class_path(object) → string
주어진 object의 클래스를 반환해요.
class A
def foo
ObjectSpace::trace_object_allocations do
obj = Object.new
p "#{ObjectSpace::allocation_class_path(obj)}"
end
end
end
A.new.foo #=> "Class"
자세한 정보와 예시는 ::trace_object_allocations 참고.
allocation_generation(object) → integer or nil
주어진 object의 가비지 컬렉터 세대(generation)를 반환해요.
class B
include ObjectSpace
def foo
trace_object_allocations do
obj = Object.new
p "Generation is #{allocation_generation(obj)}"
end
end
end
B.new.foo #=> "Generation is 3"
자세한 정보와 예시는 ::trace_object_allocations 참고.
allocation_method_id(object) → string
주어진 object의 메서드 식별자를 반환해요.
class A
include ObjectSpace
def foo
trace_object_allocations do
obj = Object.new
p "#{allocation_class_path(obj)}##{allocation_method_id(obj)}"
end
end
end
A.new.foo #=> "Class#new"
자세한 정보와 예시는 ::trace_object_allocations 참고.
allocation_sourcefile(object) → string / allocation_sourceline(object) → integer
주어진 object의 소스 파일 출처 / 소스의 원래 줄을 반환해요.
자세한 정보와 예시는 ::trace_object_allocations 참고.
count_imemo_objects([result_hash]) → hash
각 T_IMEMO 타입별 객체 수를 세요.
이 메서드는 Ruby 프로그램의 성능과 메모리 사용량에 관심 있는 MRI 개발자 전용이에요.
다음과 같은 hash를 반환해요.
{:imemo_ifunc=>8,
:imemo_svar=>7,
:imemo_cref=>509,
:imemo_memo=>1,
:imemo_throw_data=>1}
선택 인자 result_hash가 주어지면 그걸 덮어쓰고 반환해요. probe effect를 피하려는 의도예요. 반환 hash의 내용물은 구현 특유이며 미래에 바뀔 수 있어요.
이 버전에서 키는 symbol 객체예요. 이 메서드는 C Ruby에서만 동작할 것으로 기대돼요.
count_nodes([result_hash]) → hash
각 노드 타입별 노드 수를 세요.
이 메서드는 MRI 개발자 전용이에요.
다음과 같은 hash를 반환해요.
{:NODE_METHOD=>2027, :NODE_FBODY=>1927, :NODE_CFUNC=>1798, ...}
선택 인자 result_hash가 주어지면 그걸 덮어쓰고 반환해요. 반환 hash의 내용물은 구현 정의이며 미래에 바뀔 수 있어요. C Ruby에서만 기대돼요.
count_objects([result_hash]) → hash
타입별로 모든 객체를 세요.
다음과 같은 hash를 반환해요.
{
:TOTAL=>10000,
:FREE=>3011,
:T_OBJECT=>6,
:T_CLASS=>404,
# ...
}
반환 hash의 내용물은 구현 특유이며 미래에 바뀔 수 있어요. :T_로 시작하는 키는 살아 있는 객체를 의미해요. 예를 들어 :T_ARRAY는 배열의 수예요. :FREE는 현재 사용되지 않는 객체 슬롯, :TOTAL은 위의 합이에요.
선택 인자 result_hash가 주어지면 그걸 덮어쓰고 반환해요.
h = {}
ObjectSpace.count_objects(h)
puts h
# => { :TOTAL=>10000, :T_CLASS=>158280, :T_MODULE=>20672, :T_STRING=>527249 }
C Ruby에서만 동작할 것으로 기대돼요.
count_objects_size([result_hash]) → hash
각 타입별 객체 크기(바이트)를 세요.
이 정보는 불완전하다는 점을 참고해요. HINT로만 다뤄야 해요. 특히 T_DATA의 총 크기는 틀릴 수 있어요.
다음과 같은 hash를 반환해요.
{:TOTAL=>1461154, :T_CLASS=>158280, :T_MODULE=>20672, :T_STRING=>527249, ...}
선택 인자 result_hash가 주어지면 그걸 덮어쓰고 반환해요. 내용물은 구현 정의이며 미래에 바뀔 수 있어요. C Ruby에서만 기대돼요.
count_symbols([result_hash]) → hash
각 Symbol 타입별 심볼 수를 세요.
이 메서드는 MRI 개발자 전용이에요. 선택 인자 result_hash가 주어지면 그걸 덮어쓰고 반환해요. 내용물은 구현 정의이며 미래에 바뀔 수 있어요. C Ruby에서만 기대돼요.
이 MRI 버전에는 3가지 타입의 Symbol이 있어요(그리고 총합 1개).
* mortal_dynamic_symbol: GC 대상 심볼 (GC가 수집)
* immortal_dynamic_symbol: 동적 심볼에서 승격된 불멸 심볼 (GC가 수집하지 않음)
* immortal_static_symbol: 불멸 심볼 (GC가 수집하지 않음)
* immortal_symbol: 총 불멸 심볼 (immortal_dynamic_symbol+immortal_static_symbol)
count_tdata_objects([result_hash]) → hash
각 T_DATA 타입별 객체 수를 세요.
이 메서드는 MRI 개발자 전용이에요.
다음과 같은 hash를 반환해요.
{RubyVM::InstructionSequence=>504, :parser=>5, :barrier=>6,
:mutex=>6, Proc=>60, RubyVM::Env=>57, Mutex=>1, Encoding=>99,
ThreadGroup=>1, Binding=>1, Thread=>1, RubyVM=>1, :iseq=>1,
Random=>1, ARGF.class=>1, Data=>1, :autoload=>3, Time=>2}
# T_DATA objects existing at startup on r32276.
선택 인자 result_hash가 주어지면 그걸 덮어쓰고 반환해요. 내용물은 구현 특유이며 바뀔 수 있어요.
이 버전에서 키는 Class 객체 또는 Symbol 객체예요. 객체가 일반적(접근 가능한) 객체라면 키는 Class 객체이고, 내부 객체라면 키는 rb_data_type_struct가 등록한 symbol 이름이에요. C Ruby에서만 기대돼요.
define_finalizer(obj, aProc=proc())
aProc을 파이널라이저로 추가해요. obj가 파괴된 후 호출되죠. obj의 객체 ID가 aProc에 인자로 전달돼요. aProc이 lambda나 method라면 단일 인자로 호출될 수 있는지 확인해요.
반환값은 배열 [0, aProc]이에요.
권장되는 두 패턴은: 파이널라이저 proc을 필요한 상태를 안전하게 캡처할 수 있는 비인스턴스 메서드에서 만들거나, 필요한 상태를 인스턴스 변수로 명시적으로 저장하는 커스텀 callable 객체를 쓰는 거예요.
class Foo
def initialize(data_needed_for_finalization)
ObjectSpace.define_finalizer(self, self.class.create_finalizer(data_needed_for_finalization))
end
def self.create_finalizer(data_needed_for_finalization)
proc {
puts "finalizing #{data_needed_for_finalization}"
}
end
end
class Bar
class Remover
def initialize(data_needed_for_finalization)
@data_needed_for_finalization = data_needed_for_finalization
end
def call(id)
puts "finalizing #{@data_needed_for_finalization}"
end
end
def initialize(data_needed_for_finalization)
ObjectSpace.define_finalizer(self, Remover.new(data_needed_for_finalization))
end
end
파이널라이저가 파이널화할 객체를 참조하면 GC에서 실행되지 않을 거예요. 다만 exit에서는 여전히 실행돼요. 파이널화할 객체를 파이널라이저의 수신자로 캡처하면 경고가 난다는 점을 참고해요.
class CapturesSelf
def initialize(name)
ObjectSpace.define_finalizer(self, proc {
# this finalizer will only be run on exit
puts "finalizing #{name}"
})
end
end
또한 파이널화는 예측할 수 없고, exit를 제외하면 절대 실행이 보장되지 않아요.
each_object([module]) {|obj| ... } → integer
이 Ruby 프로세스의 각 살아 있는 비즉시(nonimmediate) 객체에 대해 블록을 한 번씩 호출해요. module이 지정되면 그 클래스/모듈과 일치하거나(또는 그 하위클래스인) 클래스/모듈에 대해서만 호출해요. 찾은 객체 수를 반환해요. 즉시 객체(Fixnum, Symbol, true, false, nil)는 절대 반환되지 않아요. 아래 예시에서 each_object는 우리가 정의한 숫자들과 Math 모듈에 정의된 여러 상수를 반환해요.
블록이 없으면 대신 enumerator를 반환해요.
a = 102.7
b = 95 # Won't be returned
c = 12345678987654321
count = ObjectSpace.each_object(Numeric) {|x| p x }
puts "Total count: #{count}"
다음과 같은 출력이 나와요.
12345678987654321
102.7
2.71828182845905
3.14159265358979
2.22044604925031e-16
1.7976931348623157e+308
2.2250738585072e-308
Total count: 7
garbage_collect (full_mark: true, immediate_mark: true, immediate_sweep: true)
GC.start의 별칭이에요.
internal_class_of(obj) → Class or Module, internal_super_of(cls) → Class or Module
MRI 특유 기능입니다. obj는 InternalObjectWrapper의 인스턴스일 수 있어요.
애플리케이션에서 이 메서드를 쓰면 안 된다는 점을 참고해요.
memsize_of(obj) → Integer
obj가 소비하는 메모리 크기(바이트)를 반환해요.
반환 크기는 불완전하다는 점을 참고해요. HINT로만 다뤄야 해요. 특히 T_DATA의 크기는 맞지 않을 수 있어요.
C Ruby에서만 동작할 것으로 기대돼요.
Ruby 2.2부터 memsize_of(obj)는 sizeof(RVALUE)를 포함한 메모리 크기를 반환해요.
memsize_of_all([klass]) → Integer
모든 살아 있는 객체가 소비하는 메모리 크기(바이트)를 반환해요.
klass(Class 객체여야 해요)가 주어지면 그 클래스의 인스턴스들의 총 메모리 크기를 반환해요.
반환 크기는 불완전하다는 점을 참고해요. HINT로만 다뤄야 해요. 특히 T_DATA의 크기는 맞지 않을 수 있어요.
이 메서드는 총 malloc된 메모리 크기를 반환하지 않는다는 점을 참고해요.
이 메서드는 다음 Ruby 코드로 정의할 수 있어요.
def memsize_of_all klass = false
total = 0
ObjectSpace.each_object{|e|
total += ObjectSpace.memsize_of(e) if klass == false || e.kind_of?(klass)
}
total
end
C Ruby에서만 동작할 것으로 기대돼요.
reachable_objects_from(obj) → array or nil
MRI 특유 기능입니다. 이 메서드는 obj에서 도달 가능한 모든 객체를 반환해요.
obj가 같은 객체 x에 대한 참조를 둘 이상 갖고 있으면, 반환 배열에는 x 객체 하나만 포함돼요.
obj가 true, false, nil, 심볼, Fixnum(과 Flonum) 같은 비마킹(비힙 관리) 객체라면 그냥 nil을 반환해요.
obj가 내부 객체를 참조하면 ObjectSpace::InternalObjectWrapper 클래스의 인스턴스를 반환해요. 이 객체는 내부 객체에 대한 참조를 담고 있고, type 메서드로 내부 객체의 타입을 확인할 수 있어요.
obj가 ObjectSpace::InternalObjectWrapper 클래스의 인스턴스이면, obj가 가리키는 내부 객체에서 도달 가능한 모든 객체를 반환해요.
이 메서드로 메모리 누수를 찾을 수 있어요. C Ruby에서만 동작할 것으로 기대돼요.
예시:
ObjectSpace.reachable_objects_from(['a', 'b', 'c'])
#=> [Array, 'a', 'b', 'c']
ObjectSpace.reachable_objects_from(['a', 'a', 'a'])
#=> [Array, 'a', 'a', 'a'] # all 'a' strings have different object id
ObjectSpace.reachable_objects_from([v = 'a', v, v])
#=> [Array, 'a']
ObjectSpace.reachable_objects_from(1)
#=> nil # 1 is not markable (heap managed) object
reachable_objects_from_root → hash
MRI 특유 기능입니다. 루트에서 도달 가능한 객체들을 반환해요.
trace_object_allocations { block }
ObjectSpace 확장 모듈에서 객체 할당 추적을 시작해요.
예:
require 'objspace'
class C
include ObjectSpace
def foo
trace_object_allocations do
obj = Object.new
p "#{allocation_sourcefile(obj)}:#{allocation_sourceline(obj)}"
end
end
end
C.new.foo #=> "objtrace.rb:8"
이 예시는 읽기 쉽게 ObjectSpace 모듈을 include했지만, ::trace_object_allocations 표기법을 써도 돼요(권장).
이 기능은 엄청난 성능 저하와 엄청난 메모리 소비를 유발한다는 점을 참고해요.
trace_object_allocations_clear
기록된 추적 정보를 지워요.
trace_object_allocations_debug_start ()
디버그 추적을 시작해요.
trace_object_allocations_start
객체 할당 추적을 시작해요.
trace_object_allocations_stop
객체 할당 추적을 중지해요.
::trace_object_allocations_start가 n번 호출됐다면, ::trace_object_allocations_stop을 n번 호출한 후에 추적이 멈춘다는 점을 참고해요.
undefine_finalizer(obj)
obj의 모든 파이널라이저를 제거해요.
Public Instance Methods
dump (obj, output: :string)
ruby 객체의 내용을 JSON으로 덤프해요.
output은 :stdout, :file, :string, 또는 IO 객체 중 하나일 수 있어요.
:file은 tempfile에 덤프하고 대응하는 File 객체를 반환한다는 뜻이에요.:stdout은 덤프를 출력하고nil을 반환한다는 뜻이에요.:string은 덤프와 함께 문자열을 반환한다는 뜻이에요.- IO 객체 인스턴스가 주어지면 거기로 출력되고 그 객체가 반환돼요.
이 메서드는 C Ruby에서만 동작할 것으로 기대돼요. 실험적 메서드이며 변경될 수 있어요. 특히 함수 시그니처와 출력 형식은 미래 버전에서 호환되지 않을 수 있어요.
dump_all (output: :file, full: false, since: nil, shapes: true)
ruby 힙의 내용을 JSON으로 덤프해요.
output 인자는 dump와 같아요. full은 boolean이어야 해요. true면 빈 것(T_NONE)을 포함한 모든 힙 슬롯을 덤프해요.
since는 음이 아닌 정수 또는 nil이어야 해요. 양의 정수이면 그 세대와 더 새로운 세대의 객체만 덤프해요. 현재 세대는 GC::count로 접근할 수 있어요. 객체 할당 추적을 켜지 않고 할당된 객체는 무시돼요(::trace_object_allocations 참고). since가 생략되거나 nil이면 모든 객체를 덤프해요.
shapes는 boolean 또는 음이 아닌 정수여야 해요. 양의 정수이면 제공된 shape id보다 새로운 shape만 덤프해요. 현재 shape_id는 RubyVM.stat(:next_shape_id)로 접근할 수 있어요. shapes가 false면 어떤 shape도 덤프하지 않아요.
특정 시점 이후에 할당된 객체만 덤프하려면 since와 shapes를 결합할 수 있어요.
ObjectSpace.trace_object_allocations
GC.start
gc_generation = GC.count
shape_generation = RubyVM.stat(:next_shape_id)
call_method_to_instrument
ObjectSpace.dump_all(since: gc_generation, shapes: shape_generation)
C Ruby에서만 동작할 것으로 기대돼요. 실험적 메서드이며 변경될 수 있어요.
dump_shapes (output: :file, since: 0)
ruby shape 트리의 내용을 JSON으로 덤프해요.
output 인자는 dump와 같아요. since가 양의 정수이면 주어진 shape id보다 새로운 shape만 덤프해요. 현재 shape_id는 RubyVM.stat(:next_shape_id)로 접근할 수 있어요.
C Ruby에서만 동작할 것으로 기대돼요. 실험적 메서드이며 변경될 수 있어요.
Private Instance Methods
garbage_collect (full_mark: true, immediate_mark: true, immediate_sweep: true)
GC.start의 별칭이에요.