Java CSV语法的ANTLR4侦听器导致大文件的OutOfMemoryError

Java CSV语法的ANTLR4侦听器导致大文件的OutOfMemoryError,java,parsing,csv,antlr,antlr4,Java,Parsing,Csv,Antlr,Antlr4,对于csv文件,我有一个相对简单的ANTLR4语法,它可能包含一个标题行,然后只包含用空格分隔的数据行。 值如下Double-Double-Int-String-Date-Time其中Date采用yyyy-mm-dd格式,Time采用hh:mm:ss.xxx格式 这导致了以下语法: grammar CSVData; start : (headerline | dataline) (NL dataline)* ; headerline : STRING (' ' STRI

对于csv文件,我有一个相对简单的ANTLR4语法,它可能包含一个标题行,然后只包含用空格分隔的数据行。 值如下
Double-Double-Int-String-Date-Time
其中
Date
采用
yyyy-mm-dd
格式,
Time
采用
hh:mm:ss.xxx
格式

这导致了以下语法:

grammar CSVData;

start       :   (headerline | dataline) (NL dataline)* ;

headerline  :   STRING (' ' STRING)* ;
dataline    :   FLOAT ' ' FLOAT ' ' INT ' ' STRING ' ' DAY ' ' TIME ; //lat lon floor hid day time

NL          :   '\r'? '\n' ;
DAY         :   INT '-' INT '-' INT ; //yyyy-mm-dd
TIME        :   INT ':' INT ':' INT '.' INT ; //hh:mm:ss.xxx
INT         :   DIGIT+ ;
FLOAT       :   '-'? DIGIT* '.' DIGIT+ ;
STRING      :   LETTER (LETTER | DIGIT | SPECIALCHAR)* | (DIGIT | SPECIALCHAR)+ LETTER (LETTER | DIGIT | SPECIALCHAR)* ;

fragment LETTER     :   [A-Za-z] ;
fragment DIGIT      :   [0-9] ;
fragment SPECIALCHAR:   [_:] ;
在我的Java应用程序中,我使用的侦听器扩展了
CSVDataBaseListener
,只覆盖
enterDataline(CSVDataParser.DatalineContext ctx)
方法。在那里,我只需获取令牌并为每行创建一个对象

当加载一个10MB的文件时,这一切都能正常工作。但是当我尝试加载一个110MB大小的文件时,我的应用程序将导致一个
OutOfMemoryError:GC开销超过了限制。
我用1GB的RAM运行我的应用程序,在我看来,文件大小应该不是问题

我还尝试用Java本身编写一个解析器,它使用
String.split(“”
)。该解析器可以按预期工作,也可以用于110MB的输入文件

为了估计我创建的对象的大小,我只需按照中的建议序列化我的对象。110 MB输入文件的结果大小为86513392字节,这远没有消耗1 GB RAM

所以我想知道为什么ANTLR需要如此多的内存来实现如此简单的语法。有没有办法让我的语法更好,这样ANTLR使用的内存更少

编辑

我通过加载一个有100万行(磁盘上约77MB)的文件进行了更深入的内存分析。对于每一行,我的语法会找到12个标记(每行6个值加上5个空格和一个新行)。如果语法忽略了空格,这可以减少到每行6个标记,但这仍然比自己编写解析器糟糕得多

对于100万个输入行,内存转储的大小如下:

  • 以上我的语法:1926MB
  • 每行查找六个标记的语法:1591MB
  • 我自己编写的解析器:415MB

因此,使用更少的令牌也会导致使用更少的内存,但对于简单的语法,我还是建议编写自己的解析器,因为这并不难,而且您可以从ANTLR开销中节省大量内存使用。

根据您的语法,我将假设您的输入使用ASCII字符。如果将文件以UTF-8的形式存储在磁盘上,那么只需将文件加载到使用UTF-16的
antlInputStream
,将消耗220MB的空间。除此之外,每个
CommonToken
(上次我检查过)大约有48个字节的开销,还有来自DFA缓存和
parserContext
实例的开销


获取Java应用程序使用的内存的准确图片的唯一方法是通过探查器,并且在64位模式下,并非所有探查器都正确地考虑了压缩OOP对象存储(尽管YourKit这样做)。首先要尝试的是增加允许的堆大小。一旦您知道了使用内存的特定数据结构,就可以针对该区域进行缩减。

谢谢您的回答。我更新了我的问题,我基本上认为它已经解决了。如果想用ANTLR解析大文件,只需要一些内存。仅类
CommonToken
TerminalNodeImpl
就占用了应用程序使用的所有内存的50%以上。