Asked  7 Months ago    Answers:  5   Viewed   137 times

I've been googling for the past few hours and trying different things but can't seem to the bottom of this....

When I run this code, the memory usage continuously grows.

while (true)
{
    try
    {
        foreach (string sym in stringlist)
        {
            StreamReader r = new StreamReader(@"C:Program Files" + sym + ".xml");
            XmlSerializer xml = new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"));
            XMLObj obj = (XMLObj)xml.Deserialize(r);                       
            obj.Dispose();
            r.Dispose();
            r.Close();
        }
    }    
    catch(Exception ex) 
    {
        Console.WriteLine(ex.ToString()); 
    }
    Thread.Sleep(1000);
    Console.Clear();
}

XMLObj is a custom object

[Serializable()]
public class XMLObj: IDisposable
{
    [XmlElement("block")]
    public List<XMLnode> nodes{ get; set; }

    public XMLObj() { }

    public void Dispose()
    {
        nodes.ForEach(n => n.Dispose());
        nodes= null;

        GC.SuppressFinalize(this);
    }
}

I've tried adding in GC.Collect(); but that doesn't seem to do anything.

 Answers

33

The leak is here:

new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"))

XmlSerializer uses assembly generation, and assemblies cannot be collected. It does some automatic cache/reuse for the simplest constructor scenarios (new XmlSerializer(Type), etc), but not for this scenario. Consequently, you should cache it manually:

static readonly XmlSerializer mySerializer =
    new XmlSerializer(typeof(XMLObj), new XmlRootAttribute("rootNode"))

and use the cached serializer instance.

Tuesday, June 1, 2021
 
Deyson
answered 7 Months ago
25

1.

You can set XmlWriterSettings's CheckCharacters property to avoid writing illegal chars.(Serialize method would throw exception)

using (FileStream tmpFileStream = new FileStream(tmpFile, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
    var writer = XmlWriter.Create(tmpFileStream, new XmlWriterSettings() { CheckCharacters = true});
    serializer.Serialize(writer, items);
}

2.

You can create your own XmlTextWriter to filter out unwanted chars while serializing

using (FileStream tmpFileStream = new FileStream(tmpFile, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
    var writer = new MyXmlWriter(tmpFileStream);
    serializer.Serialize(writer, items);
}

public class MyXmlWriter : XmlTextWriter
{
    public MyXmlWriter(Stream s) : base(s, Encoding.UTF8)
    {
    }

    public override void WriteString(string text)
    {
        string newText = String.Join("", text.Where(c => !char.IsControl(c)));
        base.WriteString(newText);
    }
}

3.

By creating your own XmlTextReader you can filter out unwanted chars while deserializing

using (FileStream plainTextFile = new FileStream(tmpFile, FileMode.Open, FileAccess.Read))
{
    var reader = new MyXmlReader(plainTextFile);
    result = (SomeObject)serializer.Deserialize(reader); 
}

public class MyXmlReader : XmlTextReader
{
    public MyXmlReader(Stream s) : base(s)
    {
    }

    public override string ReadString()
    {
        string text =  base.ReadString();
        string newText = String.Join("", text.Where(c => !char.IsControl(c)));
        return newText;
    }
}

4.

You can set XmlReaderSettings's CheckCharacters property to false. Deserialization will work now smoothly. (you'll get v back.)

using (FileStream plainTextFile = new FileStream(tmpFile, FileMode.Open, FileAccess.Read))
{
    var reader = XmlReader.Create(plainTextFile, new XmlReaderSettings() { CheckCharacters = false });
    result = (SomeObject)serializer.Deserialize(reader); 
}
Sunday, August 1, 2021
 
Benji
answered 5 Months ago
100

You are using watching the reference at the wrong time. You should only call watch(thing) when you are absolutely certain that thing will be garbage collected. For your Activities and Fragments, you will want something like this:

@Override public void onDestroy() {
    super.onDestroy();
    RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
    refWatcher.watch(this);
}

That is from the LeakCanary FAQ "How do I use it?" section

Saturday, August 14, 2021
 
Kumar112
answered 4 Months ago
37

When you use XmlChoiceIdentifierAttribute to deserialize a sequence of choice elements into some shared data type (here, string), you need to add two arrays to your data model:

  1. A property returning an array of objects of appropriate type to capture the choice element contents, here the four string values in the tradeAreas array.

    This first array must be marked with [XmlChoiceIdentifier(name)] where name is the name of the second array, as well as [XmlElement(elementName)] attributes for each possible choice element name.

  2. And a property returning an array of enums to capture the choice element names, here <area> for each element.

    This array must be marked with [XmlIgnore].

And finally, since the second array captures the element names for the element contents in the first array, the arrays must be in 1-1 correspondence. Thus choices must be initialized to the same length as tradeAreas, e.g. as follows:

var tradeAreas = new string[] {"Area1", "Area2", "Area3", "Area4"};
var choices = Enumerable.Repeat(ItemsChoiceType.area, tradeAreas.Length).ToArray();

In your sample code tradeAreas has four entries while choices has but one, leading to the exception you see.

Working sample .Net fiddle here.

For documentation on binding to XML choice elements using XmlSerializer, see Choice Element Binding Support: Differentiating by Name as well as XmlChoiceIdentifierAttribute Class: Remarks. For a simple manual example, see e.g. this answer to How to Serialize List<T> property in c# without showing its type in XML serialization.

Friday, August 27, 2021
 
kosnik
answered 4 Months ago
18

I can see the problem in PS v3.0 but not in PS v2.0. Here is the code I use to see this (all examples are in PowerShell):

for() {
    $runspace = [runspacefactory]::CreateRunspace()
    $runspace.Open()
    $runspace.Close()
    $p = Get-Process -Id $PID
    '{0} {1}' -f $p.Handles, ($p.PrivateMemorySize / 1mb)
}

It looks like handles and memory are leaking in v3.0 in the code above.

As far as v2.0 does not have this problem, one possible workaround may be to start the service using PS v2.0, i.e. PowerShell.exe -Version 2.0.

If this is not possible I can think of two more workarounds. One of them is not to create runspaces directly but use [powershell] instead. For example, this code does not show the leak in v3.0:

for() {
    $ps = [powershell]::Create()
    $p = $ps.AddCommand('Get-Process').AddParameter('Id', $PID).Invoke()
    '{0} {1}' -f $p.Handles, ($p.PrivateMemorySize / 1mb)
    $ps.Dispose()
}

Another workaround, if it is applicable, may be use of [runspacefactory]::CreateRunspacePool(). This way also does not show the leak:

$rs = [runspacefactory]::CreateRunspacePool()
$rs.Open()
for() {
    $ps = [powershell]::Create()
    $ps.RunspacePool = $rs
    $p = $ps.AddCommand('Get-Process').AddParameter('Id', $PID).Invoke()
    '{0} {1}' -f $p.Handles, ($p.PrivateMemorySize / 1mb)
    $ps.Dispose()
}
#$rs.Close() # just a reminder, it's not called here due to the infinite loop

The last one also works much faster because the runspace is kind of reused.

Sunday, October 10, 2021
 
Gerardo
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share