가비지 수집이 다른 리소스 유형이 아닌 메모리로만 확장되는 이유는 무엇입니까?


12

사람들은 수동 메모리 관리에 질려서 가비지 수집을 발명했으며 인생은 합리적으로 좋았습니다. 그러나 다른 모든 자원 유형은 어떻습니까? 파일 디스크립터, 소켓 또는 심지어 데이터베이스 연결과 같은 사용자 생성 데이터?

이것은 순진한 질문처럼 느껴지지만 누군가가 묻는 곳을 찾을 수 없습니다. 파일 디스크립터를 고려해 봅시다. 프로그램이 시작할 때 4000 fd 만 사용할 수 있다는 것을 프로그램이 알고 있다고 가정하십시오. 파일 디스크립터를 여는 작업을 수행 할 때마다 어떻게됩니까?

  1. 다 떨어지지 않았는지 확인하십시오.
  2. 그렇다면 가비지 수집기를 트리거하여 많은 메모리를 확보하십시오.
  3. 사용 가능한 메모리 중 일부가 파일 설명자에 대한 참조를 보유한 경우 즉시 닫습니다. 해당 리소스에 연결된 메모리가 처음 열렸을 때 더 나은 용어가 없어서 '파일 디스크립터 레지스트리'에 등록 되었기 때문에 리소스가 메모리에 속하는 것으로 알고 있습니다.
  4. 새 파일 디스크립터를 열고 새 메모리에 복사 한 후 해당 메모리 위치를 '파일 디스크립터 레지스트리'에 등록한 후 사용자에게 리턴하십시오.

따라서 리소스가 즉시 해제되지는 않지만 gc가 실행될 때마다 리소스가 완전히 사용되지 않는다고 가정 할 때 리소스가 거의 다 소모되기 직전에 포함됩니다.

그리고 그것은 많은 사용자 정의 리소스 정리 문제에 충분할 것 같습니다. 리소스에 대한 참조가 포함 된 스레드를 사용하여 C ++에서 이와 비슷한 정리를 수행하는 참조를 하나의 주석에서 찾을 수 있었으며 (정리 스레드에서) 단일 참조가 남아있을 때 정리합니다. 도서관이나 기존 언어의 일부라는 증거를 찾지 마십시오.

답변:


4

GC 는 예측 가능하고 예약 된 리소스를 처리합니다. VM은이를 완전히 제어 할 수 있으며 생성되는 인스턴스와시기를 완벽하게 제어 할 수 있습니다. 여기에있는 키워드는 "예약 된"및 "전체 제어"입니다. 핸들은 OS에 의해 할당되고 포인터는 관리 공간 외부에 할당 된 리소스에 대한 포인터입니다. 따라서 핸들과 포인터는 관리 코드 내에서 사용하도록 제한되지 않습니다. 그것들은 같은 프로세스에서 실행되는 관리 코드와 관리되지 않는 코드에 의해 사용될 수 있으며 종종 있습니다.

"리소스 콜렉터"는 핸들 / 포인터가 관리 공간 내에서 사용 중인지 여부를 확인할 수 있지만, 정의 상으로는 메모리 공간 외부에서 발생하는 일을 인식하지 못하며 (일부 상황을 악화시키기 위해 일부 핸들을 사용할 수 있음) 공정 경계를 넘어).

실제 예는 .NET CLR입니다. 풍미있는 C ++를 사용하여 관리되는 메모리 공간과 관리되지 않는 메모리 공간 모두에서 작동하는 코드를 작성할 수 있습니다. 핸들, 포인터 및 참조는 관리 코드와 관리되지 않는 코드 사이에서 전달 될 수 있습니다. 관리되지 않는 코드는 CLR이 관리되는 리소스에 대한 참조를 계속 추적 할 수 있도록 특수한 구성 / 유형을 사용해야합니다. 그러나 그것이 최선을 다하는 것입니다. 핸들과 포인터로도 같은 작업을 수행 할 수 없으며, 따라서 Resource Collector는 특정 핸들이나 포인터를 해제해도 괜찮은지를 알 수 없습니다.

편집 : .NET CLR과 관련하여 .NET 플랫폼을 사용한 C ++ 개발 경험이 없습니다. CLR이 관리 코드와 관리되지 않는 코드 사이의 핸들 / 포인터에 대한 참조를 추적 할 수 있도록하는 특별한 메커니즘이있을 수 있습니다. 이 경우 CLR은 해당 리소스의 수명을 관리하고 리소스에 대한 모든 참조가 지워지면 해제 할 수 있습니다 (적어도 일부 시나리오에서는 가능). 어느 쪽이든, 모범 사례는 핸들 (특히 파일을 가리키는 핸들)과 포인터가 필요하지 않은 즉시 해제되도록 지시합니다. Resource Collector는이를 준수하지 않을 것입니다. 이것이없는 이유입니다.

편집 2 : CLR / JVM / VM-in-general에서는 관리 공간 내부에서만 사용되는 경우 특정 핸들을 해제하는 코드를 작성하는 것이 비교적 간단합니다. .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

그러나 파일 디스크립터는 일반적으로 운영 체제에서 열린 파일을 나타내며 (OS에 따라) 잠금, 버퍼 풀, 구조 풀 등과 같은 시스템 레벨 자원을 사용합니다. 솔직히 말해서, 나중의 가비지 콜렉션을 위해 이러한 구조를 열어 두는 이점을 보지 못하고 필요 이상으로 할당 된 채로두면 많은 손해를 볼 수 있습니다. Finalize () 메서드는 프로그래머가 리소스 정리를위한 호출을 간과했지만 신뢰해서는 안되는 경우 마지막 도랑 정리를 허용하기위한 것입니다.
Brian Hibbert

내가 이해해야하는 이유는 의존하지 않아야하는 이유는 이러한 파일을 할당 할 경우 각 파일을 여는 파일 계층 구조를 내림차순으로 내리면 gc가 발생하기 전에 너무 많은 파일을 열 수 있기 때문입니다. 작동하여 폭발이 발생합니다. 런타임에서 메모리가 부족하지 않은지 확인하는 것을 제외하고는 메모리에서도 마찬가지입니다. 메모리가 수행되는 것과 거의 동일한 방식으로 시스템이 블로우 업 전에 임의의 리소스를 회수하기 위해 시스템을 구현할 수없는 이유를 알고 싶습니다.
mindreader

시스템은 메모리 이외의 GC 리소스에 기록 될 수 있지만 참조 카운트를 추적하거나 리소스가 더 이상 사용되지 않는시기를 결정하는 다른 방법이 있어야합니다. 여전히 사용중인 리소스를 할당 해제하고 재할 당하지 않으려 고합니다. 쓰레드가 쓰기 위해 열린 파일을 가지고 있다면, OS는 파일 핸들을 "환원"하고 다른 쓰레드는 같은 핸들을 사용하여 쓰기 위해 다른 파일을 엽니 다. 또한 스레드와 같은 GC가 풀릴 때까지 열어 두는 것은 상당한 자원 낭비라고 제안합니다.
Brian Hibbert

3

이러한 종류의 리소스를 관리하는 데 도움이되는 많은 프로그래밍 기술이 있습니다.

  • C ++ 프로그래머는 종종 Resource Acquisition is Initialization 또는 RAII 라는 패턴을 사용합니다 . 이 패턴은 자원을 보유한 오브젝트가 범위를 벗어날 때 보유하고있는 자원을 닫습니다. 이는 객체의 수명이 프로그램의 특정 범위에 해당하는 경우 (예 : 특정 스택 프레임이 스택에 존재하는 시간과 일치하는 경우) 유용하므로 로컬 변수 (포인터가 가리키는 객체)에 유용합니다 스택에 저장된 변수), 그러나 힙에 저장된 포인터가 가리키는 객체에는 그다지 도움이되지 않습니다.

  • Java, C # 및 기타 여러 언어는 객체가 더 이상 존재하지 않고 가비지 수집기에서 수집하려고 할 때 호출되는 메서드를 지정하는 방법을 제공합니다. 예를 들어, 종료 자 dispose()및 기타를 참조하십시오 . 이 개념은 프로그래머가 이러한 메소드를 구현하여 가비지 콜렉터가 오브젝트를 해제하기 전에 명시 적으로 자원을 닫을 수 있도록하는 것입니다. 그러나 이러한 접근 방식에는 몇 가지 문제가 있으므로 다른 곳에서 읽을 수 있습니다. 예를 들어 가비지 수집기는 원하는 것보다 훨씬 늦을 때까지 개체를 수집하지 않을 수 있습니다.

  • C # 및 기타 언어 using는 더 이상 필요하지 않은 리소스를 닫을 수 있도록 키워드를 제공 하므로 파일 설명 자나 기타 리소스를 닫는 것을 잊지 마십시오. 이것은 객체가 더 이상 존재하지 않음을 발견하기 위해 가비지 수집기에 의존하는 것보다 낫습니다. 예를 들어 /programming//q/75401/781723을 참조하십시오 . 여기서 일반적인 용어는 관리 자원 입니다. 이 개념은 RAII 및 파이널 라이저를 기반으로하여 어떤 방식으로 개선합니다.


나는 신속한 자원 할당 해제에 관심이없고, 단지 시간 할당 해제에 대한 아이디어에 더 관심이있다. RIAA는 훌륭하지만 많은 가비지 수집 언어에는 적용 할 수 없습니다. Java에 특정 리소스가 부족한시기를 알 수있는 기능이 없습니다. 사용 및 대괄호 유형 작업은 유용하고 오류를 처리하지만 관심이 없습니다. 나는 단순히 리소스를 할당하고 싶을 때 편리하거나 필요할 때마다 스스로를 정리할 것이며,이를 해결할 방법이 거의 없습니다. 나는 아무도 이것을 실제로 보지 않았다고 생각한다.
mindreader

2

모든 메모리는 동일합니다. 1K를 요청하면 1K의 주소 공간의 위치는 중요하지 않습니다.

파일 핸들을 요청할 때 열려는 파일에 대한 핸들을 원합니다. 파일에서 파일 핸들을 열면 다른 프로세스 나 시스템에서 파일에 대한 액세스를 차단하는 경우가 종종 있습니다.

따라서 파일 핸들은 필요하지 않은 즉시 닫아야합니다. 그렇지 않으면 파일에 대한 다른 액세스를 차단하지만 메모리가 부족하면 메모리를 다시 확보해야합니다.

GC 패스를 실행하는 것은 비용이 많이 들고 "필요할 때"만 수행되므로 다른 프로세스에 프로세스가 더 이상 사용하지 않을 수 있지만 여전히 열려있는 파일 핸들이 언제 필요할지 예측할 수 없습니다.


당신의 대답은 실제 열쇠에 부딪칩니다 : 메모리는 재미 있으며, 대부분의 시스템은 특히 빨리 회수 할 필요가 없습니다. 반대로, 프로그램이 파일에 대한 독점 액세스 권한을 얻는 경우 다른 파일 수에 관계없이 해당 파일을 사용해야하는 유니버스의 다른 모든 프로그램을 차단합니다.
supercat

0

나는 이것이 다른 자원에 대해 많이 접근하지 않은 이유는 정확하게 재사용 할 수 있기 때문에 다른 자원이 최대한 빨리 해제되기를 원하기 때문입니다.

물론 기존 GC 기술과 함께 "약한"파일 디스크립터를 사용하여 예제를 제공 할 수도 있습니다.


0

메모리에 더 이상 액세스 할 수 없는지 (따라서 더 이상 사용되지 않는지) 확인하는 것이 다소 쉽습니다. 대부분의 다른 유형의 리소스는 어느 정도 동일한 기술로 처리 할 수 ​​있습니다 (즉, 리소스 획득은 초기화, RAII 및 사용자가 파괴 될 때 해제되는 해당 메모리를 메모리 관리와 연결 함). 일반적으로 "정시"해제 작업을 수행하는 것은 불가능합니다 (중지 문제 확인, 마지막으로 일부 리소스가 사용 된 것을 확인해야합니다). 예, 때로는 자동으로 수행 할 수 있지만 메모리와 같이 더 지저분한 경우입니다. 따라서 대부분 사용자 개입에 의존합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.