Scala内存泄漏使用pull解析器

时间:2011-02-20 13:32:38

标签: xml scala memory-leaks hadoop wikipedia

我一直在尝试编写一个XML解析器来读取维基百科XML转储(英语,仅限当前版本,大约6.2Gb bzip)并且一直在使用Scala 2.8.1 pull解析器。它得到了合理的通过(超过1000万篇文章中的300万篇),但似乎逐渐泄漏内存并最终因出现堆错误而爆炸。我把堆高达1.5Gb并且它进一步(几乎到最后),但后来我得到(我忘了确切的异常)一个错误,表明垃圾收集器放弃了(花费了整个处理资源的很大一部分)没有回收太多)。

我的代码对我来说似乎很合理(虽然它还不是惯用的功能scala)但我看不到任何明显的泄漏源。我也知道拉解析器仍在改进 - 但我很清楚我自己的无知称这是一个库问题。我是一位经验丰富的C ++和Python程序员,但我刚刚进入Scala,所以我将不胜感激。

import java.io.{FileInputStream, BufferedInputStream}
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream
import org.apache.hadoop.io.SequenceFile.{createWriter}
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.hadoop.io.Text
import org.apache.hadoop.io.SequenceFile.CompressionType.BLOCK

import scala.io.Source
import scala.xml.pull.{XMLEventReader, EvElemStart, EvElemEnd, EvText}



object Crunch
{
    private def parsePage( parser : XMLEventReader ) : (String, Long, Long, String) =
    {
        var title = ""
        var id = 0
        var revision = 0
        var text = ""
        var done = false
        while ( parser.hasNext && !done )
        {
            parser.next match
            {
                case EvElemStart(_, "title", _, _ ) =>
                {
                    title = getText( parser, "title" )
                }
                /*case EvElemStart(_, "revision", _, _) =>
                {
                    // Need to get the 'id' from revision
                    revision = getText( parser, "revision" ).toInt
                }*/
                case EvElemStart(_, "id", _, _ ) =>
                {
                    id = getText( parser, "id" ).toInt
                }
                case EvElemStart(_, "text", _, _ ) =>
                {
                    text = getText( parser, "text" )
                }
                case EvElemEnd(_, "page") =>
                {
                    done = true
                }
                case _ =>
            }
        }
        return (title, id, revision, text)
    }

    private def getText( parser : XMLEventReader, inTag : String ) : String =
    {
        var fullText = new StringBuffer()
        var done = false
        while ( parser.hasNext && !done )
        {
            parser.next match
            {
                case EvElemEnd(_, tagName ) =>
                {
                    assert( tagName.equalsIgnoreCase(inTag) )
                    done = true
                }
                case EvText( text ) =>
                {
                    fullText.append( text )
                }
                case _ =>
            }
        }
        return fullText.toString()
    }
    def main( args : Array[String] )
    {
        require( args.length == 2 )
        val fin = new FileInputStream( args(0) )
        val in = new BufferedInputStream(fin)
        val decompressor = new BZip2CompressorInputStream(in)

        val runtime = Runtime.getRuntime

        val conf = new Configuration()
        val fs = FileSystem.get(conf)        

        //val writer = createWriter( fs, conf, new Path(args(1)), new Text().getClass(), new Text().getClass(), BLOCK )

        var count = 0
        try
        {
            val source = Source.fromInputStream( decompressor )
            val parser = new XMLEventReader(source)

            while (parser.hasNext)
            {
                parser.next match
                {
                    case EvElemStart(_, "page", attrs, _) =>
                    {
                        val (title, id, revision, text) = parsePage( parser )

                        //writer.append( new Text(title), new Text(text) )

                        count = count + 1
                        if ( count % 100 == 0 )
                        {
                            printf("%s %d (%dMb mem, %dMb free)\n", title, count,
                                (runtime.totalMemory/1024/1024).toInt,
                                (runtime.freeMemory/1024/1024).toInt )
                        }
                    }
                    case _ =>
                }
                // Do something
            }
        }
        finally
        {
            decompressor.close()
            fin.close()
        }

        println( "Finished decompression.")
    }
}

1 个答案:

答案 0 :(得分:3)

XML pull解析器有两种类型的内存问题已在trunk中修复:

  1. CDataprocessing instruction元素阻止垃圾回收
  2. Elements with a lot of children,每个孩子都需要一点记忆,最终堆耗尽。
  3. 第一个问题通常会导致内存问题非常快,所以不太可能。

    两者都应该每晚固定2.9.0,我建议使用它。如果你在2.9.0问题上运行,因为它是主干并且可能不稳定,你也可以通过下载和编译本地XMLEventEventReaderMarkupParser来反向移植这两个补丁,然后将输出打包为{ {1}}以便在scala libs jar之前将其放在2.8.1安装的00patch.jar下。