JavaRush /Java блог /Java Developer /Жизненный цикл объекта
Автор
John Selawsky
Senior Java-разработчик и преподаватель в LearningTree

Жизненный цикл объекта

Статья из группы Java Developer
Привет! Думаю, ты не сильно удивишься, если тебе сказать, что размер памяти на твоем компьютере ограничен:) Даже жесткий диск, который в разы больше оперативной памяти, можно забить под завязку любимыми играми, сериалами и прочим. Чтобы этого не произошло, нужно следить за текущим состоянием памяти и удалять с компа ненужные файлы. Какое отношение ко всему этому имеет программирование на Java? Прямое! Ведь при создании любого объекта Java-машиной под него выделяется память. В реальной большой программе создаются десятки и сотни тысяч объектов, под каждый из которых в памяти выделяется свой кусочек.Жизненный цикл объекта  - 1Но как ты думаешь, сколько существуют все эти объекты? “Живут” ли они все время, пока работает наша программа? Разумеется, нет. При всех достоинствах 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.
Жизненный цикл объекта  - 2
Сборщик мусора — внутренний механизм 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. Робот, конечно, попытается что-то сделать, но в один прекрасный момент он застрянет.
Жизненный цикл объекта  - 3
Для его правильной работы нужно держать пол в нормальном состоянии и убирать оттуда все, с чем не справится пылесос. По такому же принципу работает и сборщик мусора. Если в программе будет оставаться много объектов, которые он не может собрать (как носок или Lego для робота-пылесоса), в один прекрасный момент память закончится. И зависнет не только написанная тобой программа, но и все остальные программы, запущенные в этот момент на компьютере. Для них тоже не будет хватать памяти. Вот так выглядят в Java жизненный цикл объектов и сборщик мусора. Это не нужно заучивать: достаточно просто понять принцип работы. В следующей лекции мы поговорим об этих процессах подробнее, а пока — можешь вернуться к решению задач JavaRush :) Успехов!
Комментарии (190)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Islam Yunusov Уровень 25
14 сентября 2023
Кто пробовал запустить у себя код как в примере из лекции ( System.out.println("Объект Cat уничтожен!");) Я неожиданно озадачился как можно посчитать количество строк или же вызовов в консоли. Кто знает? P.S. Пробовал создать счётчик, но дилемма именно в том, что finalize() вызывается не всегда
Anatoly Уровень 22
27 августа 2023
окей
Андрей Карев Уровень 21
17 июля 2023
Все пишут, что finalize() устарел. Как мне правильно реагировать на эту информацию?) Garbage collector всё также работает в автоматическом режиме и это изменение никак не повлияло на процесс разработки, или я пропущу что-то важное, если не копну глубже эту тему?
Rustam Уровень 35 Student
10 июня 2023
Определенно нужно глубже разобрать упомянутые в лекции принципы
Ислам Уровень 32
26 мая 2023
Интересная лекция
Andrey Уровень 20
7 мая 2023
Хорошая статья! Спасибо
Emile Уровень 9
18 января 2023
Еще хорошая статья к данной теме: https://topjava.ru/blog/stack-and-heap-in-java
Perl Developer Уровень 9
15 января 2023
In Java 9, the method java.lang.Object.finalize() is deprecated, and it's not recommended to use it for cleaning up resources. Instead, it's recommended to use try-with-resources statement or the java.lang.AutoCloseable interface to release resources when they are no longer needed. The garbage collector in Java will handle removing the objects from memory automatically when they are no longer reachable.

class MyResource implements AutoCloseable {
    public void doSomething() {
        // Do something with the resource
    }

    @Override
    public void close() throws Exception {
        // Release the resource
    }
}

public class Main {
    public static void main(String[] args) {
        try (MyResource resource = new MyResource()) {
            resource.doSomething();
        } catch (Exception e) {
            // Handle exception
        }
    }
}

In this example, the resource is automatically closed when the try block is exited, whether it is because of a return statement, an exception, or simply reaching the end of the block.
Perl Developer Уровень 9
15 января 2023
Javarush построена на восьмой версии Java видимо. )))

@Deprecated(since="9")
protected void finalize​()
                 throws Throwable
Deprecated. 
Star Skream Уровень 22
5 сентября 2022
Даже не знал про некотырые детали GC) интерестно было почитать )