创建并写入XML文件时抛出异常 -> 内存不足异常?

14

我已经开发了一款名为“voxel”的小游戏,使用XNA技术开发。这是一款类似于Minecraft的.NET C#游戏。 我使用简单的概念将我的游戏数据保存和读取到XML文件中以构建世界地图。

现在我正在尝试加载一个较大的地图,并且生成地图时会引发一个“System out of memory exception”的异常:

[系统内存不足异常]

我不明白为什么会这样,因为在引发异常后,我的文件并不是非常重,只有70 MB左右。在生成一个70 MB的XML文件期间看到异常是否正常?

开发环境

  • Microsoft Windows 7 (x64)位元
  • Visual Studio 2012 professionnel Update 3
  • 8192 Ram
  • Intel i5 CPU 2.5 Ghz (4 cpus)

我进行了CLR分析器测试来检查垃圾收集器的工作方式:

进入图像说明 进入图像说明

初始化XmlWritter的代码:

    private XmlTextWriter myXmlTextWriter ;


    #region prepareNewWorldXmlFIle
    private void PreparedNewWorldXmlFile()
    {
        Stream fs = new FileStream(currentPath + "World\\world.xml", FileMode.Create);

        myXmlTextWriter = new XmlTextWriter(fs,Encoding.ASCII);
        myXmlTextWriter.Formatting = Formatting.Indented;
        myXmlTextWriter.WriteStartDocument(false);
        myXmlTextWriter.WriteComment("World Map ID:");
        //World and his attribute
        myXmlTextWriter.WriteStartElement("World");
        myXmlTextWriter.WriteStartElement("Matrix", null);
        myXmlTextWriter.WriteStartElement("Regions");
    }
    #endregion

我用来生成世界地图并编写XML文件的代码:

    //Octree calcul and generate map to xml file
    foreach (Region region in arcadia.world.Regions)
    {
        isAllCheckSameIdOctree = false;
        if (isFirstGenerationWorld)
        {
            //Regions and attributes
            myXmlTextWriter.WriteStartElement("Region");
            myXmlTextWriter.WriteAttributeString("id", indexRegion.ToString());
            myXmlTextWriter.WriteAttributeString("min", "x:" + region.PositionMin.X + ";y:" + region.PositionMin.Y + ";z:" + region.PositionMin.Z);
            myXmlTextWriter.WriteAttributeString("max", "x:" + region.PositionMax.X + ";y:" + region.PositionMax.Y + ";z:" + region.PositionMax.Z);
            myXmlTextWriter.WriteStartElement("Structures");
            myXmlTextWriter.WriteAttributeString("type", "cube");
        }
        indexRegion++;
        if (region.Matrice != null)
        {
            //If the node to generate contain minimum a height divisible by 2
            if (((region.PositionMax.Y - region.PositionMin.Y) / 2) > 2)
            {
                //generate and octree by 8
                GenerateNodes(region, region.PositionMin, 8);
            }
            else if (((region.PositionMax.Y - region.PositionMin.Y) / 2) <= 2)
            {
                //generate and octree by 4
                GenerateNodes(region, region.PositionMin, 4);
            }
            while (!isAllCheckSameIdOctree)
            {
                if (nodeToRegenerate != null && needRecurseBuild)
                {
                    //if the node is greater than 2
                    if (nodeToRegenerate.TotalHeight > 2)
                    {
                        nodeToRegenerate = GenerateNodes(nodeToRegenerate, region, nodeToRegenerate.Position, 8);
                        if (nodeToRegenerate == null)
                        {
                            isAllCheckSameIdOctree = true;
                        }

                    }
                    else if (nodeToRegenerate.TotalHeight <= 2)
                    {
                        nodeToRegenerate = GenerateNodes(nodeToRegenerate, region, nodeToRegenerate.Position, 4);
                        if (nodeToRegenerate == null)
                        {
                            isAllCheckSameIdOctree = true;
                        }
                    }
                }
                else
                {
                    isAllCheckSameIdOctree = true;
                }
            }
            if (isFirstGenerationWorld)
            {
                myXmlTextWriter.WriteEndElement();//Ferme le noeud Structures
                myXmlTextWriter.WriteEndElement();//Ferme le noeud Region
                myXmlTextWriter.Flush();
            }
        }
        else
        {
            if (isFirstGenerationWorld)
            {
                myXmlTextWriter.WriteEndElement();//Ferme le noeud Structures
                myXmlTextWriter.WriteEndElement();//Ferme le noeud Region
                myXmlTextWriter.Flush();
            }
        }
    }
    if (isFirstGenerationWorld)
    {
        myXmlTextWriter.WriteEndElement();//Ferme le noeud Regions
        myXmlTextWriter.WriteEndElement();//Ferme le noeud World
        myXmlTextWriter.Flush();
        myXmlTextWriter.Close();
    }

在我的generatedNode函数中捕获异常,如下所示,更多信息请参见以下内容

enter image description here

我的generatedNode函数是递归的,在其中发生了“系统内存不足异常”的异常

#region ReGenerateWorld
    private Node GenerateNodes(Node nodeToRegenerate, Region region, Vector3 position, int countToCut)
    {
        //Relative dimension of the parent octree
        int widthParent = (int)nodeToRegenerate.TotalWidth / 2;
        int heightParent = (int)nodeToRegenerate.TotalHeight / 2;
        int lenghtParent = (int)nodeToRegenerate.TotalLenght / 2;

        //Relative dimension of the parent octree
        int widthNode = (widthParent) / (countToCut / (countToCut / 2));
        int heightNode = (heightParent) / (countToCut / (countToCut / 2));
        int lenghtNode = (lenghtParent) / (countToCut / (countToCut / 2));

        if (heightNode < 1)
        {
            heightNode = 1;
        }

        int refX = (int)position.X / 2;
        int refY = (int)position.Y / 2;
        int refZ = (int)position.Z / 2;
        int indexStartX = 0;
        int indexStartY = 0;
        int indexStartZ = 0;
        int nbrToCut = 0;
        if (heightParent >= 2)
        {
            nbrToCut = ((widthParent / (widthParent / 2))) * ((heightParent / (heightParent / 2))) * ((lenghtParent / (lenghtParent / 2)));
        }
        else
        {
            nbrToCut = 4;
            heightNode = 1;
        }
        //Calculate the number of cubic to cut

        //Génére les noeud racine
        int countVertical = 0;
        int calcPosX = 0;
        int calcPosY = 0;
        int calcPosZ = 0;
        int[][][] nodeMatriceWorld = null;
        bool firstTime;
        newNode = null;
        int idGroup = 0;
        bool isSameId = true;
        int idToCheck = 0;

        for (int index = 0; (index < nbrToCut) && (refY < 32); index++)
        {
            indexStartX = refX;
            indexStartY = refY;
            indexStartZ = refZ;

            try
            {
                nodeMatriceWorld = new int[widthNode][][];
                for (int i = 0; i < widthNode; i++)
                {
                    nodeMatriceWorld[i] = new int[lenghtNode][];
                    for (int j = 0; j < lenghtNode; j++)
                    {
                        nodeMatriceWorld[i][j] = new int[heightNode];
                    }
                }
            }
            catch (Exception ex)
            {
                // OUT OF MEMORY EXCEPTION HERE
                Console.Out.WriteLine(ex.Message);
            }
            firstTime = true;
            for (int epaisseur = 0; epaisseur < heightNode; epaisseur++, indexStartY++)
            {
                for (int ligne = 0; ligne < lenghtNode; ligne++, indexStartZ++)
                {
                    for (int collone = 0; collone < widthNode; collone++, indexStartX++)
                    {
                        if (firstTime)
                        {
                            calcPosX = indexStartX;
                            calcPosY = indexStartY;
                            calcPosZ = indexStartZ;

                            firstTime = false;
                        }
                        nodeMatriceWorld[collone][ligne][epaisseur] = matriceWorld[indexStartX][indexStartZ][indexStartY];
                    }

                    indexStartX = refX;
                }

                indexStartZ = refZ;
            }

            indexStartY = refY;
            idGroup = matriceWorld[calcPosX][calcPosZ][calcPosY];
            countVertical++;
            if (newNode != null)
            {
                newNode.Dispose();
            }

            newNode = new Node(nodeMatriceWorld, new Vector3(calcPosX, calcPosY, calcPosZ), idGroup, widthNode, heightNode, lenghtNode);
            region.Nodes[idGroup].Add(newNode);

            //Regions.Add(new Node(nodeMatriceWorld, new Vector3(calcPosX, calcPosY, calcPosZ), idGroup));
            refX += widthNode;

            if (countVertical >= 4)
            {
                refY = ((int)position.Y / 2) + heightNode;
                refX = ((int)position.X / 2);
                refZ = (int)position.Z / 2;
                countVertical = 0;
            }
            else if (countVertical == 2)
            {
                refZ = ((int)position.Z / 2) + lenghtNode;
                refX = ((int)position.X / 2);

            }
        }

        isSameId = true;
        nodeToRegenerate = null;
        needRecurseBuild = false;
        idToCheck = 0;
        // Check for each octree node if all are the same id
        foreach (List<Node> listNode in region.Nodes)
        {
            foreach (Node node in listNode.Where(m => m.isGroupSameId == false))
            {

                isSameId = true;
                idToCheck = node.matriceNode[0][0][0];
                node.isGroupSameId = true;//Le met a true au depart
                for (int epaisseur = 0; epaisseur < node.TotalHeight / 2 && isSameId; epaisseur++)
                {
                    for (int ligne = 0; ligne < node.TotalLenght / 2 && isSameId; ligne++)
                    {
                        for (int collone = 0; collone < node.TotalWidth / 2 && isSameId; collone++)
                        {
                            if (node.matriceNode[collone][ligne][epaisseur] != idToCheck)
                            {
                                isSameId = false;//si au moin un cube est différent on le marque
                                node.isGroupSameId = false;
                                //node.ItemGroup = node.matriceNode[collone, epaisseur, ligne];
                                nodeToRegenerate = node;
                                needRecurseBuild = true;
                                break;
                            }

                        }
                    }

                }

                if (!isSameId)
                {

                    break;
                }
                else
                {
                    if (idToCheck != 0)
                    {
                        isSameId = true;
                        node.isGroupSameId = true;
                        node.ItemGroup = idToCheck;
                        node.matriceNode = null;
                        node.Cube = new Primitives3D.Cube(node.ItemGroup, node.Position, new Vector3(0, 0, 0), node.TotalWidth, node.TotalHeight, node.TotalLenght);
                        //Initialise le cube qui représente le noeud
                        node.Cube.BuildCubeStart();
                        if (isFirstGenerationWorld)
                        {
                            myXmlTextWriter.WriteStartElement("Cube");
                            //Structures et ses attributs
                            myXmlTextWriter.WriteAttributeString("id", node.ItemGroup.ToString());
                            myXmlTextWriter.WriteAttributeString("min", "x:" + node.Cube.BoundingBox.Min.X + ";y:" + node.Cube.BoundingBox.Min.Y + ";z:" + node.Cube.BoundingBox.Min.Z);
                            myXmlTextWriter.WriteAttributeString("max", "x:" + node.Cube.BoundingBox.Max.X + ";y:" + node.Cube.BoundingBox.Max.Y + ";z:" + node.Cube.BoundingBox.Max.Z);

                            myXmlTextWriter.WriteEndElement();//Ferme le noeud xml cube
                            myXmlTextWriter.Flush();

                        }
                        //Ajoute l'id du noeud aux groupe d'id de la region s'il n'y était pas auparavant
                        if (!region.IdFound.Contains(node.ItemGroup) && node.ItemGroup != 0)
                        {
                            region.IdFound.Add(node.ItemGroup);
                        }
                    }
                    // If the node group is equal to an empty group id -> 0 it removes entire                        else
                    {
                        nodeToDelete = node;
                    }
                }
            }
            if (!isSameId)
            {

                break;
            }
            //Console.Out.WriteLine("Nodes cout generated : " + Nodes.Count.ToString());

        }
        // If a node does not contain all the same id -> go remove it
        if (!isSameId)
        {
            region.Nodes[nodeToRegenerate.ItemGroup].Remove(nodeToRegenerate);

        }
        if (nodeToDelete != null)
        {
            region.Nodes[nodeToDelete.ItemGroup].Remove(nodeToDelete);
        }
        nodeToDelete = null;
        //Dispose the resources
        newNode.Dispose();
        nodeMatriceWorld = null;


        return nodeToRegenerate;
    }
    #endregion

异常情况:

enter image description here


我看到有些人在读取真正大的XML文件(如1GB)时遇到了问题...但不是70MB。此外,XmlReader可以流式传输数据,而不是将整个文档加载到内存中...尝试寻找LINQ to XML。 - Davor Mlinaric
这不是正常的。请检查在调用GenerateNode函数时是否通过递归进入无限循环。此外,异常似乎发生在Microsoft.CompilerServices.AsyncTargetingPack.Net4.dll中,这是一个与您的代码无关的.NET库。为什么会出现这种情况?您可以尝试从引用中移除它。 - Petr Hudeček
1
加载或保存崩溃了吗?文本显示是加载,但您展示的是保存XML的代码。您能否展示加载XML的代码?最好能提供异常的完整堆栈跟踪。 - PMF
你能提供一个小的重现代码异常的示例,以及一个样本XML文件吗?最好是压缩过的。 - Danny Tuppeny
攻击这个问题有几个方面是有帮助的。其中一个是限制读取的项目数量,然后检查数据结构是否存在重复和意外相遇等情况。另一种类似的方法是使用计数断点并检查状态。 - Henry Troup
显示剩余7条评论
6个回答

1
据我理解,您的问题不在于XML,而在于三维数组 int[][][] nodeMatriceWorld。它的大小过大或者您创建了太多次这个数组。以下是您的代码:

try
{
    nodeMatriceWorld = new int[widthNode][][];
    for (int i = 0; i < widthNode; i++)
    {
        nodeMatriceWorld[i] = new int[lenghtNode][];
        for (int j = 0; j < lenghtNode; j++)
        {
            nodeMatriceWorld[i][j] = new int[heightNode];
        }
    }
}
catch (Exception ex)
{
    // OUT OF MEMORY EXCEPTION HERE
    Console.Out.WriteLine(ex.Message);
}

检查widthNode、lenghtNode和heightNode。对于nodeMatriceWorld,您需要至少sizeof(int) * widthNode * lenghtNode * heightNode字节的内存空间。

0
当递归方法使用的堆栈内存耗尽时,您可能会遇到内存不足异常。基本上,您的递归要么是无限的,要么足够接近无限以耗尽为堆栈保留的内存。

0

看起来您正在尝试使用三维数组来表示地图,使用高度图,但您只需要2个维度。

示例: 数组:int map[x][y]; 其中map[x][y]的值是z。

这样,您需要更少的内存,可以解决您的问题。


0

我最近在使用XSLT处理XML时遇到了类似的错误。我的文件只有5MB,但是我将我的XSLT处理转换成了一个.NET类(使用MS转换工具进行转换),这解决了问题。显然是XSLT处理器的某个错误。

你可能也遇到了类似的问题,正如其他用户建议的那样,尝试使用不同的XML文档处理器,因为你正在使用的可能存在错误或限制。


0
在你的加载代码中,widthNode、lengthNode和heightNode有多大?如果出现一些不合理的值,你可能会分配一个巨大的int数组——足够大的对象会被分配到大对象堆上,这更难以管理——首先,它不会被压缩。所以你会有一个3GB的进程,实际上只使用了70MB的内存。
异常是否在重复加载(和释放)时发生,或者在每个应用程序运行时仅加载一个节点时也会发生?CLR分析器中的按地址查看对象对于查看实际内存布局很有帮助——如果堆碎片是你的问题,你会很容易地看到空闲空间间隙。
最后但并非最不重要的是,OutOfMemoryException可能由于某种原因完全错误——通常的罪魁祸首往往是在数组构造函数中得到错误的值(例如new int[int.MaxValue]将失败)。

嗨Luaan。谢谢你的建议。我会尝试检查所有内容。widthNode,lengthNode和heightNode的大小仅在1-32之间。是的,在重复加载时会出现异常。也许在循环中调用相同函数50-60次后会出现异常。 - Mehdi Bugnard

0
首先,请确保在使用完FileStream后关闭它。 例如)- 这是最佳实践之一。
private void PreparedNewWorldXmlFile()
{
    using(Stream fs = new FileStream(currentPath + "World\\world.xml", FileMode.Create))
    {
        myXmlTextWriter = new XmlTextWriter(fs,Encoding.ASCII);
        myXmlTextWriter.Formatting = Formatting.Indented;
        myXmlTextWriter.WriteStartDocument(false);
        myXmlTextWriter.WriteComment("World Map ID:");
        //World and his attribute
        myXmlTextWriter.WriteStartElement("World");
        myXmlTextWriter.WriteStartElement("Matrix", null);
        myXmlTextWriter.WriteStartElement("Regions");
    }
}

我认为你应该在区域类实例中释放所有节点对象,因为即使你将其设置为null,GC也不会收集你的区域类实例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接