Intern Pool Improvements between Various .NET Framework versions

October 8, 2012

no comments

As you probably know, .NET supports string interning for better memory usage of .NET applications. String literals are automatically interned by the runtime, while any other string can be interned by an explicit call to String.Intern. I will not go into details regarding string interning, if you are not familiar with the concept, you can get started by reading the MSDN documentation for the String.Intern method. In this post, I would like to write about one of the implementation details of the .NET intern pool, specifically, where it is stored and how the storage strategy was changed between recent .NET framework versions.

The following code is a portion from a simple console application that loops endlessly, asks the user how many strings to intern, and interns the requested amount of unique strings (please ignore the possible overflow and other possible bugs, it is just a quick sketch). The application is compiled for framework 4.5, Any CPU (prefer 32 bit is not checked) and is run on a 64bit machine.

   1: public static void Main(string[] args)

   2: {

   3:     int iterationCount = 0;

   4:  

   5:     do

   6:     {

   7:         int numberOfStringsToIntern = ReadUserInput();

   8:  

   9:         Console.WriteLine("Interning {0} Strings", numberOfStringsToIntern);

  10:         string stringPrefix = String.Format("Iteration {0}", ++iterationCount);

  11:         Intern(stringPrefix, numberOfStringsToIntern);

  12:  

  13:     } while (true);

  14: }

  15:  

  16: private static void Intern(string stringPrefix, int numberOfStringsToIntern)

  17: {

  18:     for (int stringIndex = 0; stringIndex < numberOfStringsToIntern; ++stringIndex)

  19:     {

  20:         string str = String.Format("{0} - {1}", stringPrefix, stringIndex);

  21:         String.Intern(str);

  22:     }

  23: }

To see what happens when a string is interned, we will launch the application under WinDbg, and will place a breakpoint just before the call to String.Intern (line 21 above).

   1: 0:000> .loadby sos clr

   2: 0:000> !bpmd Program.cs:48

   3: Found 1 methods in module 000007fbdcc02f90...

   4: MethodDesc = 000007fbdcc03938

   5: Adding pending breakpoints...

   6: 0:000> g

When the debugger stops, the str variable should be on the stack, let’s find it and inspect who is holding a reference to it.

   1: 0:000> !dso

   2: OS Thread Id: 0xdd8 (0)

   3: RSP/REG          Object           Name

   4: rax              000000e78ca47660 System.String    Iteration 1 - 0

   5: rbx              000000e78ca475c8 System.String    {0} - {1}

   6: rdx              000000e78ca45540 System.Text.StringBuilder

   7: rbp              000000e78ca47598 System.String    Iteration 1

   8: 000000E78AA2E9B8 000000e78ca47450 System.Int32

   9: 000000E78AA2E9D8 000000e78ca47450 System.Int32

  10: 000000E78AA2E9E0 000000e78ca42de8 System.String    Iteration {0}

  11: 000000E78AA2E9F0 000000e78ca47598 System.String    Iteration 1

  12: 000000E78AA2EA50 000000e78ca42d80 System.Object[]    (System.String[])

  13: 000000E78AA2EB88 000000e78ca42d80 System.Object[]    (System.String[])

  14: 000000E78AA2ECA8 000000e78ca42d80 System.Object[]    (System.String[])

  15: 000000E78AA2ECB0 000000e78ca42208 System.Security.Policy.Evidence

  16: 000000E78AA2EE68 000000e78ca42d80 System.Object[]    (System.String[])

  17:  

  18: 0:000> !gcroot 000000e78ca47660

  19: Found 0 unique roots (run '!GCRoot -all' to see all roots).

As we can see, prior to being interned, the string has no other roots except the rax register, and will be collected on the first garbage collection that will take place after the return from the method. Inspecting the roots again, after the call to String.Intern, will produce the following output:

   1: 0:005> !gcroot 000000e78ca47660

   2: HandleTable:

   3:     000000e78ab817e8 (pinned handle)

   4:     -> 000000e79ca43238 System.Object[]

   5:     -> 000000e78ca47660 System.String

   6:  

   7: Found 1 unique roots (run '!GCRoot -all' to see all roots).

This time, the string is being referenced by an array of objects. What other data this array contains?

   1: 0:005> !da -details -nofields 000000e79ca43238

   2: Name:        System.Object[]

   3: MethodTable: 000007fc3b48f1b8

   4: EEClass:     000007fc3aec1858

   5: Size:        1056(0x420) bytes

   6: Array:       Rank 1, Number of elements 128, Type CLASS

   7: Element Methodtable: 000007fc3b4ab4c0

   8: [0] 000000e78ca41420

   9:     Name:        System.String

  10:     MethodTable: 000007fc3b4aaee0

  11:     EEClass:     000007fc3ae13720

  12:     Size:        26(0x1a) bytes

  13:     File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

  14:     String:              

  15: [1] 000000e78ca42da0

  16:     Name:        System.String

  17:     MethodTable: 000007fc3b4aaee0

  18:     EEClass:     000007fc3ae13720

  19:     Size:        68(0x44) bytes

  20:     File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

  21:     String:          Interning {0} Strings    

  22: [2] 000000e78ca42de8

  23:     Name:        System.String

  24:     MethodTable: 000007fc3b4aaee0

  25:     EEClass:     000007fc3ae13720

  26:     Size:        52(0x34) bytes

  27:     File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

  28:     String:          Iteration {0}    

  29: [3] 000000e78ca42ee0

  30:     Name:        System.String

  31:     MethodTable: 000007fc3b4aaee0

  32:     EEClass:     000007fc3ae13720

  33:     Size:        138(0x8a) bytes

  34:     File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

  35:     String:          How many strings do you want to intern (Ctrl+C To Exit)?    

  36: [4] 000000e78ca475c8

  37:     Name:        System.String

  38:     MethodTable: 000007fc3b4aaee0

  39:     EEClass:     000007fc3ae13720

  40:     Size:        44(0x2c) bytes

  41:     File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

  42:     String:          {0} - {1}    

  43: [5] 000000e78ca47660

  44:     Name:        System.String

  45:     MethodTable: 000007fc3b4aaee0

  46:     EEClass:     000007fc3ae13720

  47:     Size:        56(0x38) bytes

  48:     File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll

  49:     String:          Iteration 1 - 0    

  50: [6] null

  51: [7] null

  52: ...

  53: [126] null

  54: [127] null

By inspecting the content of the array, we can clearly see that it can hold up to 128 elements and it currently contains all the interned strings of our application. Element 0 is String.Empty, elements 1-4 are the literal strings of the application, that were automatically interned by the runtime, and element 5 is the newly interned string. The array at which we are looking is actually a part of the intern pool. Where is it stored?

   1: 0:005> !gcwhere 000000e79ca43238

   2: Address    Gen   Heap   segment    begin      allocated   size

   3: 000000e79ca43238   3      0     000000e79ca40000   000000e79ca41000   000000e79ca49638    0x420(1056)

   4: 0:005> !eeheap -gc

   5: Number of GC Heaps: 1

   6: generation 0 starts at 0x000000e78ca41030

   7: generation 1 starts at 0x000000e78ca41018

   8: generation 2 starts at 0x000000e78ca41000

   9: ephemeral segment allocation context: none

  10:  segment     begin allocated  size

  11: 000000e78ca40000  000000e78ca41000  000000e78ca47fe8  0x6fe8(28648)

  12: Large object heap starts at 0x000000e79ca41000

  13:  segment     begin allocated  size

  14: 000000e79ca40000  000000e79ca41000  000000e79ca49638  0x8638(34360)

  15: Total Size:              Size: 0xf620 (63008) bytes.

  16: ------------------------------

  17: GC Heap Size:            Size: 0xf620 (63008) bytes.

  18:  

  19: 0:005> !dumpheap 000000e79ca41000  000000e79ca49638  

  20:          Address               MT     Size

  21: 000000e79ca41000 000000e78abe9e80       24 Free

  22: 000000e79ca41018 000007fc3b48f1b8     8736     

  23: 000000e79ca43238 000007fc3b48f1b8     1056     

  24: 000000e79ca43658 000007fc3b48f1b8     8192     

  25: 000000e79ca45658 000007fc3b48f1b8    16352     

  26:  

  27: Statistics:

  28:               MT    Count    TotalSize Class Name

  29: 000000e78abe9e80        1           24      Free

  30: 000007fc3b48f1b8        4        34336 System.Object[]

  31: Total 5 objects

As we can see in the above output, the array is stored in the Large Object Heap (we can see it by looking at its address, which is within the bounds of the LOH). How is it possible, given that the minimum allowed size is 85000 bytes? It turns out that the characteristics of the LOH (no compaction, collected only in full GC), makes it a perfect place for the runtime to store some of its frequently-used, long-lived objects, including the intern pool. (From the above output, you can see that there are 3 more arrays, stored there by the runtime, you can dump them to see the various objects they contain).

A quick summary before moving on, to the more interesting parts:

  • After a string is interned, it is being referenced by a special array, to prevent it from being garbage collected.
  • This array is pinned by the runtime, and will probably live for the entire lifetime of the application.
  • Because of the above, the ideal place for it to be allocated is on the LOH.
  • This array can hold up to 128 elements.

What happens when more than 128 strings are interned? It depends in which .NET Framework we are using. To emphasize the differences between the different .NET framework versions, I used the above application, to intern an increasing amount of strings, and check the state of the LOH after each step.

Intern Pool in .NET 3.5

The following is a snapshot of the LOH before any explicit interning was made:

   1: 0:003> .loadby sos mscorwks

   2: 0:003> !eeheap -gc

   3: Number of GC Heaps: 1

   4: generation 0 starts at 0x0000000002881030

   5: generation 1 starts at 0x0000000002881018

   6: generation 2 starts at 0x0000000002881000

   7: ephemeral segment allocation context: none

   8:          segment            begin         allocated             size

   9: 0000000002880000 0000000002881000  0000000002935fe8 0x00000000000b4fe8(741352)

  10: Large object heap starts at 0x0000000012881000

  11:          segment            begin         allocated             size

  12: 0000000012880000 0000000012881000  0000000012887048 0x0000000000006048(24648)

  13: Total Size           0xbb030(766000)

  14: ------------------------------

  15: GC Heap Size           0xbb030(766000)

  16:  

  17: 0:003> !dumpheap 0000000012881000  0000000012887048

  18:          Address               MT     Size

  19: 0000000012881000 000000000043c000       24 Free

  20: 0000000012881018 000007feeaca5a08     8192     

  21: 0000000012883018 000000000043c000       24 Free

  22: 0000000012883030 000007feeaca5a08     1056     

  23: 0000000012883450 000000000043c000     7136 Free

  24: 0000000012885030 000007feeaca5a08     8192     

  25: 0000000012887030 000000000043c000       24 Free

  26: total 7 objects

  27: Statistics:

  28:               MT    Count    TotalSize Class Name

  29: 000000000043c000        4         7208      Free

  30: 000007feeaca5a08        3        17440 System.Object[]

  31: Total 7 objects

As we can see, the runtime pre allocated a single, 128 elements, object array (the 1056 bytes object at line 22) to store the interned strings (the other two arrays are the general purpose arrays described above. Notice that the number and size of those arrays is different than in framework 4.5). At this stage, the intern pool’s array contains only the 4 literal strings which were automatically interned, and the String.Empty reference.

Let’s take another look at the LOH, this time, after interning 200 strings:

   1: 0:003> !eeheap -gc

   2: Number of GC Heaps: 1

   3: generation 0 starts at 0x0000000002881030

   4: generation 1 starts at 0x0000000002881018

   5: generation 2 starts at 0x0000000002881000

   6: ephemeral segment allocation context: none

   7:          segment            begin         allocated             size

   8: 0000000002880000 0000000002881000  0000000002945fe8 0x00000000000c4fe8(806888)

   9: Large object heap starts at 0x0000000012881000

  10:          segment            begin         allocated             size

  11: 0000000012880000 0000000012881000  0000000012889048 0x0000000000008048(32840)

  12: Total Size           0xcd030(839728)

  13: ------------------------------

  14: GC Heap Size           0xcd030(839728)

  15:  

  16: 0:003> !dumpheap 0000000012881000  0000000012889048

  17:          Address               MT     Size

  18: 0000000012881000 000000000043c000       24 Free

  19: 0000000012881018 000007feeaca5a08     8192     

  20: 0000000012883018 000000000043c000       24 Free

  21: 0000000012883030 000007feeaca5a08     1056     

  22: 0000000012883450 000000000043c000     7136 Free

  23: 0000000012885030 000007feeaca5a08     8192     

  24: 0000000012887030 000000000043c000       24 Free

  25: 0000000012887048 000007feeaca5a08     1056     

  26: 0000000012887468 000000000043c000     7136 Free

  27: total 9 objects

  28: Statistics:

  29:               MT    Count    TotalSize Class Name

  30: 000000000043c000        5        14344      Free

  31: 000007feeaca5a08        4        18496 System.Object[]

  32: Total 9 objects

After running out of space in the original 128 elements array, the runtime allocated another array to hold additional 128 interned strings (line 25). The array was allocated as part of an 8KB block of memory (memory alignment, I guess), the extra 7136 bytes were marked as free.

Finally, let’s take a look at the LOH after interning another 4000 strings:

   1: 0:003> !eeheap -gc

   2: Number of GC Heaps: 1

   3: generation 0 starts at 0x0000000002881030

   4: generation 1 starts at 0x0000000002881018

   5: generation 2 starts at 0x0000000002881000

   6: ephemeral segment allocation context: none

   7:          segment            begin         allocated             size

   8: 0000000002880000 0000000002881000  0000000002a53fe8 0x00000000001d2fe8(1912808)

   9: Large object heap starts at 0x0000000012881000

  10:          segment            begin         allocated             size

  11: 0000000012880000 0000000012881000  00000000128c5fe8 0x0000000000044fe8(282600)

  12: Total Size          0x217fd0(2195408)

  13: ------------------------------

  14: GC Heap Size          0x217fd0(2195408)

  15:  

  16: 0:003> !dumpheap 0000000012881000  00000000128c5fe8

  17:          Address               MT     Size

  18: 0000000012881000 000000000043c000       24 Free

  19: 0000000012881018 000007feeaca5a08     8192     

  20: 0000000012883018 000000000043c000       24 Free

  21: 0000000012883030 000007feeaca5a08     1056     

  22: 0000000012883450 000000000043c000     7136 Free

  23: 0000000012885030 000007feeaca5a08     8192     

  24: 0000000012887030 000000000043c000       24 Free

  25: 0000000012887048 000007feeaca5a08     1056     

  26: 0000000012887468 000000000043c000     7136 Free

  27: 0000000012889048 000007feeaca5a08     1056     

  28: 0000000012889468 000000000043c000     7136 Free

  29: 000000001288b048 000007feeaca5a08     1056     

  30: 000000001288b468 000000000043c000     7136 Free

  31:

  32: 00000000128bffe8 000007feeaca5a08     1056     

  33: 00000000128c0408 000000000043c000     7136 Free

  34: 00000000128c1fe8 000007feeaca5a08     1056     

  35: 00000000128c2408 000000000043c000     7136 Free

  36: 00000000128c3fe8 000007feeaca5a08     1056     

  37: 00000000128c4408 000000000043c000     7136 Free

  38: total 71 objects

  39: Statistics:

  40:               MT    Count    TotalSize Class Name

  41: 000007feeaca5a08       35        51232 System.Object[]

  42: 000000000043c000       36       231368      Free

  43: Total 71 objects

Now it’s getting interesting! Whenever the intern pool runs out of space, another 128 elements array is allocated, to store the additional strings. As before, we can see that a larger memory block is allocated, exactly 8KB in this case, from which only 1056 bytes are used. The additional 7136 bytes are marked as free, and will forever remain free, because no other object will ever be able to fit inside, as only objects larger than 85000 bytes are allocated on the LOH. For each 128 interned strings, we lose 7136 bytes of memory. That’s 100MB for under 2,000,000 strings.

I can think of more than one scenario in which an application will have more than that number of interned strings. For some applications, string literals alone can take thousands of places in the pool. It’s a shame that all this memory will go to waste. Luckily, as we will see, this is no longer an issue on .NET framework 4.0.

Intern Pool in .NET 4.0

The following is a snapshot of the LOH before any explicit interning was made:

   1: 0:004> .loadby sos clr

   2: 0:004> !eeheap -gc

   3: Number of GC Heaps: 1

   4: generation 0 starts at 0x00000000023e1030

   5: generation 1 starts at 0x00000000023e1018

   6: generation 2 starts at 0x00000000023e1000

   7: ephemeral segment allocation context: none

   8:          segment             begin         allocated  size

   9: 00000000023e0000  00000000023e1000  00000000023f3fe8  0x12fe8(77800)

  10: Large object heap starts at 0x00000000123e1000

  11:          segment             begin         allocated  size

  12: 00000000123e0000  00000000123e1000  00000000123e7048  0x6048(24648)

  13: Total Size:              Size: 0x19030 (102448) bytes.

  14: ------------------------------

  15: GC Heap Size:    Size: 0x19030 (102448) bytes.

  16:  

  17: 0:004> !dumpheap 00000000123e1000  00000000123e7048

  18:          Address               MT     Size

  19: 00000000123e1000 000000000010bab0       24 Free

  20: 00000000123e1018 000007feed12ae88     8192     

  21: 00000000123e3018 000000000010bab0       24 Free

  22: 00000000123e3030 000007feed12ae88     1056     

  23: 00000000123e3450 000000000010bab0     7136 Free

  24: 00000000123e5030 000007feed12ae88     8192     

  25: 00000000123e7030 000000000010bab0       24 Free

  26: total 0 objects

  27: Statistics:

  28:               MT    Count    TotalSize Class Name

  29: 000000000010bab0        4         7208      Free

  30: 000007feed12ae88        3        17440 System.Object[]

  31: Total 7 objects

The initial layout of the LOH stayed the same as it was on .NET framework 3.5. Let’s intern 200 strings and see what happens:

   1: 0:007> !eeheap -gc

   2: Number of GC Heaps: 1

   3: generation 0 starts at 0x00000000023e1030

   4: generation 1 starts at 0x00000000023e1018

   5: generation 2 starts at 0x00000000023e1000

   6: ephemeral segment allocation context: none

   7:          segment             begin         allocated  size

   8: 00000000023e0000  00000000023e1000  0000000002403fe8  0x22fe8(143336)

   9: Large object heap starts at 0x00000000123e1000

  10:          segment             begin         allocated  size

  11: 00000000123e0000  00000000123e1000  00000000123e9048  0x8048(32840)

  12: Total Size:              Size: 0x2b030 (176176) bytes.

  13: ------------------------------

  14: GC Heap Size:    Size: 0x2b030 (176176) bytes.

  15:  

  16: 0:007> !dumpheap 00000000123e1000  00000000123e9048

  17:          Address               MT     Size

  18: 00000000123e1000 000000000010bab0       24 Free

  19: 00000000123e1018 000007feed12ae88     8192     

  20: 00000000123e3018 000000000010bab0       24 Free

  21: 00000000123e3030 000007feed12ae88     1056     

  22: 00000000123e3450 000000000010bab0     7136 Free

  23: 00000000123e5030 000007feed12ae88     8192     

  24: 00000000123e7030 000000000010bab0       24 Free

  25: 00000000123e7048 000007feed12ae88     2080     

  26: 00000000123e7868 000000000010bab0     6112 Free

  27: total 0 objects

  28: Statistics:

  29:               MT    Count    TotalSize Class Name

  30: 000000000010bab0        5        13320      Free

  31: 000007feed12ae88        4        19520 System.Object[]

  32: Total 9 objects

Unlike in .NET framework 3.5, after running out of space in the initial, 128 elements array (line 21), the runtime has allocated an additional array (line 25) with 256 elements, double the size of the first one. Let’s take a look at the LOH after interning another 40000 strings:

   1: 0:004> !eeheap -gc

   2: Number of GC Heaps: 1

   3: generation 0 starts at 0x0000000002673778

   4: generation 1 starts at 0x000000000248e028

   5: generation 2 starts at 0x00000000023e1000

   6: ephemeral segment allocation context: none

   7:          segment             begin         allocated  size

   8: 00000000023e0000  00000000023e1000  0000000002a39790  0x658790(6653840)

   9: Large object heap starts at 0x00000000123e1000

  10:          segment             begin         allocated  size

  11: 00000000123e0000  00000000123e1000  0000000012469170  0x88170(557424)

  12: Total Size:              Size: 0x6e0900 (7211264) bytes.

  13: ------------------------------

  14: GC Heap Size:    Size: 0x6e0900 (7211264) bytes.

  15:  

  16: 0:004> !dumpheap 00000000123e1000  0000000012469170

  17:          Address               MT     Size

  18: 00000000123e1000 000000000010bab0       24 Free

  19: 00000000123e1018 000007feed12ae88     8192     

  20: 00000000123e3018 000000000010bab0       24 Free

  21: 00000000123e3030 000007feed12ae88     1056     

  22: 00000000123e3450 000000000010bab0     7136 Free

  23: 00000000123e5030 000007feed12ae88     8192     

  24: 00000000123e7030 000000000010bab0       24 Free

  25: 00000000123e7048 000007feed12ae88     2080     

  26: 00000000123e7868 000000000010bab0     6112 Free

  27: 00000000123e9048 000007feed12ae88     4128     

  28: 00000000123ea068 000000000010bab0     4064 Free

  29: 00000000123eb048 000007feed12ae88     8224     

  30: 00000000123ed068 000000000010bab0       24 Free

  31: 00000000123ed080 000007feed12ae88    16416     

  32: 00000000123f10a0 000000000010bab0       24 Free

  33: 00000000123f10b8 000007feed12ae88    32800     

  34: 00000000123f90d8 000000000010bab0       24 Free

  35: 00000000123f90f0 000007feed12ae88    65568     

  36: 0000000012409110 000000000010bab0       24 Free

  37: 0000000012409128 000007feed12ae88   131072     

  38: 0000000012429128 000000000010bab0       24 Free

  39: 0000000012429140 000007feed12ae88   131072     

  40: 0000000012449140 000000000010bab0       24 Free

  41: 0000000012449158 000007feed12ae88   131072     

  42: 0000000012469158 000000000010bab0       24 Free

  43: total 0 objects

  44: Statistics:

  45:               MT    Count    TotalSize Class Name

  46: 000000000010bab0       13        17552      Free

  47: 000007feed12ae88       12       539872 System.Object[]

  48: Total 25 objects

The output confirms that the intern pool allocation algorithm was changed to address the fragmentation problem that was in .NET framework 3.5 by doubling the size of each additional array (up to 16384 elements), and by that, reducing the amount of wasted memory.

It seems that the memory is still allocated in blocks of 8KB, which is not very economical for the small arrays. After the first array we have 7136 free bytes, after the second we have 6112 free bytes and after the third we have 4064 free bytes. All of that memory will go to waste.

One more thing that we can see is those 24 byte blocks of free memory that acts as a minimum boundary between each two objects allocated on the LOH.

Intern Pool in .NET 4.5

First thing first, the LOH before any explicit interning was made:

   1: 0:004> .loadby sos clr

   2: 0:004> !eeheap -gc

   3: Number of GC Heaps: 1

   4: generation 0 starts at 0x000000db02821030

   5: generation 1 starts at 0x000000db02821018

   6: generation 2 starts at 0x000000db02821000

   7: ephemeral segment allocation context: none

   8:  segment     begin allocated  size

   9: 000000db02820000  000000db02821000  000000db02827fe8  0x6fe8(28648)

  10: Large object heap starts at 0x000000db12821000

  11:  segment     begin allocated  size

  12: 000000db12820000  000000db12821000  000000db12829638  0x8638(34360)

  13: Total Size:              Size: 0xf620 (63008) bytes.

  14: ------------------------------

  15: GC Heap Size:            Size: 0xf620 (63008) bytes.

  16:  

  17: 0:004> !dumpheap 000000db12821000  000000db12829638

  18:          Address               MT     Size

  19: 000000db12821000 000000db00bea240       24 Free

  20: 000000db12821018 000007fc3b48f1b8     8736     

  21: 000000db12823238 000007fc3b48f1b8     1056     

  22: 000000db12823658 000007fc3b48f1b8     8192     

  23: 000000db12825658 000007fc3b48f1b8    16352     

  24:  

  25: Statistics:

  26:               MT    Count    TotalSize Class Name

  27: 000000db00bea240        1           24      Free

  28: 000007fc3b48f1b8        4        34336 System.Object[]

  29: Total 5 objects

Nothing has changed regarding the initial allocation of the intern pool. Like in the two previous .NET framework versions, the initial array can hold up to 128 strings. Two other things were changed. The first is the size and amount of general purpose arrays and the second is that we no longer see the 24 bytes of free space between each two elements. Let’s take a final look at the LOH, after interning 40000 strings:

   1: 0:003> !eeheap -gc

   2: Number of GC Heaps: 1

   3: generation 0 starts at 0x000000db0299aba8

   4: generation 1 starts at 0x000000db02821018

   5: generation 2 starts at 0x000000db02821000

   6: ephemeral segment allocation context: none

   7:  segment     begin allocated  size

   8: 000000db02820000  000000db02821000  000000db02eb5fe8  0x694fe8(6901736)

   9: Large object heap starts at 0x000000db12821000

  10:  segment     begin allocated  size

  11: 000000db12820000  000000db12821000  000000db128a8ef8  0x87ef8(556792)

  12: Total Size:              Size: 0x71cee0 (7458528) bytes.

  13: ------------------------------

  14: GC Heap Size:            Size: 0x71cee0 (7458528) bytes.

  15:  

  16: 0:003> !dumpheap 000000db12821000  000000db128a8ef8

  17:          Address               MT     Size

  18: 000000db12821000 000000db00bea240       24 Free

  19: 000000db12821018 000007fc3b48f1b8     8736     

  20: 000000db12823238 000007fc3b48f1b8     1056     

  21: 000000db12823658 000007fc3b48f1b8     8192     

  22: 000000db12825658 000007fc3b48f1b8    16352     

  23: 000000db12829638 000007fc3b48f1b8     2080     

  24: 000000db12829e58 000007fc3b48f1b8     4128     

  25: 000000db1282ae78 000007fc3b48f1b8     8224     

  26: 000000db1282ce98 000007fc3b48f1b8    16416     

  27: 000000db12830eb8 000007fc3b48f1b8    32800     

  28: 000000db12838ed8 000007fc3b48f1b8    65568     

  29: 000000db12848ef8 000007fc3b48f1b8   131072     

  30: 000000db12868ef8 000007fc3b48f1b8   131072     

  31: 000000db12888ef8 000007fc3b48f1b8   131072     

  32:  

  33: Statistics:

  34:               MT    Count    TotalSize Class Name

  35: 000000db00bea240        1           24      Free

  36: 000007fc3b48f1b8       13       556768 System.Object[]

  37: Total 14 objects

Although not directly related to the intern pool allocation algorithm, we can clearly see that additional memory was saved by eliminating the 24 bytes of free space between each two elements. In fact, there is no free space at all between the elements. Maybe that is one of the improvements mentioned here and here.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*