在有些情况下,公共语言运行时执行的完整垃圾回收(即第 2 代回收)可能会对性能产生负面影响。 特别是,处理大量请求的服务器可能会出现此问题;在这种情况下,长时间垃圾回收会导致请求超时。为了防止在关键时期发生完全回收,可以接收即将执行完全垃圾回收的通知,再采取措施将工作负载重定向到另一个服务器实例。 也可以自行诱导回收,前提是当前服务器实例不需要处理请求。
RegisterForFullGCNotification 方法注册为,在运行时检测到即将执行完全垃圾回收时发出通知。 此通知分为两个部分:完全垃圾回收何时即将执行,以及完全垃圾回收何时完成。
警告
只有阻止垃圾回收会引发通知。 如果<gcConcurrent>
配置元素已启用,后台垃圾回收不会发出通知。
若要确定何时发出通知,请使用 WaitForFullGCApproach
和 WaitForFullGCComplete
方法。 通常,在 while
循环中使用这些方法,以持续获取表示通知状态的 GCNotificationStatus
枚举。 如果值为 Succeeded
,可以执行下列操作:
- 为了响应使用
WaitForFullGCApproach
方法获得的通知,可以重定向工作负载,并能自行诱导回收。 - 为了响应使用
WaitForFullGCComplete
方法获得的通知,可以让当前服务器实例再次用于处理请求。 也可以收集信息。
例如,可以使用CollectionCount
方法记录回收次数。
WaitForFullGCApproach
和 WaitForFullGCComplete
方法要配合使用。 使用一个方法,而不使用另一个方法,可能会生成不确定的结果。
完全垃圾回收
如果发生下列任一情况,运行时就会执行完全垃圾回收:
- 足够多的内存已提升到第 2 代,导致执行下一个第 2 代回收。
- 足够多的内存已提升到大型对象堆,导致执行下一个第 2 代回收。
- 由于其他因素,导致第 1 代回收升级为第 2 代回收。
在 RegisterForFullGCNotification
方法中指定的阈值适用于前两种情况。 不过,在第一种情况下,不一定会在与指定的阈值相称的时间收到通知,原因有下面两个:
- 运行时不检查每个小型对象分配(出于性能考虑)。
- 只有第 1 代回收将内存提升到第 2 代。
第三种情况也加剧了通知接收时间的不确定性。 可以在此期间重定向请求,或在可以更好适应时自行诱导回收,从而减轻不合时宜的完全垃圾回收造成的影响。尽管并不保证有效,但确实证明这是非常实用的方法。
通知阈值参数
RegisterForFullGCNotification
方法包含两个参数,用于指定第 2 代对象和大型对象堆的阈值。 如果达到这些值,就应发出垃圾回收通知。 下表介绍了这些参数。
参数 | 描述 |
---|---|
maxGenerationThreshold | 介于 1 和 99 之间的数字,指定根据在第 2 代中提升的对象,应何时发出通知。 |
largeObjectHeapThreshold | 介于 1 和 99 之间的数字,指定根据大型对象堆中分配的对象,应何时发出通知。 |
如果指定的值过高,很可能会出现的情况是,将会收到通知,但在运行时执行回收前等待的时间太长。 如果自行诱导回收,回收的对象可能会多于在运行时执行回收时回收的对象。
如果指定的值过低,在运行时执行回收前等待通知的时间可能不够长。
示例
描述
在下面的示例中,一组服务器处理传入的 Web 请求。 为了模拟处理请求的工作负载,将字节数组添加到 List<T>
集合中。 每个服务器都会注册获取垃圾回收通知,再对 WaitForFullGCProc
用户方法启动线程,以持续监视 WaitForFullGCApproach
和 WaitForFullGCComplete
方法返回的 GCNotificationStatus
枚举。
在通知发出时,WaitForFullGCApproach
和 WaitForFullGCComplete
方法调用它们各自的事件处理用户方法:
OnFullGCApproachNotify
此方法调用RedirectRequests
用户方法,指示请求队列服务器暂停向服务器发送请求。 具体模拟方式是,将类级别变量bAllocate
设置为false
,这样就不会再分配对象。
接下来,调用FinishExistingRequests
用户方法,完成处理挂起的服务器请求。 具体模拟方式是,清除List<T>
集合。
最后,由于工作负载很轻,诱导垃圾回收。OnFullGCCompleteNotify
此方法调用用户方法AcceptRequests
以继续接受请求,因为服务器不再易受完全垃圾回收影响。 此操作的具体模拟方式是,将bAllocate
变量设置为true
,以便能够继续将对象添加到List<T>
集合。
下面的代码包含示例的 Main
方法。
using System;
using System.Collections.Generic;
using System.Threading;
namespace GCNotify
{
class Program
{
// Variable for continual checking in the
// While loop in the WaitForFullGCProc method.
static bool checkForNotify = false;
// Variable for suspending work
// (such servicing allocated server requests)
// after a notification is received and then
// resuming allocation after inducing a garbage collection.
static bool bAllocate = false;
// Variable for ending the example.
static bool finalExit = false;
// Collection for objects that
// simulate the server request workload.
static List<byte[]> load = new List<byte[]>();
public static void Main(string[] args)
{
try
{
// Register for a notification.
GC.RegisterForFullGCNotification(10, 10);
Console.WriteLine("Registered for GC notification.");
checkForNotify = true;
bAllocate = true;
// Start a thread using WaitForFullGCProc.
Thread thWaitForFullGC = new Thread(new ThreadStart(WaitForFullGCProc));
thWaitForFullGC.Start();
// While the thread is checking for notifications in
// WaitForFullGCProc, create objects to simulate a server workload.
try
{
int lastCollCount = 0;
int newCollCount = 0;
while (true)
{
if (bAllocate)
{
load.Add(new byte[1000]);
newCollCount = GC.CollectionCount(2);
if (newCollCount != lastCollCount)
{
// Show collection count when it increases:
Console.WriteLine("Gen 2 collection count: {0}", GC.CollectionCount(2).ToString());
lastCollCount = newCollCount;
}
// For ending the example (arbitrary).
if (newCollCount == 500)
{
finalExit = true;
checkForNotify = false;
break;
}
}
}
}
catch (OutOfMemoryException)
{
Console.WriteLine("Out of memory.");
}
finalExit = true;
checkForNotify = false;
GC.CancelFullGCNotification();
}
catch (InvalidOperationException invalidOp)
{
Console.WriteLine("GC Notifications are not supported while concurrent GC is enabled.\n"
+ invalidOp.Message);
}
}
public static void OnFullGCApproachNotify()
{
Console.WriteLine("Redirecting requests.");
// Method that tells the request queuing
// server to not direct requests to this server.
RedirectRequests();
// Method that provides time to
// finish processing pending requests.
FinishExistingRequests();
// This is a good time to induce a GC collection
// because the runtime will induce a full GC soon.
// To be very careful, you can check precede with a
// check of the GC.GCCollectionCount to make sure
// a full GC did not already occur since last notified.
GC.Collect();
Console.WriteLine("Induced a collection.");
}
public static void OnFullGCCompleteEndNotify()
{
// Method that informs the request queuing server
// that this server is ready to accept requests again.
AcceptRequests();
Console.WriteLine("Accepting requests again.");
}
public static void WaitForFullGCProc()
{
while (true)
{
// CheckForNotify is set to true and false in Main.
while (checkForNotify)
{
// Check for a notification of an approaching collection.
GCNotificationStatus s = GC.WaitForFullGCApproach();
if (s == GCNotificationStatus.Succeeded)
{
Console.WriteLine("GC Notification raised.");
OnFullGCApproachNotify();
}
else if (s == GCNotificationStatus.Canceled)
{
Console.WriteLine("GC Notification cancelled.");
break;
}
else
{
// This can occur if a timeout period
// is specified for WaitForFullGCApproach(Timeout)
// or WaitForFullGCComplete(Timeout)
// and the time out period has elapsed.
Console.WriteLine("GC Notification not applicable.");
break;
}
// Check for a notification of a completed collection.
GCNotificationStatus status = GC.WaitForFullGCComplete();
if (status == GCNotificationStatus.Succeeded)
{
Console.WriteLine("GC Notification raised.");
OnFullGCCompleteEndNotify();
}
else if (status == GCNotificationStatus.Canceled)
{
Console.WriteLine("GC Notification cancelled.");
break;
}
else
{
// Could be a time out.
Console.WriteLine("GC Notification not applicable.");
break;
}
}
Thread.Sleep(500);
// FinalExit is set to true right before
// the main thread cancelled notification.
if (finalExit)
{
break;
}
}
}
private static void RedirectRequests()
{
// Code that sends requests
// to other servers.
// Suspend work.
bAllocate = false;
}
private static void FinishExistingRequests()
{
// Code that waits a period of time
// for pending requests to finish.
// Clear the simulated workload.
load.Clear();
}
private static void AcceptRequests()
{
// Code that resumes processing
// requests on this server.
// Resume work.
bAllocate = true;
}
}
}
下面的代码包含 WaitForFullGCProc
用户方法,其中包括持续 while
循环,用于检查是否有垃圾回收通知。
public static void WaitForFullGCProc()
{
while (true)
{
// CheckForNotify is set to true and false in Main.
while (checkForNotify)
{
// Check for a notification of an approaching collection.
GCNotificationStatus s = GC.WaitForFullGCApproach();
if (s == GCNotificationStatus.Succeeded)
{
Console.WriteLine("GC Notification raised.");
OnFullGCApproachNotify();
}
else if (s == GCNotificationStatus.Canceled)
{
Console.WriteLine("GC Notification cancelled.");
break;
}
else
{
// This can occur if a timeout period
// is specified for WaitForFullGCApproach(Timeout)
// or WaitForFullGCComplete(Timeout)
// and the time out period has elapsed.
Console.WriteLine("GC Notification not applicable.");
break;
}
// Check for a notification of a completed collection.
GCNotificationStatus status = GC.WaitForFullGCComplete();
if (status == GCNotificationStatus.Succeeded)
{
Console.WriteLine("GC Notification raised.");
OnFullGCCompleteEndNotify();
}
else if (status == GCNotificationStatus.Canceled)
{
Console.WriteLine("GC Notification cancelled.");
break;
}
else
{
// Could be a time out.
Console.WriteLine("GC Notification not applicable.");
break;
}
}
Thread.Sleep(500);
// FinalExit is set to true right before
// the main thread cancelled notification.
if (finalExit)
{
break;
}
}
}
下面的代码包含 OnFullGCApproachNotify
方法,调用自WaitForFullGCProc
方法。
public static void OnFullGCApproachNotify()
{
Console.WriteLine("Redirecting requests.");
// Method that tells the request queuing
// server to not direct requests to this server.
RedirectRequests();
// Method that provides time to
// finish processing pending requests.
FinishExistingRequests();
// This is a good time to induce a GC collection
// because the runtime will induce a full GC soon.
// To be very careful, you can check precede with a
// check of the GC.GCCollectionCount to make sure
// a full GC did not already occur since last notified.
GC.Collect();
Console.WriteLine("Induced a collection.");
}
下面的代码包含 OnFullGCApproachComplete
方法,调用自WaitForFullGCProc
方法。
public static void OnFullGCCompleteEndNotify()
{
// Method that informs the request queuing server
// that this server is ready to accept requests again.
AcceptRequests();
Console.WriteLine("Accepting requests again.");
}
下面的代码包含调用自 OnFullGCApproachNotify
和 OnFullGCCompleteNotify
方法的用户方法。 用户方法重定向请求,完成现有请求,再在发生完全垃圾回收后继续执行请求。
private static void RedirectRequests()
{
// Code that sends requests
// to other servers.
// Suspend work.
bAllocate = false;
}
private static void FinishExistingRequests()
{
// Code that waits a period of time
// for pending requests to finish.
// Clear the simulated workload.
load.Clear();
}
private static void AcceptRequests()
{
// Code that resumes processing
// requests on this server.
// Resume work.
bAllocate = true;
}
整个代码示例如下所示:
using System;
using System.Collections.Generic;
using System.Threading;
namespace GCNotify
{
class Program
{
// Variable for continual checking in the
// While loop in the WaitForFullGCProc method.
static bool checkForNotify = false;
// Variable for suspending work
// (such servicing allocated server requests)
// after a notification is received and then
// resuming allocation after inducing a garbage collection.
static bool bAllocate = false;
// Variable for ending the example.
static bool finalExit = false;
// Collection for objects that
// simulate the server request workload.
static List<byte[]> load = new List<byte[]>();
public static void Main(string[] args)
{
try
{
// Register for a notification.
GC.RegisterForFullGCNotification(10, 10);
Console.WriteLine("Registered for GC notification.");
checkForNotify = true;
bAllocate = true;
// Start a thread using WaitForFullGCProc.
Thread thWaitForFullGC = new Thread(new ThreadStart(WaitForFullGCProc));
thWaitForFullGC.Start();
// While the thread is checking for notifications in
// WaitForFullGCProc, create objects to simulate a server workload.
try
{
int lastCollCount = 0;
int newCollCount = 0;
while (true)
{
if (bAllocate)
{
load.Add(new byte[1000]);
newCollCount = GC.CollectionCount(2);
if (newCollCount != lastCollCount)
{
// Show collection count when it increases:
Console.WriteLine("Gen 2 collection count: {0}", GC.CollectionCount(2).ToString());
lastCollCount = newCollCount;
}
// For ending the example (arbitrary).
if (newCollCount == 500)
{
finalExit = true;
checkForNotify = false;
break;
}
}
}
}
catch (OutOfMemoryException)
{
Console.WriteLine("Out of memory.");
}
finalExit = true;
checkForNotify = false;
GC.CancelFullGCNotification();
}
catch (InvalidOperationException invalidOp)
{
Console.WriteLine("GC Notifications are not supported while concurrent GC is enabled.\n"
+ invalidOp.Message);
}
}
public static void OnFullGCApproachNotify()
{
Console.WriteLine("Redirecting requests.");
// Method that tells the request queuing
// server to not direct requests to this server.
RedirectRequests();
// Method that provides time to
// finish processing pending requests.
FinishExistingRequests();
// This is a good time to induce a GC collection
// because the runtime will induce a full GC soon.
// To be very careful, you can check precede with a
// check of the GC.GCCollectionCount to make sure
// a full GC did not already occur since last notified.
GC.Collect();
Console.WriteLine("Induced a collection.");
}
public static void OnFullGCCompleteEndNotify()
{
// Method that informs the request queuing server
// that this server is ready to accept requests again.
AcceptRequests();
Console.WriteLine("Accepting requests again.");
}
public static void WaitForFullGCProc()
{
while (true)
{
// CheckForNotify is set to true and false in Main.
while (checkForNotify)
{
// Check for a notification of an approaching collection.
GCNotificationStatus s = GC.WaitForFullGCApproach();
if (s == GCNotificationStatus.Succeeded)
{
Console.WriteLine("GC Notification raised.");
OnFullGCApproachNotify();
}
else if (s == GCNotificationStatus.Canceled)
{
Console.WriteLine("GC Notification cancelled.");
break;
}
else
{
// This can occur if a timeout period
// is specified for WaitForFullGCApproach(Timeout)
// or WaitForFullGCComplete(Timeout)
// and the time out period has elapsed.
Console.WriteLine("GC Notification not applicable.");
break;
}
// Check for a notification of a completed collection.
GCNotificationStatus status = GC.WaitForFullGCComplete();
if (status == GCNotificationStatus.Succeeded)
{
Console.WriteLine("GC Notification raised.");
OnFullGCCompleteEndNotify();
}
else if (status == GCNotificationStatus.Canceled)
{
Console.WriteLine("GC Notification cancelled.");
break;
}
else
{
// Could be a time out.
Console.WriteLine("GC Notification not applicable.");
break;
}
}
Thread.Sleep(500);
// FinalExit is set to true right before
// the main thread cancelled notification.
if (finalExit)
{
break;
}
}
}
private static void RedirectRequests()
{
// Code that sends requests
// to other servers.
// Suspend work.
bAllocate = false;
}
private static void FinishExistingRequests()
{
// Code that waits a period of time
// for pending requests to finish.
// Clear the simulated workload.
load.Clear();
}
private static void AcceptRequests()
{
// Code that resumes processing
// requests on this server.
// Resume work.
bAllocate = true;
}
}
}