Memory management in .NET 01 Table of contents • • • • • • • • • • Memory management concepts in .NET Value and reference types Stack Heap Garbage collector Heap: SOH & LOH Finalization & IDisposable Profiling tools FAQ Useful links Memory management concepts in .NET Stack and Heap 01 Process memory (simplified) Heap Free Stack Это упрощенная визуализация использования памяти процесса. Для его выполнения, ОС выделяет т.н. «адресное пространство» - некую абстракцию памяти, в которой есть 2 области – Stack и Heap (Стек и Куча). Эти области используются для хранения переменных и объектов программы. Heap обычно располагается в начале адресного пространства, stack – в конце. Эти области памяти заполняются «навстречу» друг другу – heap в порядке увеличения адресов, stack – в порядке уменьшения. В обязанности ОС входит контроль над тем, чтобы эти области не наложились друг на друга. По умолчанию, stack имеет ограничение по размеру – в зависимости от ОС. 02 Process memory (simplified) Heap Free Stack Стек (stack) — это область памяти, в которой программа хранит информацию о вызываемых функциях, их аргументах и каждой локальной переменной в функциях. Размер области может меняться по мере работы программы. При вызове функций стек увеличивается, а при завершении — уменьшается 03 Process memory (simplified) Heap Free Stack Куча (heap) — это область памяти, в которой программа может делать всё, что заблагорассудится. Размер области может меняться. В некоторых языках/платформах, например, в С++, управление кучей осуществляется вручную программистом, а в некоторых, например, Java и .NET – автоматически при помощи Garbage collector (сборщик мусора) 04 More information На самом деле структура и алгоритмы работы с памятью гораздо шире и сложнее, чем это показано ранее, но для общего понимания этого вполне достаточно. Более полная информация о работе с памятью представлена на этих ресурсах: • Азы устройства памяти • "Что каждый программист должен знать о памяти" • Организация виртуальной памяти. Таблицы страниц и прочее • Преобразование логического адреса в линейный • Устройство MMU Value and reference types Description… 01 Value and reference types В отличие от некоторых языков программирования, C # имеет две разновидности типов данных: для значения и для ссылки. Если производительность приложения имеет существенное значение или есть заинтересованность в том, как C# управляет данными и памятью, важно знать различия между этими типами. Если в объявлении переменной используется один из основных встроенных типов данных или определенная пользователем структура данных, значит мы имеем дело с типом значения. Исключение составляет тип данных string, который является ссылочным типом. Тип значения хранит свое содержимое в памяти, выделенной в стеке. При выходе переменной x из области действия в связи с завершением выполнения метода, в котором она была объявлена, значение удаляется из стека. Использование стека является эффективным, но ограниченное время существования типов значений делает их менее подходящими для совместного использования данных между различными классами. 02 Value and reference types В отличие от этого ссылочный тип, такой как экземпляр класса или массив, размещается в другой области памяти, называемой кучей. В следующем примере пространство, необходимое для массива из десяти целых чисел, размещается в куче. int[] numbers = new int[10]; Эта память не возвращается к куче при завершении метода, она освобождается только когда система сборки мусора C# определит, что она больше не нужна. Объявление ссылочных типов увеличивает расход ресурсов, но их преимущество заключается в том, что они могут быть доступны из других классов. 03 Boxing/unboxing Упаковкой называется процесс преобразования типа значения в ссылочный тип. Для упаковки переменной необходимо создать ссылочную переменную, указывающую на новую копию в куче. Ссылочная переменная является объектом, следовательно для нее можно использовать все методы, наследуемые каждым объектом, например ToString(). В следующем коде показано, как это происходит int i = 67; // i is a value type object o = i; // i is boxed Console.WriteLine(i.ToString()); // i is boxed 04 Boxing/unboxing Распаковка применяется для классов, предназначенных для работы с объектами: например, использование ArrayList для хранения целых чисел. Для хранения целых чисел в ArrayList используется упаковка. При извлечении целого числа должна быть применена распаковка ArrayList list = new ArrayList(); // list is a reference type int n = 67; // n is a value type list.Add(n); // n is boxed n = (int)list[0]; // list[0] is unboxed 05 More information • https://msdn.microsoft.com/ru-ru/library/4d43ts61(v=vs.90).aspx Stack Description… 01 Stack STACK 79989 79990 79991 79992 79993 79994 class Program { static void Main(string[] args) { int x = 42; Foo(); } 79995 static void Foo() { int a = 50; int c = 100; } 79996 79997 79998 79999 80000 Stack pointer Address } 02 Stack STACK 79989 class Program { static void Main(string[] args) { int x = 42; Foo(); } 79990 79991 79992 79993 79994 79995 static void Foo() { int a = 50; int c = 100; } 79996 79997 79998 Stack pointer 79999 80000 Address } x 42 03 Stack STACK 79989 class Program { static void Main(string[] args) { int x = 42; Foo(); } 79990 79991 79992 79993 79994 79995 static void Foo() { int a = 50; int c = 100; } 79996 Stack pointer 79997 79998 100 79999 c a 80000 x 42 Address 50 } 04 Stack STACK 79989 class Program { static void Main(string[] args) { int x = 42; Foo(); } 79990 79991 79992 79993 79994 79995 static void Foo() { int a = 50; int c = 100; } 79996 79997 79998 79999 Stack pointer 80000 Address } x 42 05 Stack STACK 79989 79990 79991 79992 79993 79994 class Program { static void Main(string[] args) { int x = 42; Foo(); } 79995 static void Foo() { int a = 50; int c = 100; } 79996 79997 79998 79999 Stack pointer 80000 Address } Heap Description… 01 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x????? 00012 Address class Program { static void Main() { Person p; } } class Person { public int Age { get; set; } public string Name { get; set; } } 02 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x????? 00012 Address class Program { static void Main() { Person p; p.Age = 11; } } Compiler error CS0165: Use of unassigned local variable 03 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x????? 00012 Address class Program { static void Main() { Person p; p = null; p.Age = 11; } } Runtime exception: NullReferenceException Object reference not set to an instance of an object 04 Heap (weak reference) STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 00012 Address Address Person Name = null Age = 0 class Program { static void Main() { new Person(); } } 05 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x????? 00012 Address Person Name = null Age = 0 class Program { static void Main() { Person p; new Person(); } } 06 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x00001 00012 Address Person Name = null Age = 0 class Program { static void Main() { Person p = new Person(); } } 07 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x00001 00012 Address Person Name = “Boris” Age = 0 class Program { static void Main() { Person p = new Person(); p.Name = "Boris"; } } 08 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x00001 00012 Address Person Name = 0x00004 Age = 0 String char[] = “Boris” class Program { static void Main() { Person p = new Person(); p.Name = "Boris"; } } 09 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x00001 00012 Address Person Name = 0x00004 Age = 40 String char[] = “Boris” class Program { static void Main() { Person p = new Person(); p.Name = "Boris"; p.Age = 40; } } 10 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x00000 00012 Address Person Name = 0x00004 Age = 40 String char[] = “Boris” class Program { static void Main() { Person p = new Person(); p.Name = "Boris"; p.Age = 40; p = null; } } 11 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x00001 00012 Address Person Name = 0x00004 Age = 27 String char[] = “Boris” class Program { static void Main() { Person p = new Person("Boris", 27); } } 12 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x00006 00012 Address Person Name = 0x00004 Age = 27 String char[] = “Boris” Person Name = 0x00009 Age = 31 String char[] = “Peter” class Program { static void Main() { Person p = new Person("Boris", 27); p = new Person("Peter", 31); } } 13 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address a 50 00012 Address class Program { static void Main() { int a = 50; } } 14 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 p 0x00001 00011 80000 a 50 00012 Address Address Person Name = null Age = 0 class Program { static void Main() { int a = 50; Person p = new Person(); } } 15 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 p 0x00001 00011 80000 a 50 00012 Address Address Person Name = null Age = 50 class Program { static void Main() { int a = 50; Person p = new Person(); p.Age = a; } } 16 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 p 0x00001 00011 80000 a 73 00012 Address Address Person Name = null Age = 50 class Program { static void Main() { int a = 50; Person p = new Person(); p.Age = a; a = 73; } } 17 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 p2 0x00006 00011 80000 p1 0x00001 00012 Address Address Person Name = 0x00004 Age = 30 Friend = null String char[] = “Boris” Person Name = 0x00009 Age = 31 Friend = null String char[] = “Peter” class Program { static void Main() { Person p1 = new Person("Boris", 30); Person p2 = new Person("Peter", 31); } } 18 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 p2 0x00006 00011 80000 p1 0x00001 00012 Address Address Person Name = “Boris” Age = 30 Friend = 0x00006 Person Name = “Peter” Age = 31 Friend = null class Program { static void Main() { Person p1 = new Person("Boris", 30); Person p2 = new Person("Peter", 31); p1.Friend = p2; } } 19 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x00001 00012 Address Person Name = “Boris” Age = 30 class Program { static void Main() { Person p = new Person("Boris", 30); AddYears(p); } static void AddYears(Person pp) { person.Age = person.Age + 1; } } 20 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 pp 0x00001 00011 80000 p 0x00001 00012 Address Address Person Name = “Boris” Age = 30 class Program { static void Main() { Person p = new Person("Boris", 30); AddYears(p); } static void AddYears(Person pp) { person.Age = person.Age + 1; } } 21 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 pp 0x00001 00011 80000 p 0x00001 00012 Address Address Person Name = “Boris” Age = 31 class Program { static void Main() { Person p = new Person("Boris", 30); AddYears(p); } static void AddYears(Person pp) { person.Age = person.Age + 1; } } 22 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x00001 00012 Address Person Name = “Boris” Age = 31 class Program { static void Main() { Person p = new Person("Boris", 30); AddYears(p); } static void AddYears(Person pp) { person.Age = person.Age + 1; } } 23 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x????? 00012 Address class Program { static void Main() { Person p = CreatePerson(); } static Person CreatePerson() { Person pp = new Person("Boris", 30); return pp; } } 24 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 pp 0x????? 00011 80000 p 0x????? 00012 Address Address class Program { static void Main() { Person p = CreatePerson(); } static Person CreatePerson() { Person pp = new Person("Boris", 30); return pp; } } 25 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 pp 0x00001 00011 80000 p 0x????? 00012 Address Address Person Name = “Boris” Age = 30 class Program { static void Main() { Person p = CreatePerson(); } static Person CreatePerson() { Person pp = new Person("Boris", 30); return pp; } } 25 Heap STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x00001 00012 Address Person Name = “Boris” Age = 30 class Program { static void Main() { Person p = CreatePerson(); } static Person CreatePerson() { Person pp = new Person("Boris", 30); return pp; } } 26 More information • https://www.youtube.com/watch?v=clOUdVDDzIM • http://rurust.github.io/rust_book_ru/src/the-stack-and-the-heap.html Chapter 5. Memory management under the hood Chapter 1. Prelude Finalization & IDisposable Description… 01 Object lifecycle Жизненный цикл объекта: • Выделение памяти для объекта • Инициализация объекта (с помощью конструктора) • Использование объекта • Ликвидация состояния объекта • Освобождение памяти, занимаемой объектом 02 Destructor Объекты, использующие ресурсы системы должны ликвидировать свое состояние перед освобождением памяти Garbage collector Description… 01 Garbage collector Сборщик мусора .NET Framework управляет выделением и освобождением памяти для приложения. При каждом создании нового объекта среда CLR выделяет память для объекта из управляемой динамически распределяемой памяти (кучи). Пока в управляемой куче имеется доступное адресное пространство, среда выполнения продолжает выделять пространство для новых объектов. Но память имеет пределы. В конечном счете, чтобы освободить некоторое количество памяти, сборщик мусора должен выполнить процедуру очистки. Механизм оптимизации сборщика мусора определяет наилучшее время для выполнения сбора, основываясь на произведенных выделениях памяти. В ходе выполнения очистки сборщик мусора отыскивает в управляемой куче объекты, которые более не используются приложением, и освобождает выделенную для них память. 02 Garbage collector STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x00001 00012 Address Person Name = null Age = 0 class Program { static void Main() { Person p = new Person(); } } 03 Garbage collector STACK (local variables) HEAP (class instances) 79989 00001 79990 00002 79991 00003 79992 00004 79993 00005 79994 00006 79995 00007 79996 00008 79997 00009 79998 00010 79999 00011 80000 Address p 0x00000 00012 Address Person Name = null Age = 0 Garbage class Program { static void Main() { Person p = new Person(); p = null; } } 04 Finalization list Первая сборка мусора destructor A B C destructor D Список финализации destructor E destructor destructor destructor A C D 04 Finalization queue Первая сборка мусора Очередь финализации destructor A B C destructor D E Worker thread A destructor destructor C D 05 Finalization queue Первая сборка мусора A B Объекты готовы к удалению C A D E Очередь финализации (пусто!) C D Worker thread 06 Finalization queue Вторая сборка мусора A B D C E 07 Garbage collector Сборщик мусора работает на основе следующих предположений: • Чем младше объект, тем короче его время жизни • Чем старше объект, тем длиннее его время жизни • Сбор мусора в части кучи выполняется быстрее, чем во всей куче 08 Garbage collector Сборщик мусора поддерживает 3 поколения: • Поколение 0: объекты, которые были недавно созданы • Поколение 1: объекты, которые пережили одну сборку мусора • Поколение 2: объекты, которые пережили две сборки мусора 09 Garbage collector HEAP Поколение 0 A B C D 256 KB После создания нескольких объектов поколение 0 полностью заполнилось, и значит пришло время вызвать сборщик мусора 10 Garbage collector HEAP Поколение 0 A B C D 256 KB Объекты A и C пережили сборку мусора. С этого момента они будут считаться объектами поколения 1 11 Garbage collector HEAP Поколение 0 E F G 256 KB Поколение 1 H A C 2 MB После создания ещё нескольких объектов поколение 0 вновь переполнилось. Будет вызван сборщик мусора. 12 Garbage collector HEAP Поколение 0 E F G 256 KB Поколение 1 H A C 2 MB Объекты E, F и G пережили первую в свой жизни сборку мусора. Они переходят в поколение 1. Т.к. память поколения 1 ещё не заполнилась, то сборка мусора в поколении 1 не выполняется. 13 Garbage collector HEAP Поколение 0 Поколение 1 A 256 KB C E F G 2 MB Поколение 1 почти заполнено, значит при следующей сборке будет выполняться поиск мусора в поколении 1 14 Garbage collector HEAP Поколение 0 K L M 256 KB Поколение 1 N A C E F G 2 MB Поколение 0 вновь заполнилось. Поколение 1 также почти заполнено. Значит, перед сборкой мусора в поколении 0, нужно поискать мусор в поколении 1. 15 Garbage collector HEAP Поколение 0 K L M 256 KB Поколение 1 N A C E F G 2 MB После сборки мусора в поколении 1, живыми остались объекты A, E и F. Эти объекты могут перейти в поколение 2 16 Garbage collector HEAP Поколение 0 K L M 256 KB Поколение 1 N Поколение 2 A 2 MB E F 10 MB Объекты A, E и F теперь находятся в поколении 2. Поколение 1 теперь абсолютно пустое, и готово принимать выжившие объекты из поколения 0. 17 Garbage collector HEAP Поколение 0 K L M 256 KB Поколение 1 N Поколение 2 A 2 MB E F 10 MB Выполняется сбор мусора в поколении 0. Объекты K и M переходят в поколение 1 18 Garbage collector HEAP Поколение 0 Поколение 1 K 256 KB Далее, цикл повторяется M Поколение 2 A 2 MB E F 10 MB 19 Garbage collection reasons Сборка мусора выполняется: • Заполнение поколения 0 • Вызов метода Collect класса System.GC • Сообщение Windows о нехватке памяти • Выгрузка домена приложения • Завершение процесса 05 Garbage collector • Объекты, требующие финализации не удаляются при первой сборке мусора; Они удаляются только после выполнения деструктора при последующих сборках мусора • Финализация объектов выполняется в отдельном потоке • Время жизни деструктора ограничено • Необрабатываемое исключение в деструкторе приводит к остановке его работы Heap: SOH & LOH Description… 01 SOH & LOH HEAP LOH (>85000 bytes) SOH (< 85000 bytes) 30KB 130KB 90KB 50KB 15KB 110KB 20KB 15KB 30KB 5KB 02 SOH & LOH HEAP LOH (>85000 bytes) 130KB 90KB 110KB 210KB Допустим, выполняется сборка мусора 150KB 03 SOH & LOH HEAP LOH (>85000 bytes) 130KB 90KB 110KB 210KB 150KB После удаления объектов куча не дефрагментируется, между объектами остаются «дыры» 04 SOH & LOH HEAP LOH (>85000 bytes) 130KB FREE (90 KB) 110KB Не помещается 100KB FREE (210 KB) 150KB 05 SOH & LOH HEAP LOH (>85000 bytes) 130KB FREE (90 KB) 110KB 100KB FREE (110 KB) 150KB 06 SOH & LOH HEAP LOH (>85000 bytes) 130KB FREE (90 KB) 110KB 100KB FREE (110 KB) ??? ??? 85KB 150KB 07 SOH & LOH HEAP LOH (>85000 bytes) 130KB 85KB FREE 5KB 110KB 100KB FREE (110 KB) 150KB Profiling tools Description… 01 Title FAQ Description… 01 Title Useful links Description… 01 Title