Привет! Думаю, ты не сильно удивишься, если тебе сказать, что размер памяти на твоем компьютере ограничен:)
Даже жесткий диск, который в разы больше оперативной памяти, можно забить под завязку любимыми играми, сериалами и прочим. Чтобы этого не произошло, нужно следить за текущим состоянием памяти и удалять с компа ненужные файлы.
Какое отношение ко всему этому имеет программирование на Java? Прямое! Ведь при создании любого объекта Java-машиной под него выделяется память. В реальной большой программе создаются десятки и сотни тысяч объектов, под каждый из которых в памяти выделяется свой кусочек.Но как ты думаешь, сколько существуют все эти объекты? “Живут” ли они все время, пока работает наша программа? Разумеется, нет.
При всех достоинствах Java-объектов, они не бессмертны :) У объектов есть собственный жизненный цикл.
Сегодня мы чуть-чуть отдохнем от написания кода и рассмотрим этот процесс :) Тем более что он является очень важным для понимания работы программы и распоряжения ресурсами.
Итак, с чего же начинается жизнь объекта? Как и у человека — с его рождения, то есть, создания.
Cat cat = new Cat();//вот сейчас и начался жизненный цикл нашего объекта Cat!
Вначале виртуальная Java-машина выделяет необходимый объем памяти для создания объекта.
Потом она создает на него ссылку, в нашем случае — cat
, чтобы иметь возможность его отслеживать. После этого происходит инициализация всех переменных, вызов конструктора и вот — наш свежий объект уже живет своей жизнью :)
Срок жизни у объектов разный, точных цифр здесь не существует. В любом случае, в течение какого-то времени он живет внутри программы и выполняет свои функции.
Если говорить точно, объект является “живым” пока на него есть ссылки. Как только ссылок не остается — объект “умирает”.
Например:
public class Car {
String model;
public Car(String model) {
this.model = model;
}
public static void main(String[] args) {
Car lamborghini = new Car("Lamborghini Diablo");
lamborghini = null;
}
}
В методе main()
объект машины Lamborghini Diablo перестает быть живым уже на второй строке. На него была всего одна ссылка, а теперь этой ссылке был присвоен null
. Поскольку на Lamborghini Diablo не осталось ссылок, он становится “мусором”.
Ссылку при этом не обязательно обнулять:
public class Car {
String model;
public Car(String model) {
this.model = model;
}
public static void main(String[] args) {
Car lamborghini = new Car("Lamborghini Diablo");
Car lamborghiniGallardo = new Car("Lamborghini Gallardo");
lamborghini = lamborghiniGallardo;
}
}
Здесь мы создали второй объект, после чего взяли ссылку lamborghini
и присвоили ей этот новый объект. Теперь на объект Lamborghini Gallardo
указывает две ссылки, а на объект Lamborghini Diablo
— ни одной. Поэтому объект Diablo
становится мусором.
И в этот момент в работу вступает встроенный механизм Java под названием сборщик мусора, или по-другому — Garbage Collector, GC.
Сборщик мусора — внутренний механизм Java, который отвечает за освобождение памяти, то есть удаление из нее ненужных объектов.
Мы не зря выбрали для его изображения картинку с роботом-пылесосом. Ведь сборщик мусора работает примерно так же: в фоновом режиме он “ездит” по твоей программе, собирает мусор, и при этом ты с ним практически не взаимодействуешь.
Его работа — удалять объекты, которые уже не используются в программе. Таким образом он освобождает в компьютере память для других объектов. Помнишь в начале лекции мы говорили, что в обычной жизни тебе приходится следить за состоянием твоего компьютера и удалять старые файлы?
Так вот, в случае с Java-объектами сборщик мусора делает это вместо тебя.
Garbage Collector запускается многократно в течение работы твоей программы: его не надо вызывать специально и отдавать команды, хотя технически это возможно.
Позднее мы еще поговорим о нем и разберем процесс его работы более детально.
В момент, когда сборщик мусора добрался до объекта, перед самым его уничтожением, у объекта вызывается специальный метод —
finalize()
.
Его можно использовать, чтобы освободить какие-то дополнительные ресурсы, которые использовал объект.
Метод finalize()
принадлежит классу Object
. То есть, наравне с equals()
, hashCode()
и toString()
, с которыми ты уже познакомился ранее, он есть у любого объекта.
Его отличие от других методов в том, что он... как бы это сказать... весьма своенравен.
А именно — перед уничтожением объекта он вызывается далеко не всегда.
Программирование — штука точная. Программист говорит компьютеру что-то сделать — компьютер это и делает. Ты, полагаю, уже привык к такому поведению, и тебе поначалу может быть сложно принять идею: “Перед уничтожением объектов вызывается метод finalize()
класса Object
. Или не вызывается. Как повезет!”
Тем не менее, это действительно так. Java-машина сама определяет, вызывать метод finalize()
в каждом конкретном случае или нет.
Например, давай попробуем ради эксперимента запустить такой код:
public class Cat {
private String name;
public Cat(String name) {
this.name = name;
}
public Cat() {
}
public static void main(String[] args) throws Throwable {
for (int i = 0 ; i < 1000000; i++) {
Cat cat = new Cat();
cat = null;//вот здесь первый объект становится доступен сборщику мусора
}
}
@Override
protected void finalize() throws Throwable {
System.out.println("Объект Cat уничтожен!");
}
}
Мы создаем объект Cat
и уже в следующей строчке кода обнуляем единственную ссылку на него. И так — миллион раз.
Мы явно переопределили метод finalize()
, и он должен миллион раз вывести строку в консоль, каждый раз перед уничтожением объекта Cat
.
Но нет! Если быть точным, на моем компьютере он отработал всего 37346 раз! То есть только в 1 случае из 27-ми установленная у меня Java-машина принимала решение вызвать метод finalize()
— в остальных случаях сборка мусора проходила без этого.
Попробуй запустить этот код у себя: скорее всего, результат будет отличаться.
Как видишь,finalize()
трудно назвать надежным партнером :)
Поэтому небольшой совет на будущее: не стоит полагаться на метод finalize()
в случае с освобождением каких-то критически важных ресурсов. Может JVM его вызовет, а может нет. Кто знает? Если твой объект при жизни занимал какие-то суперважные для производительности ресурсы, например, держал открытым соединение с базой данных, лучше создай в своем классе специальный метод для их освобождения и вызови его явно, когда объект уже будет не нужен. Так ты точно будешь знать, что производительность твоей программы не пострадает.
В самом начале мы сказали, что работа с памятью и удаление мусора очень важны, и это действительно так. Неподобающая работа с ресурсами и непонимание процесса сборки ненужных объектов могут привести к утечке памяти. Это одна из самых известных ошибок в программировании.
Неправильно написанный программистом код может привести к тому, что для вновь созданных объектов каждый раз будет выделяться новая память, при этом старые, ненужные объекты будут недоступны для удаления сборщиком мусора.
Раз уж мы привели аналогию с роботом пылесосом, представь, что будет, если перед запуском робота разбросать по дому носки, разбить стеклянную вазу и оставить на полу разобранный конструктор Lego. Робот, конечно, попытается что-то сделать, но в один прекрасный момент он застрянет.
Для его правильной работы нужно держать пол в нормальном состоянии и убирать оттуда все, с чем не справится пылесос.
По такому же принципу работает и сборщик мусора. Если в программе будет оставаться много объектов, которые он не может собрать (как носок или Lego для робота-пылесоса), в один прекрасный момент память закончится. И зависнет не только написанная тобой программа, но и все остальные программы, запущенные в этот момент на компьютере. Для них тоже не будет хватать памяти.
Вот так выглядят в Java жизненный цикл объектов и сборщик мусора. Это не нужно заучивать: достаточно просто понять принцип работы. В следующей лекции мы поговорим об этих процессах подробнее, а пока — можешь вернуться к решению задач JavaRush :) Успехов!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
finalize()
. Весьма болезненная штука, но работать, при необходимости, может, хотя и не всегда... Результаты тестов: Если припрёт (например, при нехватке памяти) JVM может финализировать все объекты, ожидающие своей очереди. Но, если работа JVM завершится раньше, то многие объекты финализации не дождутся...finalize()
, или аннотацию@Override
?@Override
, то метод с точно такой же сигнатурой (имя метода + типы параметров), должен быть объявлен в одном из классов-предков, иначе возникнет ошибка компиляции. Подробнее об этой аннотации можно прочитать здесь и здесь.