Чому збирання сміття поширюється лише на пам'ять, а не на інші види ресурсів?


12

Схоже, люди втомилися від ручного управління пам’яттю, тому вони винайшли збирання сміття, і життя було досить гарним. А як щодо всіх інших типів ресурсів? Дескриптори файлів, сокети або навіть створені користувачем дані, такі як підключення до бази даних?

Це відчувається як наївне запитання, але я не можу знайти жодного місця, де б хто його задав. Розглянемо дескриптори файлів. Скажімо, програма знає, що матиме лише 4000 FDS, коли вона запускається. Щоразу, коли він виконує операцію, яка відкриє дескриптор файлів, що робити, якщо це буде

  1. Перевірте, чи не закінчиться він.
  2. Якщо це так, запустіть збирач сміття, що звільнить купу пам’яті.
  3. Якщо частина звільненої пам'яті містила посилання на дескриптори файлів, негайно закрийте їх. Він знає, що пам’ять належала ресурсу, оскільки пам'ять, прив'язана до цього ресурсу, була зареєстрована у "реєстрі дескрипторів файлів", через відсутність кращого терміну, коли вона була вперше відкрита.
  4. Відкрийте новий дескриптор файлу, скопіюйте його в нову пам'ять, зареєструйте це місце пам’яті у «реєстрі дескрипторів файлів» та поверніть його користувачеві.

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

І, схоже, цього було б достатньо для багатьох питань очищення ресурсів, визначених користувачем. Тут мені вдалося знайти єдиний коментар, що посилання роблять очищення подібним до цього в C ++ з потоком, що містить посилання на ресурс, і очищає його, коли лише у нього є одне посилання (з потоку очищення), але я можу ' не знайти будь-яких доказів того, що це бібліотека або частина будь-якої існуючої мови.

Відповіді:


4

GC має справу з передбачуваним та зарезервованим ресурсом. ВМ має повний контроль над ним і має повний контроль над тим, які екземпляри створюються та коли. Ключові слова тут "зарезервовані" та "тотальний контроль". Ручки розподіляються ОС, а покажчики - це ... добре вказівники на ресурси, виділені поза керованим простором. Через це, ручки та покажчики не можуть використовуватись всередині керованого коду. Вони можуть використовуватися - і часто є - керованим і некерованим кодом, що працює в одному і тому ж процесі.

"Колектор ресурсів" міг би перевірити, чи використовується ручка / покажчик у керованому просторі чи ні, але він за визначенням не знає, що відбувається поза його простором пам'яті (і, щоб погіршити ситуацію, деякі ручки можуть бути використані через межі процесу).

Практичний приклад - .NET CLR. Можна використовувати ароматизований C ++ для написання коду, який працює як з керованими, так і з некерованими просторами пам'яті; ручки, покажчики та посилання можуть передаватися між керованим та некерованим кодом. Некерований код повинен використовувати спеціальні конструкції / типи, щоб CLR міг відслідковувати посилання на керовані ресурси. Але це найкраще, що можна зробити. Це не може зробити те ж саме з ручками та покажчиками, і через це зазначений Колектор ресурсів не знає, чи нормально випустити певну ручку чи покажчик.

редагувати: Що стосується .NET CLR, я не маю досвіду розробки C ++ з платформою .NET. Можливо, є спеціальні механізми, які дозволяють CLR вести відстеження посилань на ручки / покажчики між керованим та некерованим кодом. Якщо це так, CLR може подбати про життя цих ресурсів і випустити їх, коли там будуть очищені всі посилання на них (ну, принаймні, в деяких сценаріях). Так чи інакше, найкраща практика диктує, що обробляти (особливо ті, що вказують на файли) та покажчики, слід випускати, як тільки вони не потрібні. Колектор ресурсів не дотримується цього, це ще одна причина його не мати.

редагувати 2: В цілому CLR / JVM / VMs відносно тривіально написати якийсь код, щоб звільнити певну ручку, якщо вона використовується лише всередині керованого простору. У .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.
mindreader

Гаразд, але дескриптори файлів зазвичай представляють собою відкритий файл в операційній системі, що передбачає (залежно від ОС) використання ресурсів системного рівня, таких як блокування, буферні пули, пули структури тощо. Чесно кажучи, я не бачу користі в тому, щоб залишити ці структури відкритими для подальшого вивезення сміття, і я бачу багато збитків від того, щоб залишити їх виділено довше, ніж потрібно. Методи Finalize () призначені для того, щоб дозволити останнє очищення канави у випадку, якщо програміст не помітив закликів очистити ресурси, але на нього не слід покладатися.
Брайан Хібберт

Я розумію, що причина, на яку не слід покладатися, полягає в тому, що якщо ви виділили б тону цих ресурсів, як, можливо, ви спускаєтеся вниз по ієрархії файлів, відкриваючи кожен файл, ви можете відкрити занадто багато файлів до того, як gc трапиться бігти, спричиняючи вибух. Те ж саме буде з пам’яттю, за винятком того, що час виконання перевіряється, щоб у неї не закінчилося пам’яті. Мені хотілося б знати, чому система не може бути впроваджена для відшкодування довільних ресурсів до вибуху майже таким же чином, як і пам'ять.
mindreader

Система МОЖНА записуватися до інших ресурсів GC, крім пам’яті, але вам доведеться відслідковувати еталонні підрахунки або використовувати інший метод визначення, коли ресурс більше не використовується. Вам НЕ хочеться розміщувати та перерозподіляти ресурси, які все ще використовуються. Вся спадщина хаосу може виникнути, якщо в потоці відкритий файл для запису, ОС "відновлює" ручку файлу, а інша нитка відкриває інший файл для запису за допомогою тієї ж ручки. А ще я б припустив, що марно витрачати значні ресурси, поки вони не відкриються, доки GC, як нитка, не випустить їх.
Брайан Хібберт

3

Існує багато методик програмування, які допомагають керувати цими видами ресурсів.

  • Програмісти на C ++ часто використовують шаблон, який називається Resource Acquisition - ініціалізація , або короткий RAII. Ця закономірність забезпечує те, що коли об’єкт, який утримує ресурси, виходить із сфери застосування, він закриє ресурси, на які він утримувався. Це корисно, коли термін експлуатації об'єкта відповідає певній області застосування програми (наприклад, коли він відповідає часу, коли в стеці присутній певний кадр стека), тому це корисно для об'єктів, на які вказують локальні змінні (покажчик змінні, що зберігаються в стеку), але не настільки корисні для об'єктів, на які вказують вказівники, що зберігаються на купі.

  • Java, C # та багато інших мов надають спосіб вказати метод, який буде викликатись, коли об’єкт більше не живе і збирається збирати сміттєзбірник. Див., Наприклад, фіналізатори dispose()та ін. Ідея полягає в тому, що програміст може реалізувати такий метод, щоб він явно закрив ресурс, перш ніж об’єкт буде звільнений сміттєзбірником. Однак у цих підходів є деякі проблеми, про які ви можете прочитати деінде; наприклад, сміттєзбірник може збирати об’єкт не пізніше, ніж вам хотілося б.

  • C # та інші мови містять usingключове слово, яке допомагає забезпечити закриття ресурсів після того, як вони більше не потрібні (тому ви не забудьте закрити дескриптор файлу чи інший ресурс). Це часто краще, ніж покладатися на сміттєзбірник, щоб виявити, що об’єкт більше не живе. Дивіться, наприклад, https://stackoverflow.com/q/75401/781723 . Загальний термін тут - керований ресурс . Це поняття ґрунтується на RAII та фіналізаторах, вдосконалюючи їх деяким чином.


Мене менше цікавить оперативне розмежування ресурсів, і більше мене цікавить ідея простої розстановки часу. RIAA - це чудово, але не дуже застосовно для багатьох мов збору сміття. Java не вистачає здатності знати, коли ось-ось закінчиться певний ресурс. Операції з використання та дужки типу корисні і мають справу з помилками, але мене це не цікавить. Я просто хочу виділити ресурси, і тоді вони прибиратимуть себе, коли це буде зручно чи потрібно, і є мало способів викрутити їх. Я здогадуюсь, ніхто в цьому насправді не заглядав.
mindreader

2

Вся пам'ять рівна, якщо я прошу 1K, мені байдуже, звідки в адресному просторі походить 1K.

Коли я запитую ручку файлу, я хочу, щоб ручка до файла, який я хочу відкрити. Маючи ручку файлу, відкриту у файлі, часто блокує доступ до файлу іншими процесами чи машиною.

Тому ручки файлів потрібно закрити, як тільки вони не знадобляться, інакше вони блокують інші доступу до файлу, але пам'ять потрібно відновлювати лише тоді, коли ви починаєте з неї закінчуватися.

Запуск пропуску GC коштує дорого і робиться лише "у разі необхідності", неможливо передбачити, коли для антерного процесу знадобиться обробка файлів, яку ваш процес може більше не використовувати, але все ще відкритий.


Ваша відповідь відповідає дійсному ключу: пам’ять є мінливою, і в більшості систем достатньо, щоб її не потрібно було відновлювати особливо швидко. Навпаки, якщо програма отримує ексклюзивний доступ до файлу, вона блокує будь-які інші програми у всьому світі, які, можливо, потребуватимуть використання цього файлу, незалежно від кількості інших файлів.
supercat

0

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

Зауважте, звичайно, ваш приклад тепер можна надати, використовуючи "слабкі" дескриптори файлів із існуючими методами GC.


0

Перевірити, чи пам’ять більше недоступна (і, таким чином, гарантовано, що вона більше не використовується) досить просто. Більшість інших типів ресурсів можуть обробляти більш-менш однакові методи (тобто придбання ресурсів - це ініціалізація, RAII та його аналог звільнення при знищенні користувача, що пов'язує його з адмініструванням пам'яті). Здійснювати якесь "вивільнення часу" взагалі неможливо (перевірте проблему зупинки; вам доведеться з’ясувати, що якийсь ресурс використовувався востаннє). Так, іноді це можна зробити автоматично, але це набагато важчий випадок, як пам'ять. Таким чином, вона здебільшого покладається на втручання користувача.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.