O GC lida com um recurso previsível e reservado . A VM tem controle total sobre ela e controle total sobre quais instâncias são criadas e quando. As palavras-chave aqui são "reservadas" e "controle total". Os identificadores são alocados pelo sistema operacional e os ponteiros são ... indicadores de recursos alocados fora do espaço gerenciado. Por esse motivo, identificadores e ponteiros não são restritos para serem usados dentro do código gerenciado. Eles podem ser usados - e geralmente são - por código gerenciado e não gerenciado em execução no mesmo processo.
Um "Coletor de Recursos" seria capaz de verificar se um identificador / ponteiro está sendo usado dentro de um espaço gerenciado ou não, mas, por definição, desconhece o que está acontecendo fora do espaço de memória (e, para piorar, alguns identificadores podem ser usados através dos limites do processo).
Um exemplo prático é o .NET CLR. Pode-se usar C ++ com sabor para escrever código que funciona com espaços de memória gerenciados e não gerenciados; identificadores, ponteiros e referências podem ser passados entre código gerenciado e não gerenciado. O código não gerenciado deve usar construções / tipos especiais para permitir que o CLR mantenha o rastreamento das referências feitas aos seus recursos gerenciados. Mas é o melhor que pode fazer. Ele não pode fazer o mesmo com alças e ponteiros e, por isso, o Coletor de Recursos não saberia se está certo liberar uma alça ou ponteiro específico.
editar: Em relação ao .NET CLR, não tenho experiência no desenvolvimento de C ++ com a plataforma .NET. Talvez existam mecanismos especiais que permitam ao CLR acompanhar as referências a identificadores / ponteiros entre código gerenciado e não gerenciado. Se esse for o caso, o CLR poderá cuidar do tempo de vida desses recursos e liberá-los quando todas as referências a eles forem limpas (bem, pelo menos em alguns cenários). De qualquer maneira, as práticas recomendadas determinam que manipuladores (especialmente aqueles que apontam para arquivos) e ponteiros devem ser liberados assim que não forem necessários. Um coletor de recursos não estaria em conformidade com isso, esse é outro motivo para não ter um.
editar 2: É relativamente trivial no CLR / JVM / VMs-em-geral escrever algum código para liberar um identificador específico, se for usado apenas dentro do espaço gerenciado. No .NET seria algo como:
// 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);
Existem muitas técnicas de programação para ajudar a gerenciar esses tipos de recursos.
Os programadores de C ++ geralmente usam um padrão chamado Resource Acquisition is Initialization , ou RAII, para abreviar. Esse padrão garante que, quando um objeto que se apega aos recursos fica fora do escopo, ele fecha os recursos nos quais estava se apegando. Isso é útil quando a vida útil do objeto corresponde a um escopo específico no programa (por exemplo, quando corresponde ao horário em que um quadro de pilha específico está presente na pilha), portanto, é útil para objetos apontados por variáveis locais (ponteiro variáveis armazenadas na pilha), mas não é tão útil para objetos apontados por ponteiros armazenados na pilha.
Java, C # e muitas outras linguagens fornecem uma maneira de especificar um método que será chamado quando um objeto não estiver mais ativo e prestes a ser coletado pelo coletor de lixo. Veja, por exemplo, finalizadores
dispose()
, e outros. A idéia é que o programador possa implementar esse método para fechar explicitamente o recurso antes que o objeto seja liberado pelo coletor de lixo. No entanto, essas abordagens têm alguns problemas, sobre os quais você pode ler em outros lugares; por exemplo, o coletor de lixo pode não coletar o objeto até muito mais tarde do que você deseja.C # e outros idiomas fornecem uma
using
palavra - chave que ajuda a garantir que os recursos sejam fechados depois que não forem mais necessários (para que você não esqueça de fechar o descritor de arquivo ou outro recurso). Isso geralmente é melhor do que confiar no coletor de lixo para descobrir que o objeto não está mais ativo. Consulte, por exemplo, /programming//q/75401/781723 . O termo geral aqui é um recurso gerenciado . Essa noção se baseia no RAII e nos finalizadores, melhorando-os de algumas maneiras.fonte
Toda a memória é igual, se eu pedir 1K, não me importo de onde no espaço de endereço o 1K vem.
Quando solicito um identificador de arquivo, quero um identificador para o arquivo que desejo abrir. Ter um identificador de arquivo aberto em um arquivo, geralmente bloqueia o acesso ao arquivo por outros processos ou máquina.
Portanto, os identificadores de arquivo devem ser fechados assim que não forem necessários, caso contrário, eles bloqueiam outros acessos ao arquivo, mas a memória só precisa ser recuperada quando você começar a ficar sem ele.
A execução de um passe de GC é dispendiosa e é feita apenas "quando necessário", não é possível prever quando outro processo precisará de um identificador de arquivo que seu processo talvez não esteja mais usando, mas ainda esteja aberto.
fonte
Eu acho que a razão pela qual isso não foi abordado muito para outros recursos é exatamente porque a maioria dos outros recursos prefere ser liberada o mais rápido possível para alguém reutilizar.
Observe, é claro, que seu exemplo pode ser fornecido agora usando descritores de arquivo "fracos" com técnicas existentes de GC.
fonte
Verificar se a memória não está mais acessível (e, portanto, garantimos que não será mais usada) é bastante fácil. A maioria dos outros tipos de recursos pode ser manipulada mais ou menos pelas mesmas técnicas (ou seja, a aquisição de recursos é a inicialização, RAII e sua contrapartida de liberação quando o usuário é destruído, o que o vincula à administração da memória). Em geral, é impossível realizar algum tipo de liberação "just in time" (verifique o problema de interrupção, você precisará descobrir que algum recurso foi usado pela última vez). Sim, às vezes isso pode ser feito automaticamente, mas é um caso muito mais confuso que a memória. Portanto, ele depende, em grande parte, da intervenção do usuário.
fonte