3

I've got a non recycling cursor retrieving features into a List<IFeature>.

When I populate the list, I can see my memory usage climbing up. When I discard my list (or it goes out of scope), the memory doesn't go down again, even when explicitly calling the garbage collector.

I've tried looping through the list and calling ReleaseComObject on each IFeature, also to no avail.

This is an issue, as my app does this whenever a given user action is performed, and the memory usage just keeps climbing, till there's no memory left on the machine and it crashes.

This is driving me nuts. Any ideas?

Following further investigation, it appears as though while there is a tiny amount of unmanaged memory not being released it's not enough to worry about, and it's not accumulating. It appears to be a one off hit (no change when running the test through a loop). For completeness, I'll include my test code here, for anyone else having issues. It's a slightly modified version of Kirk's, also showing total process memory (not just managed, as the IFeature data sits in unmanaged memory). The end result is that there must be something else hanging onto the features in my code, so will have to look elsewhere.

My understanding of the IRow (and IFeature) is that they're singletons, so even if I nullify the managed reference to a feature, if any other reference exists, it won't release the unmanaged memory. As a result you need to be very careful in ensuring all references are removed. I hope my pain helps someone else.

    public static void RunTest( string fgdbPath, string className)
    {
        var ws = (IFeatureWorkspace)OpenFromPath(fgdbPath);
        var fc = ws.OpenFeatureClass(className);

        long managedMem1 = GC.GetTotalMemory(true);
        long unmanagedMem1 = MemoryUsage();

        List<IFeature> feats = GetAllFeats(fc, false);

        long featCount = feats.Count;
        long managedMem2 = GC.GetTotalMemory(true);
        long unmanagedMem2 = MemoryUsage();

        feats = null;

        long managedMem3 = GC.GetTotalMemory(true);
        long unmanagedMem3 = MemoryUsage();

        GC.Collect();

        long managedMem4 = GC.GetTotalMemory(true);
        long unmanagedMem4 = MemoryUsage();

        System.Runtime.InteropServices.Marshal.ReleaseComObject(ws);

        long managedMem5 = GC.GetTotalMemory(true);
        long unmanagedMem5 = MemoryUsage();

        GC.Collect();
        long managedMem6 = GC.GetTotalMemory(true);
        long unmanagedMem6 = MemoryUsage();

        Debug.Print("");
        Debug.Print("Testing {0}\\{1}", fgdbPath, className);
        Debug.Print("Num features found: {0}", featCount);
        Debug.Print("\t\t\t\t\tManaged (Kb)\t\tTotal (Kb)");
        Debug.Print("\t\t\t\t\t------------\t\t--------------");
        Debug.Print("Start\t\t\t\t{0}\t\t\t\t\t{1}", managedMem1 / 1024, unmanagedMem1 / 1024);
        Debug.Print("Features retrieved\t{0}\t\t\t\t\t{1}", managedMem2 / 1024, unmanagedMem2 / 1024);
        Debug.Print("Features nulled\t\t{0}\t\t\t\t\t{1}", managedMem3 / 1024, unmanagedMem3 / 1024);
        Debug.Print("GC Collected\t\t{0}\t\t\t\t\t{1}", managedMem4 / 1024, unmanagedMem4 / 1024);
        Debug.Print("Workspace released\t{0}\t\t\t\t\t{1}", managedMem5 / 1024, unmanagedMem5 / 1024);
        Debug.Print("Workspace collected\t{0}\t\t\t\t\t{1}", managedMem6 / 1024, unmanagedMem6 / 1024);
    }

    static List<IFeature> GetAllFeats(IFeatureClass fc, bool recycling)
    {
        List<IFeature> outList = new List<IFeature>();
        IFeatureCursor fCur = fc.Search(null, recycling);
        IFeature feat;
        while ((feat = fCur.NextFeature()) != null)
            outList.Add(feat);
        Marshal.FinalReleaseComObject(fCur);
        return outList;
    }

    static long MemoryUsage()
    {
        var prc = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName);
        return prc[0].PrivateMemorySize;
    }

    static IWorkspace OpenFromPath(string workspacePath)
    {
        if (workspacePath.Substring(workspacePath.Length - 4) == ".gdb")
        {
            IWorkspaceFactory2 wsFactory = new FileGDBWorkspaceFactoryClass();
            return wsFactory.OpenFromFile(workspacePath, 0);
        }
        else
        {
            throw new NotSupportedException("No supported geodatabase type matches the provided workspace path: " + workspacePath);
        }

    }
4
  • Couple of Questions for you: i)How did you get the Cursor? through a Spatial Query or a just an Attribute Query? ii)What was the data source? Aug 13, 2010 at 5:11
  • are you calling ReleaseComObject on the Feature Cursor (or Enumerators) as well?
    – bFlood
    Aug 13, 2010 at 10:52
  • I get it via a spatial query to a FGDB.
    – BlinkyBill
    Aug 15, 2010 at 23:51
  • Yes. I'm releasing it before returning the list.
    – BlinkyBill
    Aug 16, 2010 at 0:00

4 Answers 4

3

When I run the code below 3 times for a file gdb polygon featureclass, I get the following:

Memory Before 951316
Memory After 1923476
Difference 972160
Feats loaded 44368
After collect 951364
Difference -972112

Memory Before 951320
Memory After 1923464
Difference 972144
Feats loaded 44368
After collect 951364
Difference -972100

Memory Before 951320
Memory After 1923464
Difference 972144
Feats loaded 44368
After collect 951352
Difference -972112

What do you get?

public static void MemoryTest(IApplication app)
{
    IFeatureLayer fLayer = ((IMxDocument)app.Document).FocusMap.get_Layer(0) as IFeatureLayer;
    long mem1 = GC.GetTotalMemory(true);
    Debug.Print("Memory Before {0}", mem1);
    List<IFeature> feats = GetAllFeats(fLayer.FeatureClass);
    long mem2 = GC.GetTotalMemory(true);
    Debug.Print("Memory After {0}", mem2);
    Debug.Print("Difference {0}", mem2 - mem1);
    Debug.Print("Feats loaded {0}", feats.Count);
    feats = null;
    GC.Collect();
    long mem3 = GC.GetTotalMemory(true);
    Debug.Print("After collect {0}", mem3);
    Debug.Print("Difference {0}", mem3 - mem2);
}
public static List<IFeature> GetAllFeats(IFeatureClass fc)
{
    List<IFeature> outList = new List<IFeature>();
    IFeatureCursor fCur = fc.Search(null, false);
    IFeature feat;
    while ((feat = fCur.NextFeature()) != null)
        outList.Add(feat);
    Marshal.FinalReleaseComObject(fCur);
    return outList;
}
11
  • hey kirk it works because you're releasing the cursor in the original fetch. I don't think the orig poster is doing that, he's trying it on the individual IFeature objects instead
    – bFlood
    Aug 13, 2010 at 15:31
  • ...probably because you're nuking it immediately with GC.Collect(), and not waiting for the garbage collector.
    – WolfOdrade
    Aug 13, 2010 at 15:51
  • @brian I'm curious if some workspacefactories cache features for performance (and fail to clean up). When workspacefactories get released from memory is a mystery to me. Aug 13, 2010 at 16:30
  • Memory Before 1017492 Memory After 1018112 Difference 620 Feats loaded 6 After collect 1019204 Difference 1092 Memory Before 1017604 Memory After 1017980 Difference 376 Feats loaded 3 After collect 1019224 Difference 1244 I am releasing the search cursor. There is a bit more logic involved, so can only assume that something's holding onto the references after the list's discarded? I assume it's ok to release the cursor then still using the IFeatures?
    – BlinkyBill
    Aug 15, 2010 at 23:56
  • 1
    @KirkKuykendall For me it ended up being a bug in 10.4.1. I added it as a possible answer below.
    – Donny V.
    Oct 4, 2017 at 19:04
0

You could use the ComReleaser if you are using ADF, it has a ManageLifetime where so it will release it when you aren't using it anymore.

0

Off the top of my head you might try encapsulating list, or items in the list, in a class that implements IDisposable, and call Dispose() on the list or the item.

public void Dispose()  
{  
  Finalize();  
  System.GC.SuppressFinalize(this);  
}
1
  • I already tried that to no avail. Thanks for the tip though.
    – BlinkyBill
    Aug 15, 2010 at 23:59
0

For anyone having this issue in 10.4.1. Looks like its a bug in ArcObjects. Doesn't get fixed until 10.5.1.

BUG-000103337
http://downloads.esri.com/support/downloads/other_/1051-IssuesAddressedList_07052017.pdf enter image description here

1
  • Good find @Donny V!
    – BlinkyBill
    Feb 12, 2018 at 21:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.