Take the 2-minute tour ×
Geographic Information Systems Stack Exchange is a question and answer site for cartographers, geographers and GIS professionals. It's 100% free, no registration required.

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?

==========UPDATE===========

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);
        }

    }
share|improve this question
    
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? –  Devdatta Tengshe Aug 13 '10 at 5:11
    
are you calling ReleaseComObject on the Feature Cursor (or Enumerators) as well? –  bFlood Aug 13 '10 at 10:52
    
I get it via a spatial query to a FGDB. –  BlinkyBill Aug 15 '10 at 23:51
    
Yes. I'm releasing it before returning the list. –  BlinkyBill Aug 16 '10 at 0:00
add comment

3 Answers

up vote 3 down vote accepted

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;
}
share|improve this answer
    
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 '10 at 15:31
    
...probably because you're nuking it immediately with GC.Collect(), and not waiting for the garbage collector. –  WolfOdrade Aug 13 '10 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. –  Kirk Kuykendall Aug 13 '10 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 '10 at 23:56
    
The issue appears to be that the managed wrapper is getting detached from the COM object in the background. If I measure memory usage with GC.GetTotalMemory(true), things are only off by a matter of bytes. Though by looking at the process memory usage (PrivateMemorySize), it's in the order of MB with each call. –  BlinkyBill Aug 16 '10 at 0:27
show 3 more comments

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.

share|improve this answer
add comment

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);
}

share|improve this answer
    
I already tried that to no avail. Thanks for the tip though. –  BlinkyBill Aug 15 '10 at 23:59
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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