【问题标题】:Force deallocate huge memory array强制释放巨大的内存数组
【发布时间】:2019-01-23 08:29:41
【问题描述】:

在 C# 核心中,

我有一个庞大的二进制数据数组,需要在我的代码中使用,我需要在完成使用后释放它分配的内存。

代码在 docker for linux 中运行(使用基础镜像:microsoft/dotnet:2.1-sdk + microsoft/dotnet:2.1-runtime) - 我看到堆使用量总是越来越大(通过 htop)。

byte[] arr = new byte[1024*1024*1024];
Console.WriteLine("array in gen:{0}", GC.GetGeneration(arr));

// 数组在第 2 代。

当我这样做时:

Console.WriteLine("total mem before:{0}", GC.GetTotalMemory(false)); 
GC.Collect();
Console.WriteLine("total mem after:{0}", GC.GetTotalMemory(false)); 

数组分配的内存没有被释放,collect似乎没有立即生效?

我也尝试过将 arr 放入 IDisposable,但它有帮助吗?

public class DisposeArr : IDisposable
{
    // Flag: Has Dispose already been called?
    bool disposed = false;
    // Instantiate a SafeHandle instance.
    SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true);

    byte[] Data { get; set; }

    public DisposeArr(long size)
    {
        Data = new byte[size];
    }
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (disposed)
            return;

        if (disposing)
        {
            Data = null;
            handle.Dispose();
            // Free any other managed objects here.
            //
        }

        disposed = true;
    }
    ~DisposeArr()
    {
      Dispose(false);
    }
}

在主代码中:

using (DisposeArr newArr = new DisposeArr(1024 * 1024 * 1024))
{

}
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("total mem before:{0}", GC.GetTotalMemory(false)); 
GC.Collect();
Console.WriteLine("total mem after:{0}", GC.GetTotalMemory(false));

如何强制数组分配的内存在 'GC.Collect' 之后立即释放?

这是完整的代码。 该代码可能有效,但如果有 100% 和 95%(几乎是内存),则可能存在一些内存泄漏(在 docker 上 - 在 linux 中 - 这可能会遇到内存泄漏)。

    using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

namespace TestHeap
{
    public class DisposeArr : IDisposable
    {
        // Flag: Has Dispose already been called?
        bool disposed = false;
        // Instantiate a SafeHandle instance.
        SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true);

        byte[] Data { get; set; }

        public DisposeArr(long size)
        {
            Data = new byte[size];
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        // Protected implementation of Dispose pattern.
        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
                return;

            if (disposing)
            {
                Data = null;
                handle.Dispose();
                // Free any other managed objects here.
                //
            }

            disposed = true;
        }
        ~DisposeArr()
        {
          Dispose(false);
        }
    }

    class Program
    {
        public void Run()
        {
            for (int i = 0; i < 100; i++)
            {
                Console.WriteLine("total mem:{0}", GC.GetTotalMemory(false)); GC.Collect();
                using (DisposeArr newArr = new DisposeArr(1024 * 1024 * 1024))
                {

                }
                //            byte[] arr = new byte[1024 * 1024 * 1024];
                Console.WriteLine("New. total mem:{0}", GC.GetTotalMemory(false));
                GC.Collect();
                Console.WriteLine("Collect. total mem:{0}", GC.GetTotalMemory(false));
                GC.WaitForPendingFinalizers();
                Console.WriteLine("Pending. total mem:{0}", GC.GetTotalMemory(false)); GC.Collect();
                GC.Collect();
                Console.WriteLine("Collect. total mem:{0}", GC.GetTotalMemory(false));
            }
            Console.ReadLine();
        }
        static void Main(string[] args)
        {
            Program p = new Program();
            p.Run();
        }
    }
}

谢谢。

【问题讨论】:

  • 几个 cmets:1) 由于其大小,该数组最终位于大对象堆上。 2) Dispose 与 C# 中的释放内存无关。 3)看起来您正在从不同的代码片段提供 sn-ps,例如在您的第一个示例中,该数组称为arr,但您会生成b 指向的内容。你能提供一个例子来说明这个数组没有被GCed吗?
  • 另外,您是处于调试模式还是发布模式?调试模式将使引用保持更长时间。
  • 你为什么觉得有必要这样做?
  • 我修复了代码(arr vs b)。不过,我不知道如何释放分配的内存。另外 - 我在 linux docker 中运行代码(可能代码运行方式与 GC 不同) - microsoft/dotnet:2.1-sdk + icrosoft/dotnet:2.1-runtime
  • 请提供一个显示问题的最小、完整示例。此外,请确保您使用的是发布模式构建。

标签: c# .net-core garbage-collection idisposable


【解决方案1】:

答案的代码有效,但如果数组只有一个引用,则无效:

即,如果我写:

byte[] b;
using (DisposeArr newArr = new DisposeArr(1024 * 1024 * 1024))
{
   b = newArr.Data;
}

b = null;

GC.Collect();

上面的代码并没有真正释放元素,因为有一个对它的引用。

一切都必须直接引用newArr.data,所以代码中根本没有byte[]。

如果我们需要引用该对象,我发现的唯一一件事就是使用不安全的创建转换为其他类。

public static class UnsafeConverter
{
    // to do: convert only arrays of the same size short[] <-> ushort[]
    private static unsafe T GetInstanceByPointer<T>(IntPtr pointer)
    {
        try
        {
            var fakeInstance = default(T);

            var sizeInstance = default(int);
            TypedReference typerefSize = __makeref(sizeInstance);


            TypedReference typedReference = __makeref(fakeInstance);
            *(IntPtr*)(&typedReference) = pointer;
            T instance = __refvalue(typedReference, T);
            return instance;
        }
        catch
        {
            return default;
        }
    }
    public static unsafe T GetInstance<T>(object value)
    {
        try
        {
            GCHandle handle = GCHandle.Alloc(value);
            IntPtr px = (IntPtr)handle;
            T instance = GetInstanceByPointer<T>(px);
            handle.Free();

            return instance;
        }
        catch
        {
            return default;
        }            
    }
} 

在 DisposeArr 类中:

public DisposeArr(byte[] arr)
{
    Data = UnsafeConverter.GetInstance<byte[]>(arr);
}

如果我显式调用 Dispose,我需要将数组保存在代码中的某个集合中,并添加一个检查该集合的函数(只要没有任何 dispose)。

即:

private static ConcurrentDictionary<int, DisposeArr> _allData = new ConcurrentDictionary<int, DisposeArr>()

类声明:

公共类 DisposeArr : IDisposable { ...

    public DisposeArr(long size)
    {
        Data = new byte[size];
        _allData.TryAdd(GetHashCode(), this);
    }

    public static CollectAll()
    {
       for (var item in _allData)
       {
          _allData.Value.Dispose();
       }
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposed)
            return;

        if (disposing)
        {
           ...
           _allData.TryRemove(GetHashCode(), out DisposeArr tmpDisposeArr);
        }
    }

}

【讨论】:

    猜你喜欢
    • 2013-04-22
    • 1970-01-01
    • 2015-10-25
    • 1970-01-01
    • 2016-12-24
    • 2012-08-13
    • 1970-01-01
    • 1970-01-01
    • 2018-01-29
    相关资源
    最近更新 更多