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

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

Dec 14, 2014

Download

Education

vasfed

 
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
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