Память руби изнутри

47
Память Ruby изнутри Василий Федосеев

description

 

Transcript of Память руби изнутри

Page 1: Память руби изнутри

Память Ruby изнутри

Василий Федосеев

Page 2: Память руби изнутри

Долгоживущие процессы

Проблема утечек памяти и стейтов

Нельзя просто перезапустить процесс, как это делает passenger

Page 3: Память руби изнутри

Управление памятью

Page 4: Память руби изнутри

Объекты в MRI

Всё - это объекты

Их очень много

Живут в куче (heap) в фиксированных слотах

sizeof(RVALUE) = 40 (обычно)

Page 5: Память руби изнутри

ObjectSpace

_id2refcount_objectseach_objectgarbage_collectdefine_finalizer / undefine_finalizer

Page 6: Память руби изнутри

Use the Source, Luke!

Page 7: Память руби изнутри

ObjectSpace

Obj

ectS

pace

Heap

Heap

Heap

...

RVAL

UE

RVAL

UE

RVAL

UE

RVAL

UE

free

free

free

freelist

RVAL

UE

Page 8: Память руби изнутри

Параметры GCRUBY_GC_MALLOC_LIMIT

По умолчанию 8 Мб

RUBY_HEAP_MIN_SLOTS

10k

1.9.2 стартует с 17k объектов, 2.0.0 с 15k

RUBY_FREE_MIN

4096

Page 9: Память руби изнутри

Однако

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

Page 10: Память руби изнутри

Не всё - объекты

Page 11: Память руби изнутри

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

Page 12: Память руби изнутри

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

Page 13: Память руби изнутри

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;};

Page 14: Память руби изнутри

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]

Page 15: Память руби изнутри

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;};

Page 16: Память руби изнутри

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;};

Page 17: Память руби изнутри

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 байт

Page 18: Память руби изнутри

Корневые объектыГлавный тред и RubyVM

Машинный контекст: стек и регистры

Глобальные константы и переменные

в том числе из нативных гемов

Таблица классов

Generic ivars

Finalizers и at_exit

Page 19: Память руби изнутри

Obj2Obj1

Корневой объект

Obj4

Корневой объект

Obj3 Obj5

Obj6Obj7

Page 20: Память руби изнутри

Obj2Obj1

Корневой объект

Obj4

Корневой объект

Obj3 Obj5

Obj6Obj7

Page 21: Память руби изнутри

Obj2Obj1

Корневой объект

Obj4

Корневой объект

Obj3 Obj5

Obj6Obj7

Подсчет ссылок

1

11

112

1

1 2

Page 22: Память руби изнутри

Obj2Obj1

Корневой объект

Obj4

Корневой объект

Obj3 Obj5

Obj6Obj7

Mark & Sweep: mark

Page 23: Память руби изнутри

Obj2Obj1

Корневой объект

Obj4

Корневой объект

Obj3 Obj5

Obj6Obj7

Mark & Sweep: mark

Page 24: Память руби изнутри

Mark & Sweep: sweep

Obj2Obj1

Корневой объект

Obj4

Корневой объект

Obj3

Page 25: Память руби изнутри

Виды ссылок

Из корневых объектовПеременные классаПеременные экземпляраСодержимое контейнеровЛокальные переменные

Page 26: Память руби изнутри

Задачкаclass A def a &b; end; def initialize a(&:to_s) endend

def closure_method A.newend

closure_methodGC.startputs ObjectSpace.each_object(A){}

что будет выведено на экран?

Page 27: Память руби изнутри

WTF?

Page 28: Память руби изнутри

Код - тоже объекты

T_CLASS, T_ICLASS

T_MODULE

T_DATA

iseq = instruction sequence

method

block

proc = iseq + VM/env

Page 29: Память руби изнутри

Виды ссылок

Глобальные переменныеПеременные классаПеременные экземпляраСодержимое контейнеровЛокальные переменныеЗамыкания

Page 30: Память руби изнутри

Как найти?

Page 31: Память руби изнутри

heap_dump

Дампит почти полное дерево ссылок

Без патчей в руби

Нет оверхеда в простое

Удобная считалка объектов

https://github.com/Vasfed/heap_dump

Page 32: Память руби изнутри

Задачкаclass A def a &b; end; def initialize a(&:to_s) endend

def closure_method A.newend

closure_methodrequire 'heap_dump'HeapDump.dump

Page 33: Память руби изнутри

Расследование$ 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,...]}

Page 34: Память руби изнутри

Расследование

object Aclass A

VM/env

proc

Глобальные переменные

Какой-то массивс 134 символами и

proc

Page 35: Память руби изнутри

Расследование//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; }}

Page 36: Память руби изнутри

Правильный ответ

MRI кеширует результаты Symbol#to_proc

В замыкание proc может попасть сам объект

Объект и все, на что он ссылается - останется в памяти до вытеснения из кеша

Это баг в ruby

На экран будет выведена единица

https://gist.github.com/4273437

Page 37: Память руби изнутри

Поиск утечек

Научиться воспроизводить

Понять что именно течет

Снять дамп

Понять почему течет

Дальше по желанию

Page 38: Память руби изнутри

Пример с рельсами

class LeakController < ApplicationController

def leak ($leak ||= []).push proc{ "some never-callback" } render text: "ololo" end

end

Page 39: Память руби изнутри

Понять что именно течет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

Page 40: Память руби изнутри

Счетчик объектов$ 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 }}

Page 41: Память руби изнутри

Пример с рельсами$ 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}

Page 42: Память руби изнутри

Пример с рельсами$ 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}}

Page 43: Память руби изнутри

Пример с рельсами

proc

global_tbl

Массив

Глобальная $leak

proc proc

...LeakController LeakController LeakController

Page 44: Память руби изнутри

Profit!

Page 45: Память руби изнутри

Популярные утечки

Глобальные переменные и их аналоги@@aa = selfEventMachine.next_tick {...}

Замыкания

Symbol#to_proc aka &:symbol

Стеки зависших тредов/файберов

Page 46: Память руби изнутри

Что это было?

Общие сведения об устройстве ObjectSpace

Как работает GC и какие объекты выживают

Как искать утечки

Page 47: Память руби изнутри

???

https://github.com/Vasfed/heap_dump

https://gist.github.com/4273437

@vasfed