Керування пам'яттю в Java

⚡ Розумний підсумок

Керування пам'яттю в Java пояснює, як JVM розподіляє пам'ять середовища виконання між стеком, купою, Code, і статичні регіони, як посилання на об'єкти обробляються під час викликів методів, і як збирач сміття повертає недоступні об'єкти, щоб програми залишалися стабільними та без витоків пам'яті.

  • 🧠 Розмітка пам'яті JVM: Java Пам'ять розділена на купу (Heap) для об'єктів, стек (Stack) для фреймів методів та локальних змінних, а також виділені області для байт-коду та статичних даних.
  • 📚 Стек проти купи: Стекові фрейми дотримуються порядку «Останній прийшов — Першим вийшов» і зберігають примітиви та посилання, тоді як купа (Heap) містить кожен об'єкт, створений за допомогою оператора new.
  • ♻️ Збір сміття: Збирач сміття tracдосяжність ks через ланцюжки посилань та автоматично повертає пам'ять від об'єктів, до яких жоден потік не може дістатися.
  • Сучасні колекціонери: Виробничі навантаження все більше покладаються на G1, ZGC та Shenandoah для збору даних з низькою паузою в купах об'ємом кілька гігабайт.
  • 🧪 Практичний приклад: Покрокове керівництво для студентів показує, коли посилання стають придатними для колекції та як обнулення посилань звільняє базовий об'єкт.

Керування пам'яттю в Java

Що таке стекова пам'ять Java?

Стекована пам'ять у Java — це область пам'яті JVM, яка зберігає фрейми методів, локальні змінні та змінні-посилання для кожного потоку. Доступ до стеку завжди здійснюється в порядку «Останній прийшов — Перший вийшов», тому метод, який був викликаний останнім, знаходиться зверху, а його локальні змінні завантажуються та витягуються разом з фреймом.

Кожна нитка в Java Віртуальна машина отримує власний стек, який забезпечує ізоляцію викликів методів та їх потокобезпечність за своєю природою. Примітивні локальні значення, такі як int, boolean та double, знаходяться безпосередньо всередині фрейму, тоді як посилання на об'єкти, що зберігаються в стеку, вказують на об'єкти, розміщені в купі.

Що таке купна пам'ять? Java?

Купа пам'яті — це спільна область JVM, яка містить усі Java об'єкт і масив, створені за допомогою оператора new, разом з будь-якими змінними посилання, що належать до цих об'єктів як поля екземпляра. На відміну від стеку (Stack), купа (Heap) спільна для всіх потоків, тому доступ до об'єктів часто вимагає синхронізації.

Купа (Heap) – це область, якою керує збирач сміття. Сучасні JVM HotSpot поділяють купу на молоде покоління для короткоживучих об'єктів та старе покоління для довгоживучих об'єктів, причому метадані класу зберігаються в окремому рідному регіоні під назвою Метапростір (Metaspace).

Розподіл пам'яті в Java

Розподіл пам'яті в Java — це процес, за допомогою якого JVM виділяє області віртуальної пам'яті для змінних та екземплярів класів і структур під час виконання програми. Пам'ять не виділяється об'єкту під час оголошення; створюється лише посилання. Фактичне виділення об'єктів відбувається за допомогою оператора new, тому кожен об'єкт знаходиться в купі (Heap).

Команда Java Розподіл пам'яті поділяється на такі розділи:

  1. купа
  2. Стек
  3. Code
  4. Статичний

Такий розподіл пам'яті необхідний для ефективного управління середовищем виконання.

  • Команда Code розділ містить ваш скомпільований байт-код.
  • Команда Стек секційні магазини методи, локальні змінні та змінні посилання.
  • Команда купа розділ містить об'єкти а також може містити посилальні змінні, що зберігаються як поля екземпляра.
  • Команда Статичний розділ тримає статичні дані та статичні методи спільний для всіх екземплярів.

Різниця між локальною та змінною екземпляра

Розуміння того, де кожен тип змінної знаходиться в JVM, допомагає пояснити поведінку стека та купи. змінна екземпляра оголошено всередині класу, але поза будь-яким методом, і він знаходиться в купі як частина свого об'єкта.

class Student{ 
int num; // num is instance variable 
public void showData{}

A локальна змінна оголошено всередині методу, включаючи аргументи методу, і знаходиться на стеку всередині активного фрейму.

public void sum(int a){

int x = a + 3;

// a, x are local variables

}

Різниця між стеком і купою

Стек і купа вирішують різні проблеми всередині JVM. Стек надає кожному потоку швидке, детерміноване виділення для короткоживучих даних, пов'язаних з областю видимості методу, тоді як купа надає спільну область для довгоживучих об'єктів, на які може посилатися будь-який потік. Коротке відео нижче підсумовує порівняння перед наступним покроковим оглядом.

Натисніть тут якщо відео недоступне

Щоб побачити, як взаємодіють Stack та Heap, розглянемо головний метод, який викликає метод m1.

public void m1{
int x = 20;
}

У стеку JVM створюється фрейм для методу m1.

Java Стек і купа

Змінна x у m1 також створюється у фреймі для m1 на стеку, як показано на зображенні нижче.

Java Стек і купа

Метод m1 потім викликає метод m2. У стеку створюється новий кадр для m2 поверх кадру для m1.

Java Стек і купа

Java Стек і купа

Локальні змінні b та c також створюються всередині фрейму для m2 на стеку.

public void m2(int b){
boolean c;
}

Далі, m2 викликає метод m3. Знову ж таки, фрейм для m3 створюється на вершині стека, як показано нижче.

Java Стек і купа

Java Стек і купа

Тепер припустимо, що метод m3 створює об'єкт для класу Account, який має два екземпляри змінних int p і int q.

class Account {
    int p;
    int q;
}

Ось код методу m3.

public void m3(){
    Account ref = new Account();
    // more code
}

Оператор new Account() створює об'єкт Account у купі.

Java Стек і купа

Змінна посилання ref створюється на стеку всередині фрейму для m3.

Java Стек і купа

Оператор присвоєння робить так, щоб змінна-посилання вказувала на об'єкт у купі.

Java Стек і купа

Після завершення виконання методу керування повертається до викликаючого методу, яким у цьому випадку є метод m2.

Java Стек і купа

Кадр для методу m3 видаляється зі стеку.

Java Стек і купа

Оскільки змінна-посилання більше не вказує на об'єкт у купі, цей об'єкт стає придатним для збирання сміття.

Java Стек і купа

Після завершення роботи методу m2 він видаляється зі стеку, і всі його змінні стають недоступними. Те саме відбувається з методом m1, і зрештою керування повертається до основного методу.

Що робити, якщо об'єкт містить інше посилання як свою змінну екземпляра?

public static void main(String args[]) {
    A parent = new A();
    // more code
}
class A {
    B child = new B();
    int e;
}
class B {
    int c;
    int d;
}

У цьому випадку змінна посилання child знаходиться в купі як частина об'єкта A та, у свою чергу, вказує на власний об'єкт B, як показано нижче.

Java Стек і купа

У чому полягає збір сміття Java?

Вивіз сміття в Java — це процес, за допомогою якого JVM автоматично керує пам'яттю. Збирач сміття знаходить об'єкти, які більше недоступні з жодного активного посилання, та повертає їхню пам'ять. Динамічне виділення пам'яті відбувається за допомогою оператора new, і пам'ять залишається виділеною, доки програма більше не містить жодного посилання на об'єкт.

Коли не залишається посилань, об'єкт вважається непотрібним, а пам'ять, яку він займає, можна звільнити. Немає явної потреби знищувати об'єкт, оскільки Java автоматично обробляє звільнення пам'яті через збирач сміття.

Техніка, що лежить в основі цього, відома як Сміттєвий збірПрограми, яким не вдається звільнити пам'ять, зрештою аварійно завершують роботу, коли більше нічого не залишається для розподілу. Кажуть, що такі програми мають витоку пам'яті. Вивіз сміття в Java працює автоматично протягом усього життєвого циклу програми, що усуває тягар ручного звільнення ресурсів та зменшує ризик витоків.

У мові C, навпаки, програміст відповідає за звільнення пам'яті, виділеної динамічно через функцію free(). Саме тут Java Управління пам'яттю пропонує значну перевагу.

Сучасні JVM HotSpot постачаються з кількома збирачами сміття, налаштованими на різні робочі навантаження. Збирач сміття G1 за замовчуванням орієнтований на збалансовану пропускну здатність та час пауз на купах об'ємом кілька гігабайт. ZGC та Shenandoah прагнуть пауз тривалістю менше мілісекунди на дуже великих купах, що робить їх...tracтивний для сервісів, чутливих до затримки. Вибір правильного колектора та налаштування розмірів молодого покоління, старого покоління та метапростору є ключовою навичкою для Java перформанс-робота.

Примітка: Усі об'єкти створюються в розділі купи пам'яті, який є областю, якою керує збирач сміття.

Приклад: вивчити механізм збору сміття в Java

У цьому покроковому посібнику показано, коли посилання стають придатними для збирання сміття всередині простої програми.

Крок 1) Скопіюйте наступний код у редактор.

class Student{
int a;
int b;
  public void setData(int c,int d){
    a=c;
    b=d;
  }
  public void showData(){
    System.out.println("Value of a = "+a);
    System.out.println("Value of b = "+b);
  }
  public static void main(String args[]){
    Student s1 = new Student();
    Student s2 = new Student();
    s1.setData(1,2);
    s2.setData(3,4);
    s1.showData();
    s2.showData();
    //Student s3;
    //s3=s2;
    //s3.showData();
    //s2=null;
    //s3.showData();
    //s3=null;
    //s3.showData();
  }
}

Крок 2) Збережіть, скомпілюйте та запустіть код. Як показано на діаграмі, створено два об'єкти та дві змінні-посилання.

 Механізм для збору сміття

Крок 3) Розкоментуйте рядки 20, 21 та 22. Збережіть, скомпілюйте та запустіть код.

Крок 4) Як показано на діаграмі нижче, дві змінні-посилання тепер вказують на один і той самий об'єкт.

Механізм для збору сміття

Крок 5) Розкоментуйте рядки 23 та 24. Збережіть, скомпілюйте та запустіть код.

Крок 6) Як показано нижче, s2 стає null, але s3 все ще вказує на об'єкт, тому об'єкт ще не має права на збирання сміття.

 Механізм для збору сміття

Крок 7) Розкоментуйте рядки 25 та 26. Збережіть, скомпілюйте та запустіть код.

Крок 8) На цьому етапі жодні посилання не вказують на об'єкт, тому він стає придатним для збирання сміття. Збирач сміття видаляє його з пам'яті, і немає можливості його отримати.

 Навчіться збирати сміття

Як видалити об'єкт у Java?

Java не надає оператора ручного видалення, тому стандартний підхід полягає у видаленні кожного посилання на об'єкт, щоб збирач сміття міг його повернути.

1) Щоб зробити об'єкт придатним для збирання сміття, присвоїте кожній змінній посилання, яка вказує на нього, значення null.

2) Примітивні типи не є об'єктами, тому їм не можна присвоїти значення null. Їхнє сховище автоматично звільняється, коли навколишній кадр Stack виштовхується.

Як видалити об'єкт в Java

Поширені запитання

Стек (Stack) зберігає фрейми методів, локальні змінні та посилання в порядку «Останній прийшов — Першим вийшов» для кожного потоку. Купа (Heap) зберігає всі об'єкти, створені за допомогою new, і є спільною для всіх потоків, тому нею керує збирач сміття (Garbage Collector).

Збирач сміття проходить ланцюжки посилань від коренів GC, таких як активні потоки та статичні поля. Будь-який об'єкт, до якого неможливо дістатися з кореня, вважається недосяжним і стає придатним для відновлення під час наступного циклу збору.

Молоде покоління зберігає короткоживучі об'єкти в просторах Eden та Survivor. Старе покоління зберігає об'єкти, які переживають кілька колекцій. Метапростір — це область власної пам'яті, яка зберігає метадані класу та замінила старіший PermGen у Java 8.

G1 є стандартним і підходить для більшості робочих навантажень на купах об'ємом кілька гігабайт. ZGC та Shenandoah орієнтовані на паузи тривалістю менше мілісекунди на дуже великих купах і є гарним вибором для чутливих до затримки сервісів, що працюють на Java 17 або пізнішої версії.

Встановіть для кожного посилання, що вказує на об'єкт, значення null або дозвольте посиланням випадати з області видимості, коли їхні стекові кадри витягуються. Щойно жодне активне посилання не досягає об'єкта, збирач сміття повертає собі пам'ять купи.

Так. Інструменти налаштування на основі штучного інтелекту аналізують журнали збору вантажу (GC), коефіцієнти розподілу та гістограми призупинення, щоб рекомендувати розміри купи (Heap), прапорці колекторів та коефіцієнти молодого покоління (Young Generation). Вони скорочують цикл зворотного зв'язку порівняно з ручним налаштуванням, особливо для робочих навантажень G1, ZGC та Shenandoah.

Платформи спостереження на основі штучного інтелекту отримують дампи купи даних та показники в реальному часі, а потім кластеризують графи збережених об'єктів, щоб позначити підозрілі класи та ланцюжки посилань. Це виявляє кандидати на витоки швидше, ніж ручний аналіз купи даних, і допомагає командам точно визначити першопричину у продакшені.

Підсумуйте цей пост за допомогою: