GC zajmuje się przewidywalnym i zarezerwowanym zasobem. Maszyna wirtualna ma nad nią całkowitą kontrolę i całkowitą kontrolę nad tym, jakie wystąpienia są tworzone i kiedy. Słowa kluczowe są tutaj „zastrzeżone” i „pełna kontrola”. Uchwyty są przydzielane przez system operacyjny, a wskaźniki są ... dobrze wskaźnikami do zasobów przydzielonych poza zarządzanym miejscem. Z tego powodu uchwyty i wskaźniki nie są ograniczone do użycia wewnątrz kodu zarządzanego. Mogą być używane - i często są - przez zarządzany i niezarządzany kod działający w tym samym procesie.
„Kolektor zasobów” byłby w stanie zweryfikować, czy uchwyt / wskaźnik jest używany w zarządzanej przestrzeni, czy nie, ale z definicji nie jest świadomy tego, co dzieje się poza przestrzenią pamięci (i, co gorsza, niektóre uchwyty mogą być używane ponad granicami procesu).
Praktycznym przykładem jest .NET CLR. Można używać aromatyzowanego C ++ do pisania kodu, który działa zarówno z zarządzanymi, jak i niezarządzanymi obszarami pamięci; uchwyty, wskaźniki i referencje mogą być przekazywane między kodem zarządzanym i niezarządzanym. Kod niezarządzany musi używać specjalnych konstrukcji / typów, aby umożliwić CLR śledzenie odniesień do zarządzanych zasobów. Ale to najlepsze, co może zrobić. Nie może zrobić tego samego z uchwytami i wskaźnikami, dlatego wspomniany moduł zbierający zasoby nie będzie wiedział, czy można zwolnić określony uchwyt lub wskaźnik.
edycja: Jeśli chodzi o .NET CLR, nie mam doświadczenia w tworzeniu C ++ na platformie .NET. Być może istnieją specjalne mechanizmy, które pozwalają CLR śledzić odwołania do uchwytów / wskaźników między kodem zarządzanym i niezarządzanym. W takim przypadku CLR może zadbać o żywotność tych zasobów i zwolnić je, gdy wszystkie odniesienia do nich zostaną usunięte (cóż, przynajmniej w niektórych scenariuszach może to zrobić). Tak czy inaczej, najlepsze praktyki określają, że uchwyty (szczególnie te wskazujące na pliki) i wskaźniki powinny zostać zwolnione, gdy tylko nie będą potrzebne. Kolektor zasobów nie przestrzegałby tego, to kolejny powód, aby go nie mieć.
edycja 2: Na CLR / JVM / VMs-ogólnie ogólnie jest dość trywialne napisanie kodu, aby zwolnić określony uchwyt, jeśli jest używany tylko w zarządzanej przestrzeni. W .NET byłoby coś takiego:
// 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);
Istnieje wiele technik programowania, które pomagają zarządzać tego rodzaju zasobami.
Programiści C ++ często używają wzorca o nazwie Resource Acquisition to Inicjalizacja , w skrócie RAII. Ten wzorzec gwarantuje, że gdy obiekt trzymający zasoby wyjdzie poza zakres, zamknie zasoby, które trzymał. Jest to pomocne, gdy czas życia obiektu odpowiada określonemu zakresowi w programie (np. Gdy pasuje do czasu, w którym na stosie znajduje się określona ramka stosu), więc jest pomocny w przypadku obiektów wskazywanych przez zmienne lokalne (wskaźnik zmienne przechowywane na stosie), ale nie tak pomocne dla obiektów wskazywanych przez wskaźniki przechowywane na stercie.
Java, C # i wiele innych języków umożliwiają określenie metody, która będzie wywoływana, gdy obiekt nie będzie już żywy i ma zostać odebrany przez moduł wyrzucający elementy bezużyteczne. Zobacz np. Finalizatory
dispose()
i inne. Chodzi o to, że programista może zaimplementować taką metodę, aby jawnie zamknęła zasób, zanim obiekt zostanie zwolniony przez moduł wyrzucający elementy bezużyteczne. Jednak w tych podejściach występują pewne problemy, o których można przeczytać gdzie indziej; na przykład śmieciarz może nie odebrać obiektu, dopóki nie będzie to konieczne.C # i inne języki zapewniają
using
słowo kluczowe, które pomaga zapewnić zamknięcie zasobów, gdy nie są już potrzebne (więc nie zapomnij zamknąć deskryptora pliku lub innego zasobu). Jest to często lepsze niż poleganie na śmieciarzu w celu wykrycia, że obiekt nie jest już aktywny. Zobacz np . Https://stackoverflow.com/q/75401/781723 . Ogólny termin tutaj to zasób zarządzany . Pojęcie to opiera się na RAII i finalizatorach, poprawiając je pod pewnymi względami.źródło
Cała pamięć jest równa, jeśli poproszę o 1K, nie obchodzi mnie, skąd w przestrzeni adresowej pochodzi 1K.
Kiedy pytam o uchwyt pliku, chcę uchwyt do pliku, który chcę otworzyć. Otwarcie uchwytu pliku na pliku często blokuje dostęp do pliku innym procesom lub maszynie.
Dlatego uchwyty plików muszą zostać zamknięte, gdy tylko nie będą potrzebne, w przeciwnym razie blokują dostęp do pliku, ale pamięć musi zostać odzyskana dopiero po jego wyczerpaniu.
Uruchomienie przepustki GC jest kosztowne i odbywa się tylko „w razie potrzeby”, nie można przewidzieć, kiedy inny proces będzie potrzebował uchwytu pliku, którego proces może nie używać, ale nadal jest otwarty.
źródło
Sądzę, że powodem, dla którego nie podchodzono do tego zbyt często w przypadku innych zasobów, jest właśnie to, że większość innych zasobów woli być zwolniona jak najszybciej, aby ktokolwiek mógł ich ponownie użyć.
Pamiętaj oczywiście, że twój przykład można teraz podać za pomocą „słabych” deskryptorów plików przy użyciu istniejących technik GC.
źródło
Sprawdzenie, czy pamięć nie jest już dostępna (a tym samym zagwarantowane, że nie będzie już używana) jest raczej łatwe. Większość innych rodzajów zasobów można obsłużyć mniej więcej tymi samymi technikami (tj. Pozyskiwanie zasobów to inicjalizacja, RAII i jego odpowiednik zwalniania, gdy użytkownik zostanie zniszczony, co łączy go z administracją pamięcią). Wykonanie pewnego rodzaju uwolnienia „na czas” jest ogólnie niemożliwe (sprawdź problem zatrzymania, musisz dowiedzieć się, że jakiś zasób był używany po raz ostatni). Tak, czasami można to zrobić automatycznie, ale jest to znacznie bardziej bałagan w przypadku pamięci. Dlatego w większości zależy od interwencji użytkownika.
źródło