Принудительно освободить огромный массив памяти - PullRequest
0 голосов
/ 23 января 2019

В ядре C #,

У меня есть огромный массив двоичных данных, который мне нужно использовать в своем коде, и мне нужно освободить выделенную память по окончании его использования.

Кодработает в Docker для Linux (с использованием базовых образов: microsoft / dotnet: 2.1-sdk + microsoft / dotnet: 2.1-runtime) - я вижу, что использование кучи всегда становится больше (htop).

byte[] arr = new byte[1024    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();
        }
    }
}
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)); 

Память, выделенная массивом, не распределяется, и сбор, кажется, не работает немедленно?

Я попытался также поместить 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% (почти память), может быть некоторая утечка памяти (и на докере - в linux - это может привести к утечке памяти).

*1024*

Спасибо.

1 Ответ

0 голосов
/ 27 января 2019

Код для ответа работает, но нет, если для массива есть одна ссылка:

, то есть, если я напишу:

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

b = null;

GC.Collect();

Приведенный выше код не соответствует действительностиизбавьтесь от элемента, потому что на него есть ссылка.

Все должно быть непосредственно связано с newArr.data, поэтому в коде вообще нет байта [].

Если нужноСсылка на этот объект, единственное, что я нашел, - это преобразование в другой класс с небезопасным созданием.

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 explicity, мне нужно сохранить массив в некоторой коллекции в коде и добавить функцию, которая проверяет эту коллекцию(всякий раз, когда не было никакого удаления).

т.е.:

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);
        }
    }

}
...