Scala — высокое использование кучи при выполнении XML.loadFile для большого количества файлов в локальной области

Я пытаюсь создать дерево объектов из большого количества XML-файлов. Однако, когда я запускаю следующий код примерно с 2000 XML-файлов (от 100 КБ до 200 МБ) (обратите внимание, что я закомментировал код, создающий дерево объектов), я получаю большой объем памяти 8-9 ГБ. Я ожидаю, что в следующем примере объем памяти будет минимальным, потому что код не содержит никаких ссылок, он просто создает Elem и отбрасывает его. Память кучи остается прежней после запуска полного GC.

def addDir(dir: File) {
dir.listFiles.filter(file => file.getName.endsWith("xml.gz")).foreach { gzipFile =>
    addGzipFile(gzipFile)
}
}
def addGzipFile(gzipFile: File) {
val is = new BufferedInputStream(new GZIPInputStream(new FileInputStream(gzipFile)))
val xml = XML.load(is)
// parse xml and create object tree
is.close()
}

Мои параметры JVM: -server -d64 -Xmx16G -Xss16M -XX:+DoEscapeAnalysis -XX:+UseCompressedOops

И вывод jmap -histo выглядит так

num     #instances         #bytes  class name
----------------------------------------------
   1:      67501390     1620033360  scala.collection.immutable.$colon$colon
   2:      37249187     1254400536  [C
   3:      37287806     1193209792  java.lang.String
   4:      37200976      595215616  scala.xml.Text
   5:      18600485      595215520  scala.xml.Elem
   6:       3420921       82102104  scala.Tuple2
   7:        213938       58213240  [I
   8:       1140334       36490688  scala.collection.mutable.ListBuffer
   9:       2280468       36487488  scala.runtime.ObjectRef
  10:       1140213       36486816  scala.collection.Iterator$$anon$24
  11:       1140210       36486720  scala.xml.parsing.FactoryAdapter$$anonfun$startElement$1
  12:       1140210       27365040  scala.collection.immutable.Range$$anon$2
...
Total     213412869     5693850736

person Sachin Kanekar    schedule 02.10.2010    source источник


Ответы (1)


Я не могу воспроизвести это поведение. Я использую следующую программу:

import java.io._
import xml.XML

object XMLLoadHeap {

  val filename = "test.xml"

  def addFile() {
    val is = new BufferedInputStream(new FileInputStream(filename))
    val xml = XML.load(is)
    is.close()
    println(xml.label)
  }

  def createXMLFile() {
    val out = new FileWriter(filename)
    out.write("<foo>\n")
    (1 to 100000) foreach (i => out.write("  <bar baz=\"boom\"/>\n"))
    out.write("</foo>\n")
    out.close()
  }

  def main(args:Array[String]) {
    println("XMLLoadHeap")
    createXMLFile()
    (1 to args(0).toInt) foreach { i => 
      println("processing " + i)
      addFile()
    }
  }

}

Я запускаю его со следующими параметрами: -Xmx128m -XX:+HeapDumpOnOutOfMemoryError -verbose:gc и в целом похоже, что он может работать бесконечно.

Вы можете попытаться проверить, делает ли это это при использовании только вашего самого большого XML-файла. Возможно, проблема не в обработке большого количества файлов, а в обработке самого большого файла. При тестировании здесь с фиктивным XML-файлом размером 200 МБ на 64-битной машине я вижу, что мне нужно около 3 ГБ памяти. Если это так, вам может понадобиться использовать синтаксический анализатор. См. XMLEventReader.

Помимо этого, если вы не создаете дерево объектов, вы можете использовать -Xmx4G -XX:+HeapDumpOnOutOfMemoryError, а затем проанализировать дамп кучи с помощью такого инструмента, как МАТ. 4 ГБ должно быть достаточно для синтаксического анализа самого большого XML-файла, и к тому времени, когда вы получите сообщение об ошибке нехватки памяти, может быть выделено достаточно объектов, чтобы точно определить, какой объект препятствует сборке мусора. Скорее всего, это будет объект, содержащий различные проанализированные объекты XML.

person huynhjl    schedule 02.10.2010
comment
Запустил программу (из консоли scala, чтобы виртуальная машина оставалась в живых) для одного самого большого файла xml (438 МБ). Использование кучи не кажется проблемой - person Sachin Kanekar; 03.10.2010
comment
Запустил программу (из консоли scala, чтобы виртуальная машина оставалась в живых) для одного самого большого файла xml (438 МБ). Взял сводку кучи после загрузки файла и запуска полного gc. Использование кучи, похоже, не является проблемой, поскольку используется только 111 МБ памяти старого поколения (и 0 МБ памяти молодого поколения). Однако выходные данные команды top показывают остаточный размер (RES) 4,8 ГБ. - person Sachin Kanekar; 03.10.2010
comment
С другой стороны, работа с 32-битной (3 ГБ) кучей выдает: java.lang.OutOfMemoryError: превышен лимит накладных расходов GC в scala.xml.parsing.FactoryAdapter.startElement(FactoryAdapter.scala:136) в com.sun.org.apache. xerces.internal.parsers.AbstractSAXParser.startElement(AbstractSAXParser.java:501) на com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanStartElement(XMLDocumentFragmentScannerImpl.java:1363) на com.sun.org.apache. xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next... - person Sachin Kanekar; 03.10.2010
comment
@Sachin Это анализ одного файла? Я бы не слишком беспокоился о стате RES сверху. Если вы используете -Xmx16G, я ожидаю, что JVM как-то его использует. Кажется, что Scala довольно требовательна к памяти при представлении XML, но я не думаю, что она без необходимости удерживает ссылки. В зависимости от того, что вы пытаетесь сделать, использование XML.load может быть неправильным подходом. - person huynhjl; 03.10.2010
comment
@huynhjl Теперь я перешел на XMLEvenReader. И может загрузить 21G xmls в дерево объектов с размером кучи 16G. Помогли две вещи: 1) использование интернализации строк и 2) использование CompressedOops. - person Sachin Kanekar; 04.10.2010