Почему сборка мусора распространяется только на память, а не на другие типы ресурсов?

12

Кажется, что люди устали от ручного управления памятью, поэтому они изобрели сборку мусора, и жизнь была достаточно хорошей. Но как насчет других типов ресурсов? Файловые дескрипторы, сокеты или даже созданные пользователем данные, такие как соединения с базой данных?

Это похоже на наивный вопрос, но я не могу найти места, где кто-то его задавал. Давайте рассмотрим файловые дескрипторы. Скажем, программа знает, что при запуске ей будет доступно только 4000 фдс. Всякий раз, когда он выполняет операцию, которая откроет дескриптор файла, что, если он будет

  1. Проверьте, чтобы убедиться, что он не собирается заканчиваться.
  2. Если это так, запустите сборщик мусора, который освободит кучу памяти.
  3. Если часть памяти освободила сохраненные ссылки на дескрипторы файлов, немедленно закройте их. Он знает, что память принадлежала ресурсу, потому что память, связанная с этим ресурсом, была зарегистрирована в «реестре файловых дескрипторов» из-за отсутствия лучшего термина при первом открытии.
  4. Откройте новый дескриптор файла, скопируйте его в новую память, зарегистрируйте эту ячейку памяти в «реестре дескрипторов файлов» и верните ее пользователю.

Таким образом, ресурс не будет освобожден быстро, но он будет освобожден всякий раз, когда запускается gc, который включает, по крайней мере, непосредственно перед тем, как ресурс должен был закончиться, предполагая, что он не используется полностью.

И кажется, что было бы достаточно для многих определяемых пользователем вопросов очистки ресурсов. Мне удалось найти здесь один комментарий, в котором ссылки делают очистку, аналогичную C ++, с потоком, который содержит ссылку на ресурс, и очищает его, когда остается только одна ссылка (из потока очистки), но я могу: не может найти никаких доказательств того, что это библиотека или часть любого существующего языка.

телепат
источник

Ответы:

4

GC имеет дело с предсказуемым и зарезервированным ресурсом. ВМ имеет полный контроль над ней и полный контроль над тем, какие экземпляры создаются и когда. Ключевыми словами здесь являются «зарезервированный» и «тотальный контроль». Дескрипторы выделяются ОС, а указатели являются ... хорошо указателями на ресурсы, выделенные за пределами управляемого пространства. Из - за этого, ручка и указатели не ограничены для использования внутри управляемого кода. Они могут использоваться - и часто - управляемым и неуправляемым кодом, выполняющимся в одном и том же процессе.

«Сборщик ресурсов» сможет проверить, используется ли указатель / указатель в управляемом пространстве или нет, но он по определению не знает, что происходит за пределами его пространства памяти (и, что еще хуже, могут использоваться некоторые дескрипторы через границы процесса).

Практическим примером является .NET CLR. Можно использовать ароматизированный C ++ для написания кода, который работает как с управляемым, так и с неуправляемым пространством памяти; дескрипторы, указатели и ссылки могут передаваться между управляемым и неуправляемым кодом. Неуправляемый код должен использовать специальные конструкции / типы, чтобы позволить CLR отслеживать ссылки на его управляемые ресурсы. Но это лучшее, что может сделать. Он не может сделать то же самое с дескрипторами и указателями, и из-за этого Resource Collector не будет знать, можно ли освободить определенный дескриптор или указатель.

редактирование: Что касается .NET CLR, я не имел опыта разработки C ++ с платформой .NET. Возможно, существуют специальные механизмы, позволяющие CLR отслеживать ссылки на указатели / указатели между управляемым и неуправляемым кодом. Если это так, то CLR может позаботиться о времени жизни этих ресурсов и освободить их, когда все ссылки на них будут очищены (ну, по крайней мере, в некоторых сценариях это может). В любом случае, лучшие практики диктуют, что ручки (особенно те, указывающие на файлы) и указатели должны быть освобождены, как только они не нужны. Ресурс Коллектор бы не соблюдающих требования с тем, что это еще одна причина, чтобы не иметь.

редактировать 2: На CLR / JVM / VM в целом довольно просто написать некоторый код для освобождения определенного дескриптора, если он используется только внутри управляемого пространства. В .NET было бы что-то вроде:

// This class offends many best practices, but it would do the job.
public class AutoReleaseFileHandle {
    // keeps track of how many instances of this class is in memory
    private static int _toBeReleased = 0;

    // the threshold when a garbage collection should be forced
    private const int MAX_FILES = 100;

    public AutoReleaseFileHandle(FileStream fileStream) {
       // Force garbage collection if max files are reached.
       if (_toBeReleased >= MAX_FILES) {
          GC.Collect();
       }
       // increment counter
       Interlocked.Increment(ref _toBeReleased);
       FileStream = fileStream;
    }

    public FileStream { get; private set; }

    private void ReleaseFileStream(FileStream fs) {
       // decrement counter
       Interlocked.Decrement(ref _toBeReleased);
       FileStream.Close();
       FileStream.Dispose();
       FileStream = null;
    }

    // Close and Dispose the Stream when this class is collected by the GC.
    ~AutoReleaseFileHandle() {
       ReleaseFileStream(FileStream);
    }

    // because it's .NET this class should also implement IDisposable
    // to allow the user to dispose the resources imperatively if s/he wants 
    // to.
    private bool _disposed = false;
    public void Dispose() {
      if (_disposed) {
        return;
      }
      _disposed = true;
      // tells GC to not call the finalizer for this instance.
      GC.SupressFinalizer(this);

      ReleaseFileStream(FileStream);
    }
}

// use it
// for it to work, fs.Dispose() should not be called directly,
var fs = File.Open("path/to/file"); 
var autoRelease = new AutoReleaseFileHandle(fs);
Марсело Де Дзен
источник
3

Это, как представляется, одна из причин языков с Мусоровозами орудий финализаторов. Финализаторы предназначены для того, чтобы позволить программисту очистить ресурсы объекта во время сборки мусора. Большая проблема с финализаторами заключается в том, что они не гарантированно работают.

Здесь есть довольно хорошее описание использования финализаторов:

Доработка и очистка объекта

На самом деле он использует файловый дескриптор в качестве примера. Вы должны обязательно очистить такой ресурс самостоятельно, но есть механизм, который МОЖЕТ восстанавливать ресурсы, которые не были должным образом освобождены.

Брайан Хибберт
источник
Я не уверен, если это отвечает на мой вопрос. В нем отсутствует часть моего предложения, в которой система знает, что у нее заканчивается ресурс. Единственный способ добиться этого - убедиться, что вы вручную запустили gc перед тем, как выделить новые файловые дескрипторы, но это крайне неэффективно, и я не знаю, можете ли вы даже заставить gc работать в java.
Разумный читатель
Хорошо, но файловые дескрипторы обычно представляют открытый файл в операционной системе, что подразумевает (в зависимости от ОС) использование ресурсов системного уровня, таких как блокировки, пулы буферов, пулы структуры и т. Д. Честно говоря, я не вижу пользы в том, чтобы оставлять эти структуры открытыми для последующей сборки мусора, и я вижу много вредов, если их выделять дольше, чем необходимо. Методы Finalize () предназначены для обеспечения последней очистки канавы в случае, если программист пропустил вызовы для очистки ресурсов, но на них не следует полагаться.
Брайан Хибберт
Насколько я понимаю, причина, по которой на них не следует полагаться, заключается в том, что если вы выделите тонну этих ресурсов, например, если вы спускаетесь вниз по файловой иерархии, открывая каждый файл, вы можете открыть слишком много файлов, прежде чем произойдет gc. бежать, вызывая взрыв. То же самое произошло бы с памятью, за исключением того, что среда выполнения проверяет, чтобы убедиться, что она не исчерпает память. Я хотел бы знать, почему система не может быть реализована для восстановления произвольных ресурсов до разрыва, почти так же, как это делается с памятью.
Разумник
Система МОЖЕТ быть записана в ресурсы GC, отличные от памяти, но вам придется отслеживать количество ссылок или иметь какой-то другой метод определения того, когда ресурс больше не используется. Вы не хотите освобождать и перераспределять ресурсы, которые все еще используются. Все помехи могут возникнуть, если у потока есть файл, открытый для записи, ОС «восстанавливает» дескриптор файла, а другой поток открывает другой файл для записи, используя тот же дескриптор. И я также по-прежнему полагаю, что это пустая трата значительных ресурсов, чтобы оставить их открытыми, пока поток, подобный GC, не сможет их освободить.
Брайан Хибберт
3

Существует много методов программирования, которые помогают управлять такими ресурсами.

  • Программисты C ++ часто используют шаблон под названием Resource Acquisition is Initialization , или сокращенно RAII. Этот шаблон гарантирует, что когда объект, который содержит ресурсы, выходит из области видимости, он закрывает ресурсы, к которым он удерживал. Это полезно, когда время жизни объекта соответствует определенной области в программе (например, когда оно соответствует времени, когда в стеке присутствует определенный кадр стека), поэтому это полезно для объектов, на которые указывают локальные переменные (указатель переменные, хранящиеся в стеке), но не очень полезные для объектов, на которые указывают указатели, хранящиеся в куче.

  • Java, C # и многие другие языки предоставляют способ указать метод, который будет вызываться, когда объект больше не существует и собирается собирать сборщик мусора. Смотрите, например, финализаторы dispose()и другие. Идея состоит в том, что программист может реализовать такой метод, чтобы он явно закрывал ресурс, прежде чем объект будет освобожден сборщиком мусора. Однако у этих подходов есть некоторые проблемы, о которых вы можете прочитать в другом месте; например, сборщик мусора может не собирать объект намного позже, чем вы хотели бы.

  • C # и другие языки предоставляют usingключевое слово, которое помогает обеспечить закрытие ресурсов после того, как они больше не нужны (поэтому не забудьте закрыть дескриптор файла или другой ресурс). Это часто лучше, чем полагаться на сборщик мусора, чтобы обнаружить, что объект больше не существует. См., Например, /programming//q/75401/781723 . Общий термин здесь - это управляемый ресурс . Это понятие основывается на RAII и финализаторах, улучшая их в некотором смысле.

DW
источник
Меня меньше интересует быстрое освобождение ресурсов, и меня больше интересует идея своевременного освобождения. RIAA великолепна, но не очень применима ко многим языкам сборки мусора. В Java отсутствует возможность узнать, когда заканчивается определенный ресурс. Операции с использованием и типа скобки полезны и имеют дело с ошибками, но я не заинтересован в них. Я просто хочу распределить ресурсы, и тогда они очистятся, когда это будет удобно или необходимо, и есть немного способов испортить это. Я думаю, что никто действительно не изучал это.
читатель разума
2

Вся память одинакова, если я спрашиваю 1K, мне все равно, откуда в адресном пространстве берется 1K.

Когда я запрашиваю дескриптор файла, я хочу дескриптор файла, который я хочу открыть. Наличие дескриптора файла, открытого для файла, часто блокирует доступ к файлу другими процессами или машиной.

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

Выполнение прохода GC является дорогостоящим и выполняется только «при необходимости», невозможно предсказать, когда другому процессу понадобится дескриптор файла, который ваш процесс, возможно, больше не использует, но все еще имеет открытый.

Ян Рингроз
источник
Ваш ответ поражает реальным ключом: память взаимозаменяема, и у большинства систем достаточно того, что ее не нужно восстанавливать особенно быстро. Напротив, если программа получает эксклюзивный доступ к файлу, это блокирует любые другие программы в юниверсе, которым может понадобиться использовать этот файл, независимо от того, сколько других файлов может существовать.
суперкат
0

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

Обратите внимание, что, конечно, ваш пример теперь может быть представлен с использованием «слабых» файловых дескрипторов с существующими методами GC.

Марк Херд
источник
0

Проверить, что память больше не доступна (и, следовательно, гарантированно больше не будет использоваться), довольно легко. Большинство других типов ресурсов могут обрабатываться более или менее теми же методами (т. Е. Получение ресурсов - это инициализация, RAII и его аналог освобождения при уничтожении пользователя, что связывает его с администрированием памяти). В общем, сделать какое-то освобождение «точно в срок» невозможно (проверьте проблему остановки, вам нужно будет узнать, что какой-то ресурс использовался в последний раз). Да, иногда это можно сделать автоматически, но это гораздо более запутанный случай с памятью. Таким образом, это зависит от вмешательства пользователя по большей части.

vonbrand
источник