1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-thinking-in-java-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
4.3 清除:收尾和垃圾收集.md 21 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 11.03.2025 09:15 d56454c

4.3 Очистка: завершение работы и сборка мусора

Программисты знают важность "инициализации", но часто забывают о значимости очистки. В конце концов, кто нуждается в очистке int, правда? Однако для библиотек просто освобождать объект после использования не всегда безопасно. Конечно, Java использует сборщик мусора для освобождения памяти, занятой объектами, которые больше не используются. Теперь рассмотрим очень специфическую и редкую ситуацию. Предположим, что наш объект выделяет "особое" пространство памяти, не используя new. Сборщик мусора знает только как освободить память, выделенную с помощью new, поэтому он не знает, как освободить "особое" пространство памяти объекта. Для решения этой проблемы Java предоставляет метод finalize(), который можно определить для нашего класса. В идеальном случае его работа должна быть следующей: сразу после того, как сборщик мусора готов освободить память, занимаемую объектом, он сначала вызывает finalize(), а затем только во время следующего процесса сборки мусора происходит фактическая освобождение памяти объекта. Таким образом, если использовать finalize(), можно выполнять важные действия по очистке или уборке во время процесса сборки мусора.Однако это также потенциальная ловушка программирования, поскольку некоторые программисты (особенно те, кто имеет опыт работы на C++) могут ошибочно считать этот метод аналогичным finalize() в C++, который используется для "деструктора" — функции, которая вызывается при удалении объекта. Однако важно различать C++ и Java, так как объекты в C++ обязательно будут удалены (не учитывая возможные ошибки программирования), тогда как объекты в Java не обязательно будут собраны как мусор. Другими словами:Сборка мусора не равнозначна "деструктированию"!

Если помнить об этом постоянно, вероятность попасть в ловушку значительно снижается. Это означает, что до тех пор, пока мы не будем больше нуждаться в объекте, нам следует предпринять определённые действия, и эти действия должны быть выполнены нами самостоятельно. Java не предоставляет "деструктор" или подобные концепции, поэтому необходимо создать примитивный метод для выполнения этих действий по очистке. Например, предположим, что объект, созданный в процессе, отображает себя на экране. Если не удалить изображение объекта со скрина, то оно может никогда не быть очищено. Если внедрить механизм удаления в finalize(), то при условии, что объект будет собран как мусор, изображение сначала само удалится со скрина. Если же объект не будет собран, изображение останется. Поэтому второй ключевой момент, который стоит запомнить: наш объект может не считаться мусором для сборки!Иногда можно заметить, что память, занимаемая объектом, никогда не освобождается, поскольку программа постоянно находится близко к исчерпанию доступной памяти. Если программа завершает выполнение и сборщик мусора до сих пор не освободил память, занятую нашими созданными объектами, то при выходе программы эти ресурсы вернутся операционной системе. Это хорошо, так как сам процесс сборки мусора также требует затрат. Если бы мы никогда его не использовали, то и этих затрат не было бы.## 4.3.1 Зачем нужен метод finalize()

На данный момент вы, возможно, уже уверены в том, чтобы использовать метод finalize() как обычный способ очистки. Какие же преимущества он предоставляет?

Третьим важным аспектом является:

Сборка мусора связана только с памятью!

Другими словами, единственной целью существования сборщика мусора является освобождение памяти, которая больше не используется программой. Поэтому любое действие, связанное со сборкой мусора, должно быть связано именно с памятью и ее освобождением.

Но это либо означает, что если объект содержит другие объекты, метод finalize() должен явно освобождать эти объекты? Ответ отрицательный — сборщик мусора будет отвечать за освобождение всех объектов независимо от того, как они были созданы. Он ограничивает использование метода finalize() специфическими случаями. В этом случае объект может распределять память по-разному по сравнению с тем, как он был создан. Однако стоит отметить, что все в Java — это объекты, поэтому вопрос остается открытым.Почему используется метод finalize(), кажется, что иногда требуется делать что-то отличное от обычного подхода Java путём распределения памяти для выполнения задач в стиле C. Это обычно делается через "нативные" методы, которые позволяют вызывать не-Java методы из Java (проблемы с нативными методами обсуждаются в Приложении A). На данный момент C и C++ являются единственными языками, поддерживающими нативные методы. Но поскольку они могут вызывать подпрограммы, написанные на других языках, они могут эффективно вызывать практически всё. Внутри неприспособленного кода, возможно, используются функции malloc() семейства C для распределения памяти. И пока не вызвана функция free(), память не будет освобождена, что приведёт к появлению "утечек" памяти. Конечно, free() — это функция C и C++, поэтому нам потребуется вызвать её внутри нативного метода в методе finalize(). Прочитав вышеуказанный текст, вы, возможно, уже поняли, что вам не следует чрезмерно использовать finalize(). Эта мысль верна; это не является идеальным местом для выполнения обычной очистки. А где же должна происходить обычная очистка?## 4.3.2 Обязательная очистка

Чтобы очистить объект, пользователи этого объекта должны вызвать метод очистки в том месте, где они хотят его выполнить. Это может показаться простым, но противоречит концепции "деструктора" в C++. В C++ все объекты будут деструктироваться (очищаться). Другими словами, все объекты "должны" быть деструктированы. Если объект создаётся как локальный (например, на стеке) — что невозможно в Java, — то очистка или деструкция будет выполняться в конце области видимости, которая заключена в "конечной фигурной скобке". Если объект был создан с помощью new (как в Java), то при вызове команды delete программистом (что отсутствует в Java), будет вызван соответствующий деструктор. Если программист забудет сделать это, деструктор никогда не будет вызван, и в результате получится "утечка памяти", а также часть объекта никогда не будет очищена.Наоборот, Java не позволяет нам создавать локальные объекты — всегда используется new. Однако в Java нет команды delete, чтобы освободить объект, так как сборщик мусора автоматически освобождает память. Поэтому, если рассматривать это с упрощённой точки зрения, можно сказать, что именно наличие сборщика мусора делает ненужными деструкторы в Java. Однако, по мере углубления знаний, становится ясно, что присутствие сборщика мусора полностью не исключает необходимости в деструкторах или механизме, который они представляют (и, конечно, нельзя напрямую вызвать finalize(), поэтому его следует избегать). Если требуется выполнение какой-либо другой формы очистки помимо освобождения памяти, всё равно потребуется вызов метода в Java. Этот метод эквивалентен деструктору в C++, хотя он менее удобен.```java //: Garbage.java // Демонстрация работы сборщика мусора и финализации


```markdown
class Кресло {
  static boolean gcrun = false;
  static boolean f = false;
  static int создано = 0;
  static int завершено = 0;
  int i;

  Кресло() {
    i = ++создано;
    if (создано == 47) {
      System.out.println("Создано 47");
    }
  }

  protected void finalize() {
    if (!gcrun) {
      gcrun = true;
      System.out.println(
        "Начало завершения после создания " +
        создано + " кресел"
      );
    }
    if (i == 47) {
      System.out.println(
        "Завершение кресла №47, установка флага для прекращения создания кресел"
      );
      f = true;
    }
    завершено++;
    if (завершено >= создано) {
      System.out.println(
        "Все " + завершено + " завершены"
      );
    }
  }
}
```Примечание: В данном примере используется IT-терминология на русском языке, чтобы обеспечить понятность и соответствие стандартам документации. Программа создаёт множество объектов `Chair`, и она прекращает создание новых объектов после запуска сборщика мусора. Поскольку сборщик мусора может запуститься в любое время, программа использует метку `gcrun`, чтобы указать, что сборщик мусора уже начал работу. Вторая метка `f` позволяет объекту `Chair` сообщить основной программе, что следует прекратить создание новых объектов. Обе эти метки устанавливаются внутри метода `finalize()`, который вызывается во время процесса сборки мусора.Два других статических поля — `created` и `finalized` — используются для отслеживания количества созданных объектов и объектов, завершивших свою работу сборщиком мусора соответственно. Наконец, каждый объект `Chair` имеет своё собственное поле (`non-static`) `int i`, которое используется для отслеживания его уникального номера. Когда объект с номером 47 завершает свою работу, метка `f` устанавливается как `true`, что приводит к окончанию создания новых объектов `Chair`.

Все это происходит внутри основной программы — в следующем цикле:

```java
while (!Chair.f) {
    new Chair();
    new String("To take up space");
}

Можно было бы удивиться, когда этот цикл будет остановлен, так как нет явных изменений значения Chair.f. Однако метод finalize() меняет значение этой метки до тех пор, пока не будет завершён объект с номером 47.Каждый новый объект String, созданный в каждом проходе цикла, представляет собой дополнительный мусор, предназначенный для привлечения внимания сборщика мусора. Как только сборщик мусора начинает чувствовать себя «напряжённым» из-за недостатка доступной памяти, он активируется. При запуске этой программы можно предоставить командной строке параметр before или after. В случае использования параметра before, вызывается метод System.gc() (выполняется принудительная сборка мусора) и метод System.runFinalization(), чтобы завершить необходимые операции. Эти методы доступны в Java 1.0, однако метод runFinalizersOnExit(), вызываемый с использованием параметра after, поддерживается только начиная с версии Java 1.1 (замечание ③). Обратите внимание, что этот метод может быть вызван в любое время выполнения программы, и выполнение завершающих операций независимо от того, выполняется ли сборка мусора. К сожалению, схема сборщика мусора в Java 1.0 никогда не могла правильно вызывать метод finalize(). Поэтому этот метод (особенно те, что используются для закрытия файлов) фактически часто вообще не вызывается. Некоторые статьи утверждают, что все модули завершения будут вызваны при выходе программы — даже если программа завершилась аварийно, сборщик мусора не применил бы действия к этим объектам.Это не является действительной ситуацией, поэтому мы не можем рассчитывать на то, что finalize() будет вызван для всех объектов. В частности, метод finalize() практически бесполезен в Java 1.0.Пример программы показывает, что обещание, данное в Java 1.1, стало реальностью: модули завершения обязательно выполняются — но только если явно заставить это произойти. При использовании аргумента, отличного от before или after (например, none), ни один из двух модулей завершения не выполнится, и вы получите следующий вывод:

Created 47

Created 47
Started termination after creating 8694 chairs
Termination of chair #47, setting flag to stop creation of chairs
After all chairs were created:
total created = 9834, total terminated = 108
goodbye!

Поэтому к моменту окончания программы не все модули завершения могут быть вызваны (примечание ④). Для принудительного выполнения модулей завершения можно сначала вызвать System.gc(), затем System.runFinalization(). Это позволит очистить все объекты, которые до этого момента не были использованы. Один странный момент здесь состоит в том, чтобы сначала вызвать gc(), прежде чем вызвать runFinalization(), что кажется противоречащим документации Sun, которая утверждает, что сначала должны быть выполнены модули завершения, а затем освобожден пространство памяти. Однако, если сначала вызвать runFinalization(), а затем gc(), модули завершения просто не выполнятся.

Примечание ④: к тому времени, когда вы прочтете эту книгу, некоторые реализации Java Virtual Machine (JVM) могут начать демонстрировать различное поведение.Для всех объектов Java 1.1 иногда по умолчанию пропускается выполнение модулей завершения, поскольку считается, что стоимость такого выполнения слишком велика. Независимо от того, каким образом вы заставляете выполниться сборку мусора, вы можете заметить более длительную задержку по сравнению со случаями, где нет дополнительных операций завершения.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-thinking-in-java-zh.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-thinking-in-java-zh.git
oschina-mirror
wizardforcel-thinking-in-java-zh
wizardforcel-thinking-in-java-zh
master