JavaRush /Курсы /JSP & Servlets /Память в JVM

Память в JVM

JSP & Servlets
18 уровень , 0 лекция
Открыта

Знакомство с памятью в JVM

Как ты уже знаешь, JVM запускает Java-программы внутри себя. Как и любая виртуальная машина, она имеет собственную систему организации памяти.

Схема организации внутренней памяти указывает на принцип работы вашего Java-приложения. Таким образом можно определить узкие места в работе приложений и алгоритмов. Давай разберемся, как она устроена.

Знакомство с памятью в JVM

Важно! Модель Java в первоначальном виде была недостаточно хороша, поэтому она была пересмотрена в Java 1.5. Эта версия используется по сей день (Java 14+).

Стек потока

Модель памяти в Java, используемая внутри JVM, делит память на стеки потоков (thread stacks) и кучу (heap). Посмотрим на Java-модель памяти, логично разделенную на блоки:

Стек потока

Все потоки, работающие в JVM, имеют свой стек. Стек в свою очередь держит информацию о том, какие методы вызвал поток. Я буду называть это “стеком вызовов”. Стек вызовов возобновляется, как только поток выполняет свой код.

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

Все локальные переменные примитивных типов (boolean, byte, short, char, int, long, float, double) полностью хранятся в стеке потоков и не видны другим потокам. Один поток может передать копию примитивной переменной другому потоку, но не может совместно использовать примитивную локальную переменную.

Куча (heap)

Куча содержит все объекты, созданные в вашем приложении, независимо от того, какой поток создал объект. К этому относятся и обертки примитивных типов (например, Byte, Integer, Long и так далее). Неважно, был ли объект создан и присвоен локальной переменной или создан как переменная-член другого объекта, он хранится в куче.

Ниже диаграмма, которая иллюстрирует стек вызовов и локальные переменные (они хранятся в стеках), а также объекты (они хранятся в куче):

Куча (heap)

В случае, когда локальная переменная примитивного типа, она хранится в стеке потока.

Локальная переменная также может быть ссылкой на объект. В этом случае ссылка (локальная переменная) хранится в стеке потоков, но сам объект хранится в куче.

Объект содержит методы, эти методы содержат локальные переменные. Эти локальные переменные также хранятся в стеке потоков, даже если объект, которому принадлежит метод, хранится в куче.

Переменные-члены объекта хранятся в куче вместе с самим объектом. Это верно как в случае, когда переменная-член имеет примитивный тип, так и в том случае, если она является ссылкой на объект.

Статические переменные класса также хранятся в куче вместе с определением класса.

Взаимодействие с объектами

К объектам в куче могут обращаться все потоки, которые имеют ссылку на объект. Если поток имеет доступ к объекту, то он может получить доступ к переменным этого объекта. Если два потока вызывают метод для одного и того же объекта одновременно, они оба будут иметь доступ к переменным-членам объекта, но каждый поток будет иметь свою собственную копию локальных переменных.

Взаимодействие с объектами (heap)

Два потока имеют набор локальных переменных. Local Variable 2 указывает на общий объект в куче (Object 3). Каждый из потоков имеет свою копию локальной переменной со своей ссылкой. Их ссылки являются локальными переменными и поэтому хранятся в стеках потоков. Тем не менее, две разные ссылки указывают на один и тот же объект в куче.

Обрати внимание, что общий Object 3 имеет ссылки на Object 2 и Object 4 как переменные-члены (показано стрелками). Через эти ссылки два потока могут получить доступ к Object 2 и Object 4.

На диаграмме также показана локальная переменная (Local variable 1 из methodTwo). Каждая ее копия содержит разные ссылки, которые указывают на два разных объекта (Object 1 и Object 5), а не на один и тот же. Теоретически оба потока могут обращаться как к Object 1, так и к Object 5, если они имеют ссылки на оба этих объекта. Но на диаграмме выше каждый поток имеет ссылку только на один из двух объектов.

Пример взаимодействия с объектами

Давай посмотрим, как мы можем продемонстрировать работу в коде:


 public class MySomeRunnable implements Runnable() {

    public void run() {
        one();
    }

    public void one() {
        int localOne = 1;

        Shared localTwo = Shared.instance;

        //… делаем что-то с локальными переменными

        two();
    }

    public void two() {
        Integer localOne = 2;

        //… делаем что-то с локальными переменными
    }
}

public class Shared {

    // храним инстанс на наш объект в переменной

    public static final Shared instance = new Shared();

    // переменные-члены, указывающие на два объекта в куче

    public Integer object2 = new Integer(22);
    public Integer object4 = new Integer(44);
}

Метод run() вызывает метод one(), а one() в свою очередь вызывает two().

Метод one() объявляет примитивную локальную переменную (localOne) типа int и локальную переменную (localTwo), которая является ссылкой на объект.

Каждый поток, выполняющий метод one(), создаст свою собственную копию localOne и localTwo в своем стеке. Переменные localOne будут полностью отделены друг от друга, находясь в стеке каждого потока. Один поток не может видеть, какие изменения вносит другой поток в свою копию localOne.

Каждый поток, выполняющий метод one(), также создает свою собственную копию localTwo. Однако две разные копии localTwo в конечном итоге указывают на один и тот же объект в куче. Дело в том, что localTwo указывает на объект, на который ссылается статическая переменная instance. Существует только одна копия статической переменной, и эта копия хранится в куче.

Таким образом, обе копии localTwo в конечном итоге указывают на один и тот же экземпляр Shared. Экземпляр Shared также хранится в куче. Он соответствует Object 3 на диаграмме выше.

Обрати внимание, что класс Shared также содержит две переменные-члены. Сами переменные-члены хранятся в куче вместе с объектом. Две переменные-члены указывают на два других объекта Integer. Эти целочисленные объекты соответствуют Object 2 и Object 4 на диаграмме.

Также обрати внимание, что метод two() создает локальную переменную с именем localOne. Эта локальная переменная является ссылкой на объект типа Integer. Метод устанавливает ссылку localOne для указания на новый экземпляр Integer. Ссылка будет храниться в своей копии localOne для каждого потока. Два экземпляра Integer будут сохранены в куче и, поскольку метод создает новый объект Integer при каждом выполнении, два потока, выполняющие этот метод, будут создавать отдельные экземпляры Integer. Они соответствуют Object 1 и Object 5 на диаграмме выше.

Обрати также внимание на две переменные-члены в классе Shared типа Integer, который является примитивным типом. Поскольку эти переменные являются переменными-членами, они все еще хранятся в куче вместе с объектом. В стеке потоков хранятся только локальные переменные.

Комментарии (16)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Александр Уровень 66
26 февраля 2025
В оригинале:

public void methodTwo() {
        Integer localVariable1 = new Integer(99);
В случае с этой лекцией:

public void two() {
        Integer localOne = 2;
new то забыли.

public void one() {
        int localOne = 1;

        Shared localTwo = Shared.instance;

        //… делаем что-то с локальными переменными

        two();
    }

    public void two() {
        Integer localOne = 2;

Еще на стадии написания в Идее выдаст ошибку что localOne уже создана в методе one().
Иван Шишкин Уровень 13
27 ноября 2024
Наконец-то, хоть что-то понятное пошло! Статья топ 👍
Олег Уровень 106 Expert
11 сентября 2024
Тема важная, но пока тяжело, очень запутанно
Иван Корниенко Уровень 109
18 июня 2024
Оригинал -> тык
Павел Уровень 19 Expert
24 января 2024
Автор (бот в шляпе) часто повторяет: "Обрати внимание"... Но мне-то от этого обрати внимание не легче. Даже как-то наоборот.
Михаил Шапошников Уровень 1 Expert
16 октября 2023
Stack Стек В Java каждый поток имеет свой собственный стек, который используется для хранения локальных переменных и для отслеживания, на какой стадии выполнения находится поток. Этот стек является приватным для каждого потока и не может быть доступен другим потокам. Что хранится в стеке: 1. **Локальные переменные**: Примитивные типы данных (**int, float, boolean** и т.д.) и ссылки на объекты, которые объявлены внутри метода. 2. **Ссылки на объекты**: Ссылки на объекты, которые хранятся в **Heap(куче)**. 3. **Промежуточные результаты вычислений**: Это могут быть результаты арифметических операций, операций сравнения и т.д., которые используются в выражениях и конструкциях управления. 4. **Информация о вызове методов**: Каждый раз, когда вызывается метод, создается новый "фрейм стека", который хранит информацию о состоянии вызова метода: параметры, локальные переменные, адрес возврата и так далее. Heap(Куча) Куча (Heap) — это область памяти, используемая для хранения объектов в Java. В отличие от стека, который хранит локальные переменные и примитивные типы данных, куча используется для хранения объектов и массивов. Heap является общей для всех потоков. Объекты, созданные в любом потоке, размещаются в heap и могут быть доступны для всех других потоков. Что хранит Heap: 1. **Объекты**: Все объекты, созданные с помощью оператора **`new`**, хранятся в куче. 2. **Поля объектов**: Значения всех полей объектов, включая примитивные типы данных и ссылки на другие объекты. 3. **Массивы**: Все массивы также хранятся в куче, независимо от того, содержат ли они примитивные типы или объекты. 4. **Статические переменные**: Поскольку статические переменные принадлежат классу, а не конкретному экземпляру, они также хранятся в куче. 5. **Пул строк**: Строки в Java хранятся в специальном пуле строк в куче, чтобы повысить эффективность памяти.
Руслан Никитин Уровень 109
7 декабря 2024
статика разве не в метаспейс лежит?
Ольга Николенко Уровень 109 Expert
16 октября 2025
Это зависит от версии java, хранится в permanent genaration - часть кучи / metaspace - не часть кучи. Насколько я поняла. Baeldiung - Permgen vs Metaspace in Java
Руслан Никитин Уровень 109
17 октября 2025
да, metaspace от 8 и выше версиях java и оно не часть кучи
Dima Makarov Уровень 42
18 сентября 2023
Код в конце и его разбор - это как картины Сальвадора Дали, чистый абстракционизм без какого-либо отношения к реальности
Anonymous #3322801 Уровень 2 Expert
15 сентября 2023
Вкратце как я поняла Thread Stack хранит: - стек вызовов - локальные переменные примитивного типа - ссылку на объект (даже если сам объект хранится в куче) Heap хранит: - объект - все поля объекта (примитивные и ссылочные) включая статические Итог по примеру: метод one(): - localOne - примитивная локальная переменная, значение переменной копируется каждым потоком в свой кеш. Не используется потоками совместно. Каждый работает со своей копией. - localTwo - ссылочная локальная переменная, ссылка на объект копируется каждым потоком в свой кеш. Статической контекст указывает на то, что объект существует только в одном экземпляре, т.е. ссылки на объект разные, но ссылаются они по факту на один и тот же объект. Используется потоками совместно. У каждого своя ссылка, но объект один. метод two(): - localOne - ссылочная локальная переменная. Каждый поток, при заходе в данный метод создает новый экземпляр и сохраняет ссылку на этот экземпляр к себе в кеш, как итог у всех потоков, заходящих в данный метод, разные ссылки на разные объекты. Все потоки работают с разными объектами.
Anonymous #3322801 Уровень 2 Expert
19 сентября 2023
Java Memory Model делится на: Heap хранит PermGen (Meta Space) хранит Thread Stack хранит Все ссылочные объекты 1. Хранит описание классов Java 1. Стек вызовов и поля объекта и некоторых дополнительных 2. Локальные (примитивные и ссылочные) данных переменные включая статические. ссылочного типа Все объекты в heap хранятся 3. Ссылку на объект в разных областях памяти в (даже если сам зависимости от пережитых объект хранится ими сборок мусора: в куче) 1. Young generation - Eden - Survival space (S0 и S1) 2. Old generation
jvatechs Уровень 111 Expert
5 августа 2023
Немного выпал с объяснения в последнем абзаце: две переменные-члены в классе Shared типа Integer, который является примитивным типом., т.е. вот эти:

    public Integer object2 = new Integer(22);
    public Integer object4 = new Integer(44);
Чего? Серьезно? С каких пор классы-обертки стали примитивными типами данных?
Pavel Kozhanov Уровень 1
12 апреля 2023
Обрати также внимание на две переменные-члены в классе Shared типа Integer, который является примитивным типом. Поскольку эти переменные являются переменными-членами, они все еще хранятся в куче вместе с объектом. В стеке потоков хранятся только локальные переменные. Все объекты Integer созданные через литерал с одинаковым значением (от -128 до 127) будут равны Тоесть : Integer a = 2; Integer b = 2; a == b; //true т.к. в Джаве помимо пула строк есть пул цифр
Pavel Kozhanov Уровень 1
12 апреля 2023
При создании интеджера от -128 до 127 В ЛЮБОМ ПОТОКЕ (через литерал), этот интеджер будет указывать на объект из пула интов.