Организация памяти JVM
При запуске программы, написанной на Java или Котлине, все элементы программы хранятся в различных участках памяти виртуальной машины Java. Технически, JVM память может быть разбита на четыре участка:
- участок для хранения функций (хранит байт-коды всех имеющихся в программе функций);
- участок для хранения констант (хранит строковые литералы и значения переменных, известные во время компиляции)
- куча (хранит значения для переменных ссылочного типа)
- стек (хранит локальные переменные и параметры — ссылки, если их тип ссылочный и значения в противном случае)
Участки для хранения функций и констант во время выполнения программы не меняются или почти не меняются. В куче по мере необходимости (при создании или изменении строк, списков, …) создаются новые участки памяти. Эти участки разрушаются сборщиком мусора, когда их перестают использовать.
Стек по принципу работы подобен магазину автомата. Рассмотрим пример:
fun bar(x: Int, y: Int): Int { // Stack: main::args, foo::n, bar::x, bar::y val z = x + y // Stack: main::args, foo::n, bar::x, bar::y, bar::z return z } fun foo(n: Int): Int { // Stack: main::args, foo::n return bar(n / 2, n * 2) } fun main(args: Array<String>) { // Stack: main::args foo(8) }
Здесь изначально стек хранил только параметр args
, объявленный в функции main
и содержащий ссылку на массив. После вызова foo
в стеке появляется её параметр n
, а после вызова bar
— её параметры x
и y
. Поскольку все эти параметры целые, хранятся их значения, а не ссылки. Затем определяется промежуточная переменная z
, которая тоже попадает в стек.
По окончании работы функции bar
из стека удаляются верхние переменные x
, y
, z
, а после окончания работы foo
— также переменная n
. Таким образом, стек делается больше или меньше в процессе работы функций.