I've read many articles about GC, and about "do no care about objects" paradigm, but i did a test for proove it.
So idea is: i'm creating a lot of large objects stored in local functions, and I suspect that after all tasks are done it will clean the memory itself. But GC didn't. So test code:
class Program
{
static void Main()
{
var allDone = new ManualResetEvent(false);
int completed = 0;
long sum = 0; //just to prevent optimizer to remove cycle etc.
const int count = int.MaxValue/10000000;
for (int i = 0; i < count; i++)
{
ThreadPool.QueueUserWorkItem(delegate
{
unchecked
{
var dumb = new Dumb();
var localSum = 0;
foreach (int x in dumb.Arr)
{
localSum += x;
}
sum += localSum;
}
if (Interlocked.Increment(ref completed) == count)
allDone.Set();
if (completed%(count/100) == 0)
Console.WriteLine("Progress = {0:N2}%", 100.0*completed/count);
});
}
allDone.WaitOne();
Console.WriteLine("Done. Result : {0}", sum);
Console.ReadKey();
GC.Collect();
Console.WriteLine("GC Collected!");
Console.WriteLine("GC CollectionsCount 0 = {0}, 1 = {1}, 2 = {2}", GC.CollectionCount(0), GC.CollectionCount(1),GC.CollectionCount(2));
Console.ReadKey();
}
}
class Dumb
{
public int[] Arr = Enumerable.Range(1,10*1024*1024).ToArray(); // 50MB
}
so in my case app eat ~2GB of RAM, but when I'm clicking on keyboard and launching GC.Collect it free occuped memory up to normal size of 20mb.
I've read that manual calls of GC etc is bad practice, but i cannot avoid it in this case.
In your example there is no need to explicitly call GC.Collect()
If you bring it up in the task manager or Performance Monitor you will see the GC working as it runs. GC is called when needed by the OS (when it is trying to allocate and doesn't have memory it will call GC to free some up).
That being said since your objects ( greater than 85000 bytes) are going onto the large object heap, LOH, you need to watch out for large object heap fragmentation. I've modified your code so show how you can fragment the LOH. Which will give an out of memory exception even though the memory is available, just not contiguous memory. As of .NET 4.5.1 you can set a flag to request that LOH to be compacted.
I modified your code to show an example of this here:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace GCTesting
{
class Program
{
static int fragLOHbyIncrementing = 1000;
static void Main()
{
var allDone = new ManualResetEvent(false);
int completed = 0;
long sum = 0; //just to prevent optimizer to remove cycle etc.
const int count = 2000;
for (int i = 0; i < count; i++)
{
ThreadPool.QueueUserWorkItem(delegate
{
unchecked
{
var dumb = new Dumb( fragLOHbyIncrementing++ );
var localSum = 0;
foreach (int x in dumb.Arr)
{
localSum += x;
}
sum += localSum;
}
if (Interlocked.Increment(ref completed) == count)
allDone.Set();
if (completed % (count / 100) == 0)
Console.WriteLine("Progress = {0:N2}%", 100.0 * completed / count);
});
}
allDone.WaitOne();
Console.WriteLine("Done. Result : {0}", sum);
Console.ReadKey();
GC.Collect();
Console.WriteLine("GC Collected!");
Console.WriteLine("GC CollectionsCount 0 = {0}, 1 = {1}, 2 = {2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2));
Console.ReadKey();
}
}
class Dumb
{
public Dumb(int incr)
{
try
{
DumbAllocation(incr);
}
catch (OutOfMemoryException)
{
Console.WriteLine("Out of memory, trying to compact the LOH.");
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
try // try again
{
DumbAllocation(incr);
Console.WriteLine("compacting the LOH worked to free up memory.");
}
catch (OutOfMemoryException)
{
Console.WriteLine("compaction of LOH failed to free memory.");
throw;
}
}
}
private void DumbAllocation(int incr)
{
Arr = Enumerable.Range(1, (10 * 1024 * 1024) + incr).ToArray();
}
public int[] Arr;
}
}
The .NET runtime will garbage collect without your call to the GC. However, the GC methods are exposed so that GC collections can be timed with the user experience (load screens, waiting for downloads, etc).
Use GC methods isn't always a bad idea, but if you need to ask then it likely is. :-)
I've read that manual calls of GC etc is bad practice, but i cannot avoid it in this case.
You can avoid it. Just don't call it. The next time you try to do an allocation, the GC will likely kick in and take care of this for you.
Few things I can think of that may be influencing this, but none for sure :(
One possible effect is that GC doesn't kick in right away... the large objects are on the collection queue - but haven't been cleaned up yet. Specifically calling GC.Collect forces collection right there and that's where you see the difference. Otherwise it would've just happened at some point later.
Second reason i can think of is that GC may collect objects, but not necessarily release memory to OS. Hence you'd continue seeing high memory usage even though it's free internally and available for allocation.
The garbage collection is clever and decide when the time right to collect your objects. This is done by heuristics and you must read about that. The garbage collection makes his job very good. Are the 2GB a problem for yout system or you just wondering about the behaviour?
Whenever you call GC.Collect() don't forget the call GC.WaitingForPendingFinalizer. This avoids unwanted aging of objects with finalizer.
Related
I am reading a book on C# memory management which says:
What’s important to understand is that a finalizer increases the life
of an object. Because the finalization code also has to run, the .NET
Framework keeps a reference to the object in a special finalization
queue. An additional thread runs all the finalizers at a time deemed
appropriate based on the execution context. This delays garbage
collection for types that have a finalizer.
As per my knowledge finalizer would run on garbage collection only, not before it. So, how can it delay its garbage collection?
After going through MSDN links posted by comments and deleted answers, below are the details of the whole process:
When a finalizable object is discovered (by GC) to be dead, its finalizer is put in a queue so that its cleanup actions are executed, but the object itself is promoted to the next generation. Therefore, you have to wait until the next garbage collection that occurs on that generation (which is not necessarily the next garbage collection) to determine whether the object has been reclaimed.
Following is the code to demonstrate the same:
using System;
class Program
{
static void CreateDestructorReferences()
{
for (int i = 0; i < 1000; i++)
_ = new Destructor();
}
static void CreateWithoutDestructorReferences()
{
for (int i = 0; i < 1000; i++)
_ = new WithoutDestructor();
}
static void Main(string[] args)
{
CreateDestructorReferences();
DemoGCProcess("****Objects With Destructors*****");
CreateWithoutDestructorReferences();
DemoGCProcess("****Objects Without Destructors*****");
Console.ReadLine();
}
private static void DemoGCProcess(string text)
{
Console.WriteLine(text);
var memory = GC.GetTotalMemory(false);
GC.Collect(0);
GC.WaitForPendingFinalizers();
var memory1 = GC.GetTotalMemory(false);
Console.WriteLine("Memory freed on first Garbage Collection on Generation 0:" + (memory - memory1));
GC.Collect(1);
var memory2 = GC.GetTotalMemory(false);
Console.WriteLine("Memory freed on second Garbage Collection on Generation 0 and Generation 1:" + (memory1 - memory2));
}
}
class Destructor
{
~Destructor()
{
//Console.WriteLine("Destructor is called");
}
}
class WithoutDestructor
{
}
And here is the output of the above program:
I've notice in my test code that using the ConcurrentQueue<> somehow does not release resources after Dequeueing and eventually I run out of memory. Or the Garbage collection is not happening frequently enough.
Here is a snippet of the code. I know the ConcurrentQueue<> store references and yes, I do want create a new object each time so if the enqueueing is faster than dequeueing, memory will continue to rise. Also a screenshot of the memory usage. For testing, I sent through 5000 byte arrays with 500000 elements each.
There is a similar question asked:
ConcurrentQueue holds object's reference or value? "out of memory" exception
and everything mentioned in that post is what I experienced ... except that the memory won't release after dequeueing, even when the Queue is emptied.
I would appreciate any thoughts/insights to this.
ConcurrentQueue<byte[]> TestQueue = new ConcurrentQueue<byte[]>();
Task EnqTask = Task.Factory.StartNew(() =>
{
for (int i = 0; i < ObjCount; i++)
{
byte[] InData = new byte[ObjSize];
InData[0] = (byte)i; //used to show different array object
TestQueue.Enqueue(InData);
System.Threading.Thread.Sleep(20);
}
});
Task DeqTask = Task.Factory.StartNew(() =>
{
int Count = 0;
while (Count < ObjCount)
{
byte[] OutData;
if (TestQueue.TryDequeue(out OutData))
{
OutData[1] = 0xFF; //just do something with the data
Count++;
}
System.Threading.Thread.Sleep(40);
}
Picture of memory
I want to create a function that makes my application allocate X RAM for Y seconds
(I know theres 1.2GB limit on objects).
Is there better way than this?
[MethodImpl(MethodImplOptions.NoOptimization)]
public void TakeRam(int X, int Y)
{
Byte[] doubleArray = new Byte[X];
System.Threading.Sleep(Y*60)
return
}
I would prefer to use unmanaged memory, like this:
IntPtr p = Marshal.AllocCoTaskMem(X);
Sleep(Y);
Marshal.FreeCoTaskMem(p);
Otherwise the CLR garbage collector may play tricks on you.
You have to KeepAlive your block of memory, otherwise the GC can deallocate it. I would even fill the memory (so that you are sure the memory was really allocated, and not reserved)
[MethodImpl(MethodImplOptions.NoOptimization)]
public void TakeRam(int X, int Y)
{
Byte[] doubleArray = new Byte[X];
for (int i = 0; i < X; i++)
{
doubleArray[i] = 0xFF;
}
System.Threading.Sleep(Y)
GC.KeepAlive(doubleArray);
}
I'll add that, at 64bits, the maximum size of an array is something less than 2GB, not 1.2GB.
Unless you write to memory it will not get allocated. Doing repeated garbage collections does not lower the memory usage significantly. The program grows to 1,176,272K with garbage collection, and 1,176,312 K without garbage collection; with this line commented out: GC.GetTotalMemory(true); When this line is commented out byteArray[i] = 99; the program grows to 4,464K
Changed the name of the array; it doesn't hold doubles.
Changed it to write to the memory
Changed the name of the sleep function.
You can see in Task Manager that the memory gets allocated to the running process:
This works:
using System;
using System.Runtime.CompilerServices;
namespace NewTest
{
class ProgramA
{
static void Main(string[] args)
{
TakeRam(1200000000, 3000000);
}
[MethodImpl(MethodImplOptions.NoOptimization)]
static public void TakeRam(long X, int Y)
{
Byte[] byteArray = new Byte[X];
for (int i = 0; i < X; i += 4096)
{
GC.GetTotalMemory(true);
byteArray[i] = 99;
}
System.Threading.Thread.Sleep(Y);
}
}
}
We are running a web farm using .NET. Each web server holds a considerable amount of static objects in it's memory. A Gen 2 garbage collection (GC) takes 10-20 seconds and it runs every 5 minutes.
We ran more or less into the same problems that StackOverflow ran into: http://samsaffron.com/archive/2011/10/28/in-managed-code-we-trust-our-recent-battles-with-the-net-garbage-collector
At the moment, we are reducing the number of objects in the cache. However, this takes time.
At the same time, we implemented the methods documented here to get notifications in .NET about approaching GCs.
The goal is to take a web server out of the farm when a GC is approaching and include it into the farm after the GC is over.
However, we only get a notification for 0.7% of all GCs.
We are using a maxGenerationThreshold and largeObjectHeapThreshold of 8. We tried other thresholds, but the amount of missed GCs did not change.
We are using concurrent server garbage collection (http://msdn.microsoft.com/en-us/library/ms229357.aspx). The GCLatencyMode is Interactive (see http://msdn.microsoft.com/en-us/library/system.runtime.gclatencymode.aspx). Here again, we tried to use other GC modes (Workstation mode, Batch, etc.). And again we did not get a notification for most of the GCs.
Are we doing something wrong, or is it impossible to get a notification for every GC that occurs?
How can we increase the number of notifications?
According to http://assets.red-gate.com/community/books/assets/Under_the_Hood_of_.NET_Management.pdf, at the beginning a GC is triggered when Gen2 hits ~10 MB. We have a lot of RAM, so if we could set this threshold manually to a higher level, it would take more time to reach this threshold and in my understanding the probability would increase to get a notification. Is there a way to modify this threshold?
This is the code that registers and listens to the notifications:
GC.RegisterForFullGCNotification(gcThreshold, gcThreshold);
// Start a thread using WaitForFullGCProc.
thWaitForFullGC = new Thread(WaitForFullGCProc);
thWaitForFullGC.Name = "HealthTestGCNotificationListenerThread (Threshold=" + gcThreshold + ")";
thWaitForFullGC.IsBackground = true;
WaitForFullGCProc():
private void WaitForFullGCProc()
{
try
{
while (!gcAbort)
{
// Check for a notification of an approaching collection.
GCNotificationStatus s;
do
{
int timeOut = CheckForMissedGc() > 0 ? 5000 : (10 * 60 * 1000);
s = GC.WaitForFullGCApproach(timeOut);
if (this.GcState == GCState.InducedUnnotified)
{
// Set the GcState back to okay to prevent the message from staying in the ApplicationMonitoring.
this.GcState = GCState.Okay;
}
} while (s == GCNotificationStatus.Timeout);
if (s == GCNotificationStatus.Succeeded)
{
SetGcState(GCState.Approaching, "GC is approaching..");
gcApproachNotificationCount++;
}
else
{
...
}
Stopwatch stopwatch = Stopwatch.StartNew();
s = GC.WaitForFullGCComplete((int)PrewarnTime.TotalMilliseconds);
long elapsed = stopwatch.ElapsedMilliseconds;
if (s == GCNotificationStatus.Timeout)
{
if (this.ForceGCWhenApproaching && !this.IsInGc && !this.IsPeriodicGcApproaching)
{
this.IsInGc = true;
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true);
GC.WaitForPendingFinalizers();
elapsed = stopwatch.ElapsedMilliseconds;
this.IsInGc = false;
}
}
}
gcAbort = false;
}
catch (Exception e)
{
}
}
Note: This is more of a comment but includes a large code sample.
Have you considered trying to get your GC notifications through another way? Jeffrey Richter ( CLR via C#) explains a good way to get notifications,it uses an object and checks its finalizer method in what generation it is.
This is the class: It uses internal objects which are either collected if the supplied generation matches ( See new GenObject(0); for example. ) or resurrected for the next higher generation.
And you just subscribe to it with GCNotification.GCDone += GCDoneHandler;
public static class GCNotification
{
private static Action<Int32> s_gcDone = null; // The event's field
public static event Action<Int32> GCDone
{
add
{
// If there were no registered delegates before, start reporting notifications now
if (s_gcDone == null) { new GenObject(0); new GenObject(1); new GenObject(2); }
s_gcDone += value;
}
remove { s_gcDone -= value; }
}
private sealed class GenObject
{
private Int32 m_generation;
public GenObject(Int32 generation) { m_generation = generation; }
~GenObject()
{ // This is the Finalize method
// If this object is in the generation we want (or higher),
// notify the delegates that a GC just completed
if (GC.GetGeneration(this) >= m_generation)
{
Action<Int32> temp = Volatile.Read(ref s_gcDone);
if (temp != null) temp(m_generation);
}
// Keep reporting notifications if there is at least one delegate registered,
// the AppDomain isn't unloading, and the process isn’t shutting down
if ((s_gcDone != null)
&& !AppDomain.CurrentDomain.IsFinalizingForUnload()
&& !Environment.HasShutdownStarted)
{
// For Gen 0, create a new object; for Gen 2, resurrect the object
// & let the GC call Finalize again the next time Gen 2 is GC'd
if (m_generation == 0) new GenObject(0);
else GC.ReRegisterForFinalize(this);
}
else { /* Let the objects go away */ }
}
}
}
Consider the code below:
using System;
namespace memoryEater
{
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("alloc 1");
var big1 = new BigObject();
Console.WriteLine("alloc 2");
var big2 = new BigObject();
Console.WriteLine("null 1");
big1 = null;
//GC.Collect();
Console.WriteLine("alloc3");
big1 = new BigObject();
Console.WriteLine("done");
Console.Read();
}
}
public class BigObject
{
private const uint OneMeg = 1024 * 1024;
private static int _idCnt;
private readonly int _myId;
private byte[][] _bigArray;
public BigObject()
{
_myId = _idCnt++;
Console.WriteLine("BigObject {0} creating... ", _myId);
_bigArray = new byte[700][];
for (int i = 0; i < 700; i++)
{
_bigArray[i] = new byte[OneMeg];
}
for (int j = 0; j < 700; j++)
{
for (int i = 0; i < OneMeg; i++)
{
_bigArray[j][i] = (byte)i;
}
}
Console.WriteLine("done");
}
~BigObject()
{
Console.WriteLine("BigObject {0} finalised", _myId);
}
}
}
I have a class, BigObject, which creates a 700MiB array in its constructor, and has a finalise method which does nothing other than print to console. In Main, I create two of these objects, free one, and then create a third.
If this is compiled for 32 bit (so as to limit memory to 2 gigs), an out of memory exception is thrown when creating the third BigObject. This is because, when memory is requested for the third time, the request cannot be satisfied and so the garbage collector runs. However, the first BigObject, which is ready to be collected, has a finaliser method so instead of being collected is placed on the finalisation queue and is finalised. The garbage collecter then halts and the exception is thrown. However, if the call to GC.Collect is uncommented, or the finalise method is removed, the code will run fine.
My question is, why does the garbage collector not do everything it can to satisfy the request for memory? If it ran twice (once to finalise and again to free) the above code would work fine. Shouldn't the garbage collector continue to finalise and collect until no more memory can be free'd before throwing the exception, and is there any way to configure it to behave this way (either in code or through Visual Studio)?
Its undeterministic when GC will work and try to reclaim memory.
If you add this line after big1 = null . However you should be carefult about forcing GC to collect. Its not recommended unless you know what you are doing.
GC.Collect();
GC.WaitForPendingFinalizers();
Best Practice for Forcing Garbage Collection in C#
When should I use GC.SuppressFinalize()?
Garbage collection in .NET (generations)
I guess its because the time the finalizer executes during garbage collection is undefined. Resources are not guaranteed to be released at any specific time (unless calling a Close method or a Dispose method.), also the order that finalizers are run is random so you could have a finalizer on another object waiting, while your object waits for that.