.NET Memory Analysis Enhancements in Visual Studio 2013

.NET Memory Analysis Enhancements in Visual Studio 2013

Rate This
  • Comments 1

In my previous post about the new .NET memory analysis feature in Visual Studio, I discussed this new feature we have added to Visual Studio 2013 Ultimate targeted at helping to solve memory problems that occur in .NET applications running in production environments – I encourage you to read that post first

Now with the RTM version of Visual Studio 2013 we’ve added some great new features to the tool to help make it even more useful for diagnosing issues:

  • Inclusive Size calculation for objects
  • Just My Code and Collapse small objects
  • References View for objects
  • Static variable roots are shown by name in the Paths to Root view

In this post I’ll discuss what those features are, and how they can help you be more productive in your memory investigations. First let’s examine the sample data that I will be using, and then the four new features.

Sample Data

For the purposes of this post, I’ll reuse the same dumps I created in the previous post’s “Collecting the data” section.

First I’m going to open the second file created (iisexpress2.dmp) which will give me the following initial view:

dump2-summary

There are a few things that you may immediately notice:

  • There is a big info icon with a message stating “View Settings have filtered some object types (Just My Code, Collapse Small Objects).
  • There are only six rows in the summary table (this is because the “View Settings” have filtered out most of the objects that are most likely non-consequential to my memory problem).
  • The top two items in the list are List<SampleLeak.Models.User> and SampleLeak.Models.User (in contrast the top two types in the previous post are System.Byte[] and System.String).

Inclusive Size

The reason that List<SampleLeak.Models.User> and SampleLeak.Models.User appear at the top of the table is these types have the largest size of all the types in memory. Inclusive size is the aggregate size of the objects of the type you are looking at, plus the size of all of their child objects (the objects they are holding alive in memory). The idea behind inclusive size, is if you were to garbage collect that object this would be the total amount of memory that would be reclaimed. Since at a high level when you are investigating memory you are trying to determine “what is using or holding onto so much memory” we have added an inclusive size column and sort the summary table by inclusive size by default.

To better understand how inclusive size is calculated let’s look at a simple example. Given the following object graph.

Simple-RefGraph

The memory tool will produce the following table:

Object Type

Count

Size (Bytes)

Inclusive Size (Bytes)

Customer

1

100

400

Address

1

50

200

String

2

150

150

Byte[]

1

100

100

Notice that the inclusive size of Address is the size of the Address plus the size of the String it is referencing plus the size of the Byte[] it is referencing. The inclusive size of Customer is the size of Customer plus the size of the String it is referencing, plus the inclusive size of Address.

Additionally, as you would expect if you compare two dumps, there is an “Inclusive Size Diff” column so you can see how much the inclusive size of a type has changed.

inclusive-size-diff

View Settings

As I mentioned above there are two new view settings that are intended to reduce the noise in the summary table.

  1. Just My Code attempts to help hide types that generally appear in such large numbers that on their own they are too common to be interesting or types that generally created by the runtime and not under the control of user code so are not likely to be interesting to your investigation.
  2. Collapse Small Objects is designed to reduce noise by hiding any types that have an inclusive size of less than 0.5% of the total size of the managed heap.

The first term that needs to be defined is what it means to “hide” types. When a type is “hidden” the memory tool adds the size of an object of that type to the size of the object referencing it (its parent) and then removes it from the list of displayed types (generally called “folding” in profiling tools).

Folding Explained

The concept of folding can be confusing, so let’s look at an example. I’ll start with the same object graph I used above to explain how inclusive size is calculated

Simple-RefGraph

However in this case we will fold Strings and Byte[]’s, which will cause the object graph to appear as

Folded-RefGraph

And the summary table will change to

Object Type

Count

Size (Bytes)

Inclusive Size (Bytes)

Customer

1

200

400

Address

1

200

200

It is important to note that folding is applied at the type level. Meaning that if the type of String is folded, every String in the heap is folded, folding is not selectively applied where some Strings are folded and other Strings are left unfolded.

Digging Into Just My Code

Now that you understand how folding works, we can discuss how Just My Code is implemented. As I mentioned, our goal was to help reduce noise by folding away objects that are so common they aren’t likely a meaningful place to begin your memory investigation. For example, in almost any .NET application both String and Byte[]’s will be two of the most common types in the heap. What you really need to understand is what is referencing those types and therefore ultimately responsible for those being kept alive in memory. So, since they are unlikely to be an interesting place to begin your investigation Just My Code removes them from the table.

What Just My Code folds is controlled by regular expressions. The default set of regular expressions are as follow:

System\.String$
System\.Object$
Microsoft\..*Runtime;System\._
System\..*Metadata;
System\..*Entry
System\.Reflection
\[\]

The one thing you might notice above is that other than System and Microsoft objects being included in this, all arrays will be folded away. The reason we chose to go with this approach by default is that while those arrays may represent large amounts of memory, arrays are basically “double counting” the memory when we calculate inclusive size because they are not large in themselves, the instances they are holding onto are what take most of the memory .

For example, in the dump file we are using in this example, the List<SampleLeak.Models.User> creates a SampleLeak.Models.User[] as part of the List<> implementation which holds the instances of SampleLeak.Models.User. Since we know the aggregate size of the SampleLeak.Models.User instances there is no need to also show the SampleLeak.Models.User[].

Configuring View Settings

Both Just My Code and Collapse Small Objects can be enabled or disabled from the View Settings dropdown in the top right part of the window.

view-settings

It is worth noting that these are global settings to Visual Studio, meaning:

  • Just My Code is the same Just My Code setting you use while debugging (Tools -> Options -> Debugging | Enable Just My Code), so enabling or disabling it in the Heap View window will change the global setting for all of your debug sessions until you change it again
  • Collapse Small Objects only affects the Debug Managed Memory view, but when you set or unset it, that will persist until you change it again.

Currently neither the defaults for what is folded by Just My Code nor the threshold for Collapse Small Objects are configurable in Visual Studio, but if you are interested in tweaking them they are controlled by registry keys under HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\12.0_Config\ManagedMemoryAnalysis

Just My Code is a semi-colon delimited list of regular expressions contained in …\ManagedMemoryAnalysis\FoldPaths, and the value for the Collapse Small Objects threshold is contained in …\ManagedMemoryAnalysis\SmallObjectFoldPercentage

Additionally I mentioned in the previous post that we only show the 10 largest instances of a type, we have now made that number configurable using the registry key …\ManagedMemoryAnalysis\NumObjectsPerType

References View

The final enhancement we have added is a References view that compliments the Paths to Root View. Where the Paths to Root view shows the references to this type keeping it from being garbage collected, the References view shows what this object is referencing. This helps to answer the question of “what is contributing to the inclusive size of this type”.

To see this in action, let’s go back to the example dump file we have open. I’ll select the List<SampleLeak.Models.User> row since that has the largest inclusive size of any of my types. By default I see the Paths to Root view in the bottom part of the window, but I also see a tab for the new References view. Once I select that, I can see that SampleLeak.Models.User[] is the only type being referenced by List<SampleLeak.Models.User>, but when I expand that node I see that it is referencing 16 SampleLeak.Models.User objects, expanding that row shows me that the SampleLeak.Models.User objects are referencing 16 total Byte[]’s and 32 total Strings, and the majority of that memory is being used by the Byte[]’s.

The power of this view is that to determine why each SampleLeak.Models.User is holding onto so much memory all I have to do is follow the references “down”. Where if I only had the Paths to Root view, I would have to see that Byte[]’s were occupying a large amount of memory and then look at their Paths to Root to see that they were being reverenced by SampleLeak.Models.User objects, but that still wouldn’t account for the Strings also being referenced.

referenced objects

At this point it is worth noting the blue “info” icon that appears on the “Object Type” column. Hovering over it will give me a message that “View settings are not applied in the referenced objects view”. This is why I’m seeing the SampleLeak.Models.User[], Byte[], and String even though Just My Code is currently enabled. The reason that we made this decision is that while we think the view filtering settings are useful for helping you focus on problem objects in the summary table, when you want to answer the question of “what is this object holding onto” it may be very important to see these types in the context of what an object is referencing. For example, in the screenshot above I can see that the majority of the memory is being used by Byte[], if Just My Code was applied to this view it would be misleading me because it would appear that the SampleLeak.Models.User was a large object on its own.

The only slight oddity about this is because the cost of Byte[] has been folded into SampleLeak.Models.User you still see that the size of SampleLeak.Model.User as it appears with the folding enabled (i.e. it still shows as 16,385,984 even though the 16 user objects are actually only 320 bytes on their own). We appreciate your feedback if you have any about the decisions we have made as described above.

Static Variables

The final improvement I would like to call out is that when an object is rooted in memory by a static variable, we now show the name of that variable in the Object Type column. To see this in action, I’m going to select the List<SampleLeak.Models.User> row in the type summary table at the top. When I expand the Paths to Root for the List<SampleLeak.Models.User> I can see that where the type of root is shown inside the square brackets is tells me this root is the static variable SampleLeak.Data.UserRepository.m_userCache.

Static-Variable

With this information, I know exactly which static in my code is responsible for rooting these objects and preventing them from being garbage collected.

In Closing

In summary, in this post (and in part 1) we looked at the new feature enhancements to the .NET memory analysis tool for dump files in Visual Studio Ultimate. You may also want to watch the Visual Studio Toolbox episode where I walk through using this tool to solve a memory problem.  Finally check out the instance value inspection capabilities we added in Visual Studio 2013 Update 2.

I hope that you find these features useful as you investigate memory problems, and if you have any comments/questions I’d love to hear them in the comments below or in our MSDN forum.

Leave a Comment
  • Please add 3 and 5 and type the answer here:
  • Post
  • Difficult to make a business case for the $100,000+ cost to upgrade our three large enterprise applications from VS 2012 to 2013 given the lack of forward support for VS 2012 except for minor bug fixes.  Making a business case for VS 2013 stops with a 'How do we know that the upgrade cost will not lead back the same situation when the next major VS release comes out in 9-12 months?'   VS major release cycle time is 50% the time between major releases of our enterprise applications, or for clarity, core financial applications need significantly longer QA validation and development cycle times given each release is audited.

Page 1 of 1 (1 items)