Память руби изнутри
description
Transcript of Память руби изнутри
Память Ruby изнутри
Василий Федосеев
Долгоживущие процессы
Проблема утечек памяти и стейтов
Нельзя просто перезапустить процесс, как это делает passenger
Управление памятью
Объекты в MRI
Всё - это объекты
Их очень много
Живут в куче (heap) в фиксированных слотах
sizeof(RVALUE) = 40 (обычно)
ObjectSpace
_id2refcount_objectseach_objectgarbage_collectdefine_finalizer / undefine_finalizer
Use the Source, Luke!
ObjectSpace
Obj
ectS
pace
Heap
Heap
Heap
...
RVAL
UE
RVAL
UE
RVAL
UE
RVAL
UE
free
free
free
freelist
RVAL
UE
Параметры GCRUBY_GC_MALLOC_LIMIT
По умолчанию 8 Мб
RUBY_HEAP_MIN_SLOTS
10k
1.9.2 стартует с 17k объектов, 2.0.0 с 15k
RUBY_FREE_MIN
4096
Однако
2.0.0dev :001 > ObjectSpace.each_object(Hash){} => 118 2.0.0dev :002 > ObjectSpace.each_object(Fixnum){} => 0 2.0.0dev :003 > ObjectSpace.each_object(Symbol){} => 0
Не всё - объекты
Object ID2.0.0dev :001 > 0.object_id => 1 2.0.0dev :002 > 1.object_id => 3 2.0.0dev :003 > :a.object_id => 468808 2.0.0dev :004 > "a".object_id => 70199055954380 2.0.0dev :005 > true.object_id => 20 2.0.0dev :006 > false.object_id => 0 2.0.0dev :007 > nil.object_id => 8
Object ID
101010101011 1
Fixnum flag
Symbol id 1100
RVALUE ptr 000
RUBY_Qfalse = 0x00, RUBY_Qtrue = 0x14, RUBY_Qnil = 0x08, RUBY_Qundef = 0x34,
RUBY_IMMEDIATE_MASK = 0x07, RUBY_FIXNUM_FLAG = 0x01, RUBY_FLONUM_MASK = 0x03, RUBY_FLONUM_FLAG = 0x02, RUBY_SYMBOL_FLAG = 0x0c, RUBY_SPECIAL_SHIFT = 8
RBasicenum ruby_value_type { RUBY_T_NONE = 0x00,
RUBY_T_OBJECT = 0x01, RUBY_T_CLASS = 0x02, RUBY_T_MODULE = 0x03, RUBY_T_FLOAT = 0x04, RUBY_T_STRING = 0x05, RUBY_T_REGEXP = 0x06, RUBY_T_ARRAY = 0x07, RUBY_T_HASH = 0x08, RUBY_T_STRUCT = 0x09, RUBY_T_BIGNUM = 0x0a, RUBY_T_FILE = 0x0b, RUBY_T_DATA = 0x0c, RUBY_T_MATCH = 0x0d, RUBY_T_COMPLEX = 0x0e, RUBY_T_RATIONAL = 0x0f,
RUBY_T_NIL = 0x11, RUBY_T_TRUE = 0x12, RUBY_T_FALSE = 0x13, RUBY_T_SYMBOL = 0x14, RUBY_T_FIXNUM = 0x15,
RUBY_T_UNDEF = 0x1b, RUBY_T_NODE = 0x1c, RUBY_T_ICLASS = 0x1d, RUBY_T_ZOMBIE = 0x1e,
RUBY_T_MASK = 0x1f};
struct RBasic { VALUE flags; VALUE klass;};
RObject#define ROBJECT_EMBED_LEN_MAX 3struct RObject {
union {! struct {
! long numiv;! VALUE *ivptr;
struct st_table *iv_index_tbl;! } heap;
! VALUE ary[ROBJECT_EMBED_LEN_MAX]; } as;};
struct RBasic { VALUE flags; VALUE klass;};
ivars[numiv]
RClassstruct RClass { struct RBasic basic; rb_classext_t *ptr; struct st_table *m_tbl; struct st_table *iv_index_tbl;};
struct rb_classext_struct { VALUE super; struct st_table *iv_tbl; struct st_table *const_tbl; VALUE origin; VALUE refined_class; rb_alloc_func_t allocator;};
T_DATA
struct rb_data_type_struct { const char *wrap_struct_name; struct {! void (*dmark)(void*);! void (*dfree)(void*);! size_t (*dsize)(const void *);! void *reserved[2]; } function; const rb_data_type_t *parent; void *data;};
struct RTypedData { struct RBasic basic; const rb_data_type_t *type; VALUE typed_flag; /* 1 or not */ void *data;};
RVALUE
typedef struct RVALUE { union {! struct {! VALUE flags;! struct RVALUE *next;! } free;! struct RBasic basic;! struct RObject object;! struct RClass klass;! struct RFloat flonum;! struct RString string;! struct RArray array;! struct RRegexp regexp;! struct RHash hash;! struct RData data;! struct RTypedData typeddata;! struct RStruct rstruct;! struct RBignum bignum;! struct RFile file;! struct RNode node;! struct RMatch match;! struct RRational rational;! struct RComplex complex; } as;} RVALUE;
слоты в куче - это RVALUE
union от всех возможных системных типов
тип определяется по флагам
размер обычно 40 байт
Корневые объектыГлавный тред и RubyVM
Машинный контекст: стек и регистры
Глобальные константы и переменные
в том числе из нативных гемов
Таблица классов
Generic ivars
Finalizers и at_exit
Obj2Obj1
Корневой объект
Obj4
Корневой объект
Obj3 Obj5
Obj6Obj7
Obj2Obj1
Корневой объект
Obj4
Корневой объект
Obj3 Obj5
Obj6Obj7
Obj2Obj1
Корневой объект
Obj4
Корневой объект
Obj3 Obj5
Obj6Obj7
Подсчет ссылок
1
11
112
1
1 2
Obj2Obj1
Корневой объект
Obj4
Корневой объект
Obj3 Obj5
Obj6Obj7
Mark & Sweep: mark
Obj2Obj1
Корневой объект
Obj4
Корневой объект
Obj3 Obj5
Obj6Obj7
Mark & Sweep: mark
Mark & Sweep: sweep
Obj2Obj1
Корневой объект
Obj4
Корневой объект
Obj3
Виды ссылок
Из корневых объектовПеременные классаПеременные экземпляраСодержимое контейнеровЛокальные переменные
Задачкаclass A def a &b; end; def initialize a(&:to_s) endend
def closure_method A.newend
closure_methodGC.startputs ObjectSpace.each_object(A){}
что будет выведено на экран?
WTF?
Код - тоже объекты
T_CLASS, T_ICLASS
T_MODULE
T_DATA
iseq = instruction sequence
method
block
proc = iseq + VM/env
Виды ссылок
Глобальные переменныеПеременные классаПеременные экземпляраСодержимое контейнеровЛокальные переменныеЗамыкания
Как найти?
heap_dump
Дампит почти полное дерево ссылок
Без патчей в руби
Нет оверхеда в простое
Удобная считалка объектов
https://github.com/Vasfed/heap_dump
Задачкаclass A def a &b; end; def initialize a(&:to_s) endend
def closure_method A.newend
closure_methodrequire 'heap_dump'HeapDump.dump
Расследование$ grep '"name":"A"' dump.json ,{"id":70129858139840,"bt":"T_CLASS","class":70129858139820,"name":"A","methods":{"a":70129857754000,"initialize":70129858140300}}
$ grep 70129858139840 dump.json | grep T_OBJECT,{"id":70129858139780,"bt":"T_OBJECT","class":70129858139840}
$ grep 70129858139780 dump.json | grep -v 'id":70129858139780',{"id":70129858139720,"bt":"T_DATA","class":70129857815360,"type_name":"VM/env","size":104,"env":[70129858139780,70129858139720],"local_size":2,...
$ grep 70129858139720 dump.json | grep -v 'id":70129858139720',{"id":70129858139700,"bt":"T_DATA","type_name":"proc","envval":70129858139720,"block":{"iseq":70129858139740,"self":"to_s"}
$ grep 70129858139700 dump.json | grep -v 'id":70129858139700',{"id":70129858139760,"bt":"T_ARRAY","class":null,"val":[null,null,null,null,...,null,null,"to_s",70129858139700,null,null,...]}
Расследование
object Aclass A
VM/env
proc
Глобальные переменные
Какой-то массивс 134 символами и
proc
Расследование//string.cstatic VALUE sym_to_proc(VALUE sym){ static VALUE sym_proc_cache = Qfalse; enum {SYM_PROC_CACHE_SIZE = 67}; ...
if (!sym_proc_cache) {! sym_proc_cache = rb_ary_tmp_new(SYM_PROC_CACHE_SIZE * 2);! rb_gc_register_mark_object(sym_proc_cache);! ... index = (id % SYM_PROC_CACHE_SIZE) << 1;
aryp = RARRAY_PTR(sym_proc_cache); if (aryp[index] == sym) return aryp[index + 1]; else {! proc = rb_proc_new(sym_call, (VALUE)id);! aryp[index] = sym; aryp[index + 1] = proc;! return proc; }}
Правильный ответ
MRI кеширует результаты Symbol#to_proc
В замыкание proc может попасть сам объект
Объект и все, на что он ссылается - останется в памяти до вытеснения из кеша
Это баг в ruby
На экран будет выведена единица
https://gist.github.com/4273437
Поиск утечек
Научиться воспроизводить
Понять что именно течет
Снять дамп
Понять почему течет
Дальше по желанию
Пример с рельсами
class LeakController < ApplicationController
def leak ($leak ||= []).push proc{ "some never-callback" } render text: "ololo" end
end
Понять что именно течетclass LeakController < ApplicationController
def leak ($leak ||= []).push proc{ "some never-callback" } render text: "ololo" end
def count GC.start render :json => HeapDump.count_objects([:ApplicationController] + ApplicationController.subclasses.map{|c| c.name.to_sym}) end
def dump fork { HeapDump.dump; exit } render :text => "May be Dumped" endend
Счетчик объектов$ curl http://localhost:3000/count{ "total_slots": 183152, "free_slots": 53535, "basic_types": { "T_OBJECT": 5064, "T_CLASS": 2723, "T_MODULE": 423, "T_FLOAT": 82, "T_STRING": 74909, "T_REGEXP": 1235, "T_ARRAY": 21019, "T_HASH": 585, "T_STRUCT": 199, "T_BIGNUM": 2, "T_FILE": 8, "T_DATA": 12741, "T_MATCH": 4, "T_COMPLEX": 1, "T_RATIONAL": 69, "T_NODE": 10038, "T_ICLASS": 515 }, "user_types": {
"LeakController": 7 }}
Пример с рельсами$ grep 'name":"LeakController"' dump.json | grep T_CLASS
,{"id":70281018730340,"bt":"T_CLASS","class":70281018730260,"name":"LeakController",
"methods":{"_layout":70281018415040,"leak":70281029395580,"count":70281029394940,"dump":70281029394280, ...},
"ivs":{"__classpath__":"LeakController","@controller_name":"leak","@visible_actions":70281018559740,"@controller_path":"leak","@_layout":null,"@action_methods":70281004447860,"@parent_name":null,"@parent_prefixes":70281009406480,"@_config":70281007802340,"@view_context_class":70281004125560},
"super":70281029346060}
Пример с рельсами$ grep 70211925295180 dump.json ,{"id":70211925285360,"bt":"T_DATA","class":70211923570840,"type_name":"proc","size":72,"is_lambda":0,"blockprocval":null,"envval":70211925285380,"block":{"iseq":{"id":70211951312640,"name":"block in leak","filename":"/Users/vasfed/work/railsclub/examples/leaky_app/app/controllers/leak_controller.rb","line":5,"type":"block","refs_array_id":70211931351000,"coverage":null,"klass":null,"cref_stack":70211931351600,"defined_method_id":0},"self":70211925295180,"lfp":70211950222292,"dfp":70211950222292}}
Пример с рельсами
proc
global_tbl
Массив
Глобальная $leak
proc proc
...LeakController LeakController LeakController
Profit!
Популярные утечки
Глобальные переменные и их аналоги@@aa = selfEventMachine.next_tick {...}
Замыкания
Symbol#to_proc aka &:symbol
Стеки зависших тредов/файберов
Что это было?
Общие сведения об устройстве ObjectSpace
Как работает GC и какие объекты выживают
Как искать утечки
???
https://github.com/Vasfed/heap_dump
https://gist.github.com/4273437
@vasfed