Considering all the accolades the Microsoft marketing gurus have received, you might think that they perhaps blinked while naming this particular CLR feature. Who would have thought that a name as unglamorous as the "garbage collector" would stick? Well, considering what the GC actually does, I cannot think of a more appropriate name myself . Believe me, I tried ”for example, "memory space reclaimer" and "managed heap recycler." These entries just do not seem to have the same pizzazz as the "garbage collector." So, sticking with GC, let's examine the "garbage" that gets collected.
On the mainframe, when your COBOL batch Job ended, you knew that you were freeing resources. Additionally, when you did mainframe COBOL CICS development, you knew that the completion of your CICS task freed resources. Now, if I were to ask you to list the "resources" that were freed, you would likely begin with external input and output devices (including disk storage). Perhaps you might continue by mentioning the operating system's Job processing initiator as a freed resource.
Naturally, you would not stop there. You would include the resources associated with the variables defined in your program's Working-Storage Section and Linkage Section as freed resources. Lastly, you would mention the virtual copy of your COBOL program and JCL Job as freed resources. Now for a question: Have you ever given thought to the disposition of these "resources" after they were freed? As I recall, we referred to this as "concerns for the electronic heavens."
These freed resources represented garbage: resources that had been used and discarded. As unsettling as it is to admit, we mainframe programmers just did not care about (or need to care about) the disposition of our garbage as long as the resources were in fact freed. Little did we know, our resident system programmer kept a watchful eye on the available resources systemwide using system-level software to identify the freed resources (our garbage), reclaim unused memory, and compact freed space to minimize fragmentation.
On the .NET platform, you will create lots of garbage. [2] That is to say, your well-written applications will contain many managed objects that eventually are freed. Once freed, the objects become garbage and candidates for collection.
In Chapter 7, you learned about the .NET Framework. As you recall, the Framework includes delegates, interfaces, classes, enumerations, and structures (which I referred to using the acronym DICES). With the exception of enumerations and structures, these .NET Framework objects are destined to become collected garbage. So, why are enumerations and structures treated differently?
Well, in Chapter 7, you learned that enumerations could only contain numeric data types (structures), right? You also learned that all enumerations are derived from the System.Enum class. So, where do you think the System.Enum class is derived from? That is right. System.Enum is derived from the System.ValueType class. As you know, all structures are derived from the same class: System.ValueType. There you have it: Enumerations and structures are excluded from garbage collection because they are both derived from the System.ValueType class and are considered ValueType objects. [3]
Note | ValueType objects can participate in garbage collection if they are boxed . Boxing is further discussed in the section "The Boxing of ValueType Objects" later in this chapter. |
Basically, that leaves you with delegates, interfaces, and classes (and any boxed ValueTypes) to be garbage collected. As the GC makes its rounds looking for garbage (objects that are no longer referenced and available for collection), one of several things can occur:
Dead objects can be collected (i.e., they are effectively removed from the managed heap).
Dead objects that happen to have a Finalizer method can be flagged and moved to a different managed heap [4] (I discuss this further in the section "Garbage Collection Schedule" later in this chapter).
Live objects can be promoted from one heap generation to the next heap generation (i.e., from frequently scanned generation 0 to less frequently scanned generation 1, and so forth).
Figure 8-1 illustrates boxed structures and other .NET Framework objects on the managed heap waiting to be collected by the GC.
As you will notice in Figure 8-1, your installed system will typically support multiple heap generations. As garbage collections occur, any objects that survive will be promoted to the next heap generation. These objects are said to have aged. The following set of code samples is provided to demonstrate the CLR promoting an object from one heap generation to the next. The CLR does this for performance reasons.
Note | The System namespace and GC class are introduced in each code sample to support this demonstration. Comments are included in each code sample to help explain the GC methods being used. |
As you will notice in Listing 8-1, I have created a console application and named the project HeapGenerationExampleCobol . For your convenience, the sample code from the .cob module is provided in Listing 8-1.
![]() |
000010 IDENTIFICATION DIVISION. 000020* This is an example of how the CLR Promotes Objects 000030* from one HEAP Generation to the next - as an object ages 000040 PROGRAM-ID. MAIN. 000050 ENVIRONMENT DIVISION. 000060 CONFIGURATION SECTION. 000070 REPOSITORY. 000080* .NET Framework Classes 000090 CLASS SYS-OBJECT AS "System.Object" 000100 CLASS SYS-INTEGER AS "System.Int32" 000110 CLASS GC AS "System.GC". 000120* 000130 DATA DIVISION. 000140 WORKING-STORAGE SECTION. 000150 77 obj OBJECT REFERENCE SYS-OBJECT. 000160 77 GC_OBJ OBJECT REFERENCE GC. 000170 77 My_Int OBJECT REFERENCE SYS-INTEGER. 000180 77 My_String PIC X(20). 000190 000200 01 NULL-X PIC X(1). 000210 LINKAGE SECTION. 000220 000230 PROCEDURE DIVISION. 000240 000250 DISPLAY "Begin Heap Generation COBOL.NET Example" 000260 DISPLAY " " 000270 000280* Instantiate Object from .NET Framework Classes 000290 INVOKE SYS-OBJECT "NEW" RETURNING obj 000300 000310* Execute GC "GetGeneration" Method Using Inline Invoke syntax 000320 SET My_Int to GC::"GetGeneration" (obj) 000330* Execute ToString Method Using Inline Invoke syntax 000340 SET My_String to My_Int::"ToString" 000350 Display "HEAP Generation of obj BEFORE FIRST collection: " 000360 My_String 000370 000380* Manually Induce Garbage Collection on all Generations 000390 INVOKE GC "Collect". 000400 000410* Execute GC "GetGeneration" Method Using Inline Invoke 000420 SET My_Int to GC::"GetGeneration" (obj) 000430* Execute ToString Method Using Inline Invoke syntax 000440 SET My_String to My_Int::"ToString" 000450 Display "HEAP Generation of obj AFTER FIRST collection: " 000460 My_String 000470 000480* Manually Induce Garbage Collection on all Generations 000490 INVOKE GC "Collect". 000500 000510* Execute GC "GetGeneration" Method Using Inline Invoke 000520 SET My_Int to GC::"GetGeneration" (obj) 000530* Execute ToString Method Using Inline Invoke syntax 000540 SET My_String to My_Int::"ToString" 000550 Display "HEAP Generation of obj AFTER SECOND collection: " 000560 My_String 000570 000580* Remove Object reference 000590* This will make it eligible for Garbage Collection 000600 SET obj to NULL 000610 000620* Manually Induce Garbage Collection on all Generations 000630 INVOKE GC "Collect" 000640 000650* Optionally, I could have induced a collection 000660* specifically on the generation # containing my obj. 000670* Using the syntax "GC.Collect USING BY VALUE var1" 000680* with var1 having the value of 1, to target generation 1 000690 000700 DISPLAY "Enter X and Press Enter to Exit.". 000710 ACCEPT NULL-X. 000720 000730 END PROGRAM MAIN.
![]() |
After you execute the HeapGenerationExampleCobol project, the output shown in Figure 8-2 will appear.
Feel free to view the associated IL (using ILDASM) for this assembly. The more that you do this, the better you will understand the effect your coding statements have on application processing. ( Please see the related common intermediate language [CIL] reference in the "Books" subsection of the "To Learn More" section.)
Let's now take a look at this heap generation example implemented using VB .NET.
As you will notice in Listing 8-2, the VB .NET code sample also is created as a console application. The .vb code for HeapGenerationExampleVB is provided in Listing 8-2.
![]() |
Module Module1 'This is an example of how the CLR Promotes Objects 'from one HEAP Generation to the next - as an object ages Sub Main() Console.WriteLine _ ("Begin Heap Generation Visual Basic.NET Example") Console.WriteLine(String.Empty) 'Instantiate Object from .NET Framework Classes Dim obj As New Object() 'Execute GC "GetGeneration" Method and ToString Method Console.WriteLine _ ("HEAP Generation of obj BEFORE FIRST collection: " _ & GC.GetGeneration(obj).ToString) 'Manually Induce Garbage Collection on all Generations GC.Collect() 'Execute GC "GetGeneration" Method and ToString Method Console.WriteLine _ ("HEAP Generation of obj AFTER FIRST collection: " _ & GC.GetGeneration(obj).ToString) 'Manually Induce Garbage Collection on all Generations GC.Collect() Console.WriteLine _ ("HEAP Generation of obj AFTER SECOND collection: " _ & GC.GetGeneration(obj).ToString) 'Remove Object reference 'This will make it eligible for Garbage Collection obj = Nothing 'Manually Induce Garbage Collection on all Generations GC.Collect() 'Optionally, I could have induced a collection 'specifically on the generation # containing my obj. 'Using the syntax GC.Collect(1) to target generation 1 Console.WriteLine("Press Enter to Exit") Console.ReadLine() End Sub End Module
![]() |
For your convenience, I have executed the HeapGenerationExampleVB project. As you can see in Figure 8-3, the display shows that the CLR has promoted the object from heap generation 0 to heap generation 1 and then to heap generation 2.
As previously mentioned in the section "Creating .NET Garbage," structures and enumerations (in other words, ValueType objects) can participate in garbage collection through the CLR feature called boxing . Using the boxing feature, you can reference a structure or an enumeration (value type) as a reference type object. Usually, this conversion is done implicitly as the result of a coding assignment. Likewise, there is an unboxing feature, which is the boxing operation occurring in reverse. The COBOL .NET and VB .NET code snippets in this section illustrate the boxing feature in practice.
Listing 8-3 presents the COBOL .NET code snippet (using a Console Application template).
![]() |
000010 IDENTIFICATION DIVISION. 000020* This is an example of IMPLICITLY BOXING a Structure 000030 PROGRAM-ID. MAIN. 000040 ENVIRONMENT DIVISION. 000050 CONFIGURATION SECTION. 000060 REPOSITORY. 000070* .NET Framework Classes 000080 CLASS SYS-OBJECT AS "System.Object". 000090* 000100 DATA DIVISION. 000110 WORKING-STORAGE SECTION. 000120 000130* Declare Data Items with COBOL.NET Data Types 000140* this Data Type maps to a Structure/Value Type. 000150* Initialize with the value of 9999 (Hex x'270F). 000160* The myFirstInt variable is allocated on the Stack 000170* does not get Garbage Collected 000180 000190 77 myFirstInt PIC S9(9) USAGE IS COMP-5 Value 9999. 000200* Declare Data Items using .NET Data Types 000210* that References an Object 000220* The myobject variable is allocated memory on the HEAP 000230* and will be Garbage Collected 000240 000250 77 myobject OBJECT REFERENCE SYS-OBJECT. 000260 000270 01 NULL-X PIC X(1). 000280 LINKAGE SECTION. 000290* 000300 PROCEDURE DIVISION. 000310* Reference the Value of the Value Type 000320 SET myobject to myFirstInt. 000330 000340 DISPLAY "Use ILDASM to view the BOXing of the Structure" 000350 DISPLAY "Enter X and Press Enter to Exit.". 000360 ACCEPT NULL-X. 000370 000380 END PROGRAM MAIN.
![]() |
Listing 8-4 presents the VB .NET code snippet (which also uses a Console Application template).
![]() |
Module Module1 Sub Main() 'This is an example of IMPLICITLY BOXING a Structure 'Declare a Structure/Value Type. 'Initialize with the value of 9999 (Hex x'270F) 'The myFirstInt variable is allocated on the Stack 'and does not get Garbage Collected Dim myFirstInt As Integer = 9999 'Declare a Reference Type Object 'Reference the Value of the Value Type 'The myobject variable is allocated memory on the HEAP 'and will be Garbage Collected Dim myobject As Object = myFirstInt Console.WriteLine _ ("Use ILDASM to view the BOXing of the Structure") Console.WriteLine _ ("Press Enter to Exit") Console.ReadLine() End Sub End Module
![]() |
Now, let's take a peek at the ILDASM display and look at the MSIL for evidence that this boxing feature is actually executed. You will notice that the box command appears in the MSIL for each language. Notice also that the hex value of x'270F' is used to initialize the value type variable.
Listing 8-5 presents the MSIL snippet for the COBOL .NET BoxingExampleCOBOL project.
![]() |
. . . <copied from the _InitialProgram Method Output> IL_0016: ldc.i4 0x270f IL_001b: stsfld int32 MAIN::MYFIRSTINT_001CE9F8 IL_0020: ldnull IL_0021: stsfld object MAIN::MYOBJECT_001CEAB8 . . . <copied from the Procedure Method Output> IL_006a: stelem.ref IL_006b: br.s IL_006d IL_006d: ldsfld int32 MAIN::MYFIRSTINT_001CE9F8 IL_0072: box int32 IL_0077: stsfld object MAIN::MYOBJECT_001CEAB8 IL_007c: ldloc __DisplayInfo IL_0080: ldc.i4.0 . . .
![]() |
Listing 8-6 shows the MSIL snippet for the VB .NET BoxingExampleVB project.
![]() |
<copied from the Main Method Output> .method public static void Main() cil managed . . . .locals init ([0] int32 myFirstInt, [1] object myobject) IL_0000: nop IL_0001: ldc.i4 0x270f IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: box [mscorlib]System.Int32 IL_000d: stloc.1 . . . IL_002a: nop IL_002b: ret } // end of method Module1::Main
![]() |
I included this small amount of COBOL .NET and VB .NET code to demonstrate the activities that go on behind the scenes. Some of these activities result in different amounts of garbage being created by your application. Some of these things will matter to you.
You may come across information in the future (and even in the next chapter of this book) that will point out advantages of value type objects over reference type objects. But as you can see here, depending on how you use them, you could end up actually creating reference type objects that will get garbage collected.
In other words, there are times when you should use value type objects and other times when reference type objects are more suitable. Each object type has special program coding considerations. Each object type has its pros and cons. However, once a value type object is boxed, the playing field is somewhat leveled.
Cross-Reference | In Chapter 9, you will explore value type objects and reference type objects in detail. |
Tip | Just for clarification , the distinction made in Chapter 6 between COBOL .NET data types and .NET data types shouldn't be confused with the distinction made here between value types and reference types. For example, within the category of .NET data types, you have both value types and reference types. This was further explained in Chapter 7 in the section "Structures." Did I confuse you? Don't worry, in Chapter 9 I thoroughly discuss the topic of objects. Then, having come full circle, I'm sure your understanding will be complete. |
In the section "Creating .NET Garbage" earlier in this chapter, I mentioned that the .NET GC "makes its rounds." Then, I turned around and used a GC.Collect method in the heap generation code samples to manually induce a garbage collection. Of course, this is an obvious contradiction.
At this point, you are ready to have the following questions answered :
What is the garbage collection schedule?
Can you control the garbage collection schedule?
Should you control the garbage collection schedule?
The real answer to the first question is "Who knows ?" Now, this does not sound like a polite way to answer a question, right? Given the technical nature of the topic, some will say that this answer is just barely acceptable. We reformed mainframe programmer types have been trained for years to never guess, to never assume anything. So, in our honor , here is a more appropriate answer. (Be sure to take a deep breath before you continue!)
The GC has an optimizing engine that determines how often to perform a collection. The GC is described as following a heuristic approach. Depending on the actual characteristic of managed heap allocation attempts and available space at the time, the GC follows an established algorithm. [5] For example, if an application attempts to allocate space, the first target managed heap generation is generation 0. The GC checks to see if there is enough space to complete the allocation (this also involves processing of objects and generation promotion of objects). If the needed space is not available on generation 0, the GC then follows the same algorithm on generation 1 and then again on generation 0 (remember that eligible objects are being removed, disposed, finalized, and/or promoted). The GC compacts the free space found on the managed heap. The GC, if needed, then goes through the same process with generation 2, then generation 1, and then generation 0. Again, it is processing objects, promoting objects, reclaiming and compacting space, and so forth.
All right, now exhale. That was certainly a mouthful. Can you do me a favor? The next time that someone asks you about the GC's schedule, try giving them this same answer. As you watch the person's jaw drop, calmly sum things up by saying something like "It's really intuitive and simple." Hey, why not have a little fun every now and then?
Note | Just a reminder: The GC is a feature of the CLR. |
Let us now tackle the remaining two questions, which both speak to the topic of controlling the GC schedule.
Yes, for better or for worse , you can control the GC schedule. Why "for better or for worse"? Well, first understand that generally speaking, you should not even think about controlling, influencing, manipulating, or otherwise intervening with the GC's schedule.
My liberal use of the GC.Collect method in this chapter in various code samples is for demonstration purposes only. Some of the same coding statements available (e.g., GC.Collect) for increasing the frequency of collection could in some cases improve one application's performance but seriously degrade a different application's performance. This also holds true for the use of the Dispose and Finalize methods. You should use the Dispose and Finalize methods only after you have performed detailed analysis regarding the intended benefit and any potential negative impact of using them. Having said that, here are a few other legitimate scenarios for explicitly influencing the GC schedule:
Your managed code makes use of unmanaged resources (e.g., file, window, or network connections).
Your managed code makes use of unmanaged objects (e.g., legacy applications, COM objects, and so forth).
Your managed code makes use of only managed objects and managed resources, yet your application fits a rare example of one that has the need to adopt a "deterministic" cleanup approach.
I strongly recommend that if your application falls into one of these three scenarios you refer to the "To Learn More" section at the end of this chapter. You will find a healthy number of useful references to support your continued learning.
Caution | A few of you might feel a little uncomfortable with the idea of my mentioning a piece of technology only to turn around and discourage its use. First, I wish to only discourage its misuse . Second, we highly skilled reformed mainframe programmers have done this sort of thing before. Haven't you cautioned others on the use of the FREEMAIN and GETMAIN commands when doing mainframe COBOL CICS development? As you know, the misuse of these "memory management" commands can have grave consequences. Suffice it to say that a warning is appropriate in this context as well. |
Are you ready for one more stab at the GC topic? Good! I think you will like the next discussion.
Perhaps you are starting to get a feel for this CLR feature, this powerful engine called the GC. Maybe, just maybe, you have a feel for the scheduling concerns that surround the GC. Through discussion and sample code you have even explored the heap generation promotion behavior of the CLR and GC. But still you are curious ; you are thirsty for more. OK, here you go.
Included with the Windows and .NET platform are many tools that monitor performance. Given the impact that memory management (and mismanagement) can have on performance, familiarity with these monitoring tools is relevant. I will use this section to give you yet another view of the actual "behavior" of the GC.
Among the many Windows-based performance-monitoring tools are a few that are bundled with various Windows and .NET software editions:
Windows Task Manager (discussed in Chapter 3)
Application Center Test (ACT) (discussed in Chapter 13)
Performance Monitor (Perfmon)
As noted, the Windows Task Manager and Application Center Test are discussed elsewhere in this book ”in Chapter 3 and Chapter 13, respectively.
Tip | To access the Windows Task Manager utility, press Ctrl-Alt-Delete. Depending on your version of the Windows operating system, you may have to click Task Manager after you press the keyboard combination. Optionally, you can access the Windows Task Manager utility by right- clicking an empty space on the taskbar and then clicking Task Manager. |
At this time, I will introduce you to the Windows product called Performance Monitor (Perfmon). Functionally, you might (loosely) compare this product with some of the various OMEGAMON-type mainframe-monitoring products. As you will soon see, with Perfmon you can obtain a rather granular view of your application's performance (including the general performance of your system-level processes).
You can access the Perfmon tool in one of several ways:
From your desktop, click the Start button and select Programs Administrative Tools Performance.
From an open Windows Task Manager window on the toolbar, select File New Task (Run). Then enter Perfmon in the Open text box and click OK.
From your desktop, click the Start button and select Run. Then enter Perfmon in the Open text box and click OK.
Programmatically, you can access the Perfmon objects (or performance counter objects) via an instance of the .NET namespace System.Diagnostics.PerformanceCounter .
For this discussion, you will access Perfmon using the last two approaches. First, you will take a look at the "programmatic" approach with a set of sample applications. Following that discussion, I introduce the actual Perfmon tool.
Note | You will notice a large block of COBOL .NET DISPLAY statements and VB .NET Console.WriteLine statements in the following set of sample applications (PerfmonCobol and PerfmonVB). As you execute the sample applications for the first discussion in the section "Creating a Performance Counter Component," simply disregard these code comments/instructions. I provided these code comments/ instructions as preparation for the discussion in the section "Using the Windows Performance Monitor Tool" later in this chapter. In this later section, you will again execute this same set of sample applications. At that time, you should pay attention to the code comments/ instructions. |
In the following set of code samples, you will separately create a COBOL .NET project and a VB .NET project. I created each using the Console Application template type. I chose to name the applications PerfmonCobol and PerfmonVB, respectively. In each project, you will focus on the PerformanceCounter class from the System.Diagnostics .NET namespace. The following five properties of the PerformanceCounter class are used:
Input: CategoryName = ".NET CLR Memory"
Input: CounterName = "# Bytes in all Heaps"
Input: MachineName = "."
Input: InstanceName = "PerfmonVB"
Output: RawValue
I chose this particular performance object (the value specified in the CategoryName property) and this particular performance counter (the value specified in the CounterName property) for demonstration purposes only. In the section "Performance Counter Objects" later in this chapter, I list a few of the other performance objects that are directly related to the .NET CLR.
The COBOL .NET code in Listing 8-7 will programmatically create a performance counter component. Please examine the code and the included comments before you run the application. This application demonstrates the activity of the GC. By showing how the total allocated heap bytes fluctuate (without manually inducing a garbage collection), you can see that the GC is alive and actively clearing space from the managed heaps.
![]() |
000010 IDENTIFICATION DIVISION. 000020* This is an example of how the CLR 000030* actively performs memory management. 000040 PROGRAM-ID. MAIN. 000050 ENVIRONMENT DIVISION. 000060 CONFIGURATION SECTION. 000070 REPOSITORY. 000080* .NET Framework Classes 000090 CLASS SYS-INT64 AS "System.Int64" 000100 CLASS PERFCOUNTER AS "System.Diagnostics.PerformanceCounter" 000110 PROPERTY PROP-CategoryName AS "CategoryName" 000120 PROPERTY PROP-CounterName AS "CounterName" 000130 PROPERTY PROP-MachineName AS "MachineName" 000140 PROPERTY PROP-InstanceName AS "InstanceName" 000150 PROPERTY PROP-RawValue AS "RawValue". 000160* 000170 DATA DIVISION. 000180 WORKING-STORAGE SECTION. 000190 77 PERFCOUNTER_Obj OBJECT REFERENCE PERFCOUNTER. 000200 77 myLong OBJECT REFERENCE SYS-INT64. 000210 77 My_String PIC X(20). 000220 77 myString1 PIC X(40) VALUE "This is an example of creating Garbage". 000230 77 i PIC S9(9) COMP-5. 000240* Set this variable to the number of times to process the loop 000250 77 maxInt PIC S9(9) COMP-5 VALUE 999. 000260 000270 01 NULL-X PIC X(1). 000280 LINKAGE SECTION. 000290 000300 PROCEDURE DIVISION. 000310 000320* Set Properties of PerformanceCounter Class 000330 INVOKE PERFCOUNTER "NEW" RETURNING PERFCOUNTER_Obj 000340 SET PROP-CategoryName OF PERFCOUNTER_Obj TO ".NET CLR Memory" 000350 SET PROP-CounterName OF PERFCOUNTER_Obj TO "# Bytes in all Heaps" 000360 SET PROP-MachineName OF PERFCOUNTER_Obj TO "." 000370 SET PROP-InstanceName OF PERFCOUNTER_Obj TO "PerfmonCobol" 000380 000390 SET myLong to PROP-RawValue OF PERFCOUNTER_Obj 000400 INITIALIZE My_String 000410 SET My_String to myLong::"ToString" 000420 000430 DISPLAY "Begin Performance Monitor COBOL Example" 000440 DISPLAY " " 000450 DISPLAY "1St Performance Monitor Reading: " My_String 000460 DISPLAY " " 000470 DISPLAY "Warning: This loop will run for a long time." 000480 DISPLAY "!! Depending on the Value of the maxInt variable !! " 000490 DISPLAY "I suggest that you let it run for a while" 000500 DISPLAY "at the same time, you can View the Perfmon Tool info." 000510 DISPLAY "You can either let the loop run and end normally or" 000520 DISPLAY "you can manually terminate the sample application" 000530 DISPLAY "by CLOSING the opened console window." 000540 DISPLAY " " 000550 DISPLAY "Please Prepare your Perfmon window as follows:" 000560 DISPLAY "Performance Object = .NET CLR Memory" 000570 DISPLAY "Counter = # Bytes in all Heaps" 000580 DISPLAY "Machine Name = Local computer" 000590 DISPLAY "Instance Name = PerfmonVB and/or PerfmonCobol" 000600 DISPLAY " " 000610 DISPLAY "Enter X and Press Enter to Resume Sample Application." 000620 ACCEPT NULL-X. 000630 000640 PERFORM VARYING i 000650 FROM 0 BY 1 UNTIL i >= maxInt 000660 MOVE "This String had been modified." TO myString1 000670 SET myLong to PROP-RawValue OF PERFCOUNTER_Obj 000680 INITIALIZE My_String 000690 SET My_String to myLong::"ToString" 000700 Display "Allocated Heap: " My_String 000710 END-PERFORM 000720 000730 DISPLAY "The loop has completed. Enter X and Press Enter to Exit.". 000740 ACCEPT NULL-X. 000750 000760 END PROGRAM MAIN.
![]() |
If possible, please run the application. After it displays hundreds of output lines, the application will complete. Take a moment and scroll through the console window display. It should be obvious at which point the GC performed its collection (at each point where the displayed value decreased).
In the sample code, please notice the approach to "creating" the Performance-Counter object. In object-oriented terms, this is referred to as object instantiation.
In Listing 8-8, the performance counter component has been programmatically created. Please examine the code and the included comments. When you run this application, a stream of lines will be written to the console window. Notice how the RawValue amount fluctuates constantly. Well, this value is showing the total amount of allocated spaces across all of the managed heaps in each generation. Why does it fluctuate so? Because the GC is busily clearing space as the application continues to add space. This demonstrates that garbage collection will occur, even without any manual intervention.
![]() |
'This is an example of how the CLR 'actively performs memory management. Module Module1 Sub Main() Dim i As Int32 '* Set Properties of PerformanceCounter Class Dim myCounter As New System.Diagnostics.PerformanceCounter() With myCounter .CategoryName = ".NET CLR Memory" .CounterName = "# Bytes in all Heaps" .MachineName = "." .InstanceName = "PerfmonVB" End With 'Set this variable to the number of times to process the loop Dim maxInt As Int32 = 999 Console.WriteLine("Begin Performance Monitor VB Example") Console.WriteLine(String.Empty) Console.WriteLine("1St Performance Monitor Reading: " & _ myCounter.RawValue.ToString()) Console.WriteLine(" ") Console.WriteLine("Warning: This loop will run for a long time.") Console.WriteLine("!! Depending on the Value of the maxInt variable !! ") Console.WriteLine("I suggest that you let it run for a while") Console.WriteLine("at the same time, you can View the Perfmon Tool info.") Console.WriteLine("You can either let the loop run and end normally or") Console.WriteLine("you can manually terminate the sample application") Console.WriteLine("by CLOSING the opened console window.") Console.WriteLine(" ") Console.WriteLine("Please Prepare your Perfmon window as follows:") Console.WriteLine("Performance Object = .NET CLR Memory") Console.WriteLine("Counter = # Bytes in all Heaps") Console.WriteLine("Machine Name = Local computer") Console.WriteLine("Instance Name = PerfmonVB and/or PerfmonCobol") Console.WriteLine(" ") Console.WriteLine("Then Press Enter to Resume Sample Application.") Console.ReadLine() Dim myString1 As String = "This is an example of creating Garbage" For i = 0 To maxInt myString1 = "This String had been modified." Console.WriteLine("Allocated Heap: " & _ myCounter.RawValue.ToString()) Next Console.WriteLine("The loop has completed. Press Enter to Exit.") Console.ReadLine() End Sub End Module
![]() |
Please run the VB .NET example application. If you scroll through the console window display, you will notice that the "TOTAL of BYTES in ALL HEAP" value increases and decreases. Each decrease signals that a garbage collection has occurred.
Please notice use of the With and End With language statements in the VB .NET sample code. The use of these statements is considered good coding practice when you need to access multiple properties on one object.
In the sample applications, you have used the System.Diagnostics.PerformanceCounter class. You set a few of its properties. One of those properties is CategoryName.
Now, what will you do when you want to use categories other than the one you used here (".NET CLR Memory")? Given that you have many performance categories to choose from (approximately 66), there has to be a way to find out what choices are available, right? Well, there is. You have a few options:
You can view the MSDN Web references included at the end of this chapter in the "Web Sites" subsection of the "To Learn More" section.
You can view the drop-down menus of the Perfmon GUI tool.
You can write a small program to instantiate the appropriate .NET namespace class: System.Diagnostics.PerformanceCounterCategory . Then, execute the appropriate method (i.e., GetCategories ) to receive an array that contains all of the available performance categories. The following sample code demonstrates this:
'This is an example of how to retrieve 'the Performance Categories Module Module1 Sub Main() Dim myCounterCategories As New _ System.Diagnostics.PerformanceCounterCategory() Dim x As Array = myCounterCategories.GetCategories() For Each myCounterCategories In x Console.WriteLine(myCounterCategories.CategoryName) Next Console.WriteLine("Press Enter to Exit.") Console.ReadLine() End Sub End Module
Note | The preceding code sample is one of a few exceptions where I provide just the VB .NET code. Converting the sample to COBOL .NET would be a good exercise for you to do. |
After running the small sample application GetPerfCategoriesVB, I did a little copying and pasting to include some of the following performance categories, which are directly related to .NET:
.NET CLR Data
.NET CLR Exceptions
.NET CLR Interop
.NET CLR JIT
.NET CLR Loading
.NET CLR LocksAndThreads
.NET CLR Memory
.NET CLR Networking
.NET CLR Remoting
.NET CLR Security
ASP.NET
ASP.NET Applications
ASP.NET Apps v1.0.3705.0
ASP.NET v1.0.3705.0
There will be occasions when you'll want to use some of the other performance categories (ones that don't appear in the preceding list). Feel free to run the GetPerfCategoriesVB application for a complete list.
Tip | For your continued learning, I invite you to experiment with the many other performance objects and counters. I have provided several references related to performance objects and counters in the "To Learn More" section at the end of this chapter. |
Before I get into the fun stuff, please recall that the focus here is the performance concerns of .NET managed code, particularly as it relates to the CLR. Specifically, this section of the book focuses on the GC and its various aspects of memory management. Why am I making this point?
Well, Perfmon has a large set of objects available (a partial list appears in the section "Performance Counter Objects"). About 15 percent (10 of 66) of those objects are directly tied to the .NET CLR. Several more are directly related to other .NET managed code concerns (e.g., ASP.NET, Web services, WMI, and so on). Although most of the remaining Perfmon objects are indirectly related to .NET and the CLR, you can appreciate the fact that Perfmon provides both a granular view and a comprehensive view.
Using the GUI (front-end) that Windows provides to expose these objects, you can select one or more objects, which are referred to as performance objects. Once you select your performance object, you can select one counter, several counters, or all counters listed for that particular performance object. You will typically make two additional choices: choosing an Instance and choosing a Computer. The Instance option provides you the opportunity to see all executing processes or to narrow your view to just one executing process. The Computer Selection option provides you the opportunity to target just your local workstation or other remote servers.
Note | To prepare for the steps that follow, I have modified the two Perfmon sample programs. I have modified the variable maxInt from 999 to 99999. This will cause the loop to run for "a long time." Please locate your two compiled sample code executables (PerfmonCobol.exe and PerfmonVB.exe). These files should be located in the bin subfolder within the folder location of each actual sample application. When you have located the .exe files, double-click them to launch the sample applications. Each sample application was designed to "pause" (remember the block of Display/Console.WriteLine statements?). Leave each sample application paused with the console window open. |
Figure 8-4 shows each sample application launched and paused. Please do this before you proceed to the following Perfmon setup steps.
The following series of figures illustrates the basic steps in starting and setting up Perfmon. Figure 8-5 shows the initial launch of Perfmon.
Figure 8-6 shows how to access the Add counter button. Figure 8-7 shows how to select a .NET CLR-related performance object.
Figure 8-8 shows how to select a specific counter.
As shown in Figure 8-9, I have selected the "Use local computer counters" radio button. Additionally, in the "Select instances from list" box, I have selected both PerfmonCobol and PerfmonVB.
Note | If you do not see the PerfmonCobol and PerfmonVB applications as available choices, launch each sample application by clicking its respective .exe file. |
As shown in Figure 8-9, I have clicked the Explain button. You use this button to get a better understanding about each counter. To complete the Perfmon setup, make sure to click the Add button after you have made all of the selections on this screen. Afterward, simply click the Close button.
Note | Optionally, you can format your Perfmon display (e.g., change the scale, colors, and so on) by pressing Ctrl-Q. If you select View from the toolbar, you can customize your Perfmon window ”for example, you can remove the console tree. Also, you can remove any instance displays (as I have done) that you may not have an immediate interest in. You do this by first selecting the instance in the box where the PerfmonCobol and PerfmonVB instances are displayed and pressing Delete. |
After you start each Perfmon sample application (PerfmonVB and PerfmonCobol) and perform the appropriate setup on the Perfmon tool, you should see a display similar to the one shown in Figure 8-10. You will notice that I have moved the two console windows next to each other with the Performance window sitting on top of them both.
If you have the time, let each application process to completion. It is rather interesting to watch the graphs change (well, more interesting than watching paint dry). I actually found it interesting that the behavior of the GC varied slightly between the two sample applications. One minute, their graphs (reflecting their total bytes allocated across each heap generation) are going up and then down (as the GC performs its collections and heap compactions), almost in unison . Then, occasionally, the VB .NET application will "break out" ahead and grab more storage (having less collected). But then, COBOL .NET (not to be left behind) "catches up" each time.
Note | If you let these sample applications run for a while, you will eventually need to adjust the default scale for the graph (from 0-100 to 0-500, for example). You do this by pressing Ctrl-Q to access the System Monitor Properties page. Next, click the Graph tab. You will see a Vertical Scale section near the bottom portion of the dialog box. To adjust the vertical scale, change the Minimum and Maximum values as needed. |
You will notice that over time, the allocated heap count continues to inch upward as the GC collects less. My theory about this is that perhaps there is a gradual increase in the amount of aged objects that have been promoted to managed heap generation 2. Obviously, you are welcome to modify the Perfmon sample applications as needed to prove or disprove this theory. And as for me? That is fun enough ”for now.
I believe that I've discussed the GC in sufficient depth. Although there's certainly more to discover, you're well equipped for that leg of the journey. The discovery process will be great.
![]() |
You are sitting on top of a gold mine. Well, a gold mine's worth of free samples ” right on your own hard disk. A large number of Microsoft samples are bundled with the Visual Studio .NET (VS .NET) product. It is likely that you installed them when you installed your edition of VS .NET. Although you will not find any COBOL .NET samples there, you will find lots of good VB .NET and C# samples. Remember, you also have a good "stash" of free COBOL .NET samples sitting on your hard disk courtesy of Fujitsu. The locations of these samples, both Microsoft provided and Fujitsu provided, are as follows:
Microsoft: <Hard drive>:\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Samples
Fujitsu: <Hard drive>:\Program Files\Fujitsu NetCOBOL for .NET\Examples
![]() |
[2] Pun intended.
[3] I further discuss ValueType objects in Chapter 9.
[4] This "other heap" is sometimes referred to as the finalization queue .
[5] Some believe that a particular phase of the moon or the velocity of the northern wind has some influence on the GC schedule as well. Well, if a groundhog's vision can influence seasonal weather patterns, anything is possible.