Java 如何将17MB的文本文件解析为List会导致OutOfMemory堆为128MB?

Java 如何将17MB的文本文件解析为List会导致OutOfMemory堆为128MB?,java,heap,out-of-memory,Java,Heap,Out Of Memory,在我的应用程序的某些部分中,我将17MB的日志文件解析为列表结构—每行一个日志条目。大约有100K行/日志条目,即每行大约170字节。令我惊讶的是,即使指定128MB(256MB似乎足够),堆空间也会用完。如何将10MB的文本转换为对象列表,从而使空间增加十倍 我知道字符串对象使用的空间量至少是ANSI文本(Unicode,一个字符=2字节)的两倍,但这至少消耗了ANSI文本的四倍 我要寻找的是一个近似值,即n个日志项的ArrayList将消耗多少,或者我的方法如何创建会加剧这种情况的无关对象(

在我的应用程序的某些部分中,我将17MB的日志文件解析为列表结构—每行一个日志条目。大约有100K行/日志条目,即每行大约170字节。令我惊讶的是,即使指定128MB(256MB似乎足够),堆空间也会用完。如何将10MB的文本转换为对象列表,从而使空间增加十倍

我知道字符串对象使用的空间量至少是ANSI文本(Unicode,一个字符=2字节)的两倍,但这至少消耗了ANSI文本的四倍

我要寻找的是一个近似值,即n个日志项的ArrayList将消耗多少,或者我的方法如何创建会加剧这种情况的无关对象(请参阅下面关于
String.trim()的注释

这是我的LogEntry类的数据部分

public class LogEntry { 
    private Long   id; 
    private String system, version, environment, hostName, userId, clientIP, wsdlName, methodName;
    private Date                timestamp;
    private Long                milliSeconds;
    private Map<String, String> otherProperties;
公共类日志项{
私人长id;
私有字符串系统、版本、环境、主机名、userId、clientIP、wsdlName、methodName;
私有日期时间戳;
私有长毫秒;
私有财产;
这是做阅读的部分

public List<LogEntry> readLogEntriesFromFile(File f) throws LogImporterException {
    CSVReader reader;
    final String ISO_8601_DATE_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS";

    List<LogEntry> logEntries = new ArrayList<LogEntry>();
    String[] tmp;
    try {
        int lineNumber = 0;
        final char DELIM = ';';
        reader = new CSVReader(new InputStreamReader(new FileInputStream(f)), DELIM);
        while ((tmp = reader.readNext()) != null) {
            lineNumber++;

            if (tmp.length < LogEntry.getRequiredNumberOfAttributes()) {

                String tmpString = concat(tmp);

                if (tmpString.trim().isEmpty()) {
                    logger.debug("Empty string");
                } else {
                    logger.error(String.format(
                            "Invalid log format in %s:L%s. Not enough attributes (%d/%d). Was %s . Continuing ...",
                            f.getAbsolutePath(), lineNumber, tmp.length, LogEntry.getRequiredNumberOfAttributes(), tmpString)
                    );
                }

                continue;
            }

            List<String> values = new ArrayList<String>(Arrays.asList(tmp));
            String system, version, environment, hostName, userId, wsdlName, methodName;
            Date timestamp;
            Long milliSeconds;
            Map<String, String> otherProperties;

            system = values.remove(0);
            version = values.remove(0);
            environment = values.remove(0);
            hostName = values.remove(0);
            userId = values.remove(0);
            String clientIP = values.remove(0);
            wsdlName = cleanLogString(values.remove(0));
            methodName = cleanLogString(stripNormalPrefixes(values.remove(0)));
            timestamp = new SimpleDateFormat(ISO_8601_DATE_PATTERN).parse(values.remove(0));
            milliSeconds = Long.parseLong(values.remove(0));

            /* remaining properties are the key-value pairs */
            otherProperties = parseOtherProperties(values);

            logEntries.add(new LogEntry(system, version, environment, hostName, userId, clientIP,
                    wsdlName, methodName, timestamp, milliSeconds, otherProperties));
        }
        reader.close();
    } catch (IOException e) {
        throw new LogImporterException("Error reading log file: " + e.getMessage());
    } catch (ParseException e) {
        throw new LogImporterException("Error parsing logfile: " + e.getMessage(), e);
    }

    return logEntries;
}
public List readLogEntriesFromFile(文件f)抛出LogImporterException{
CSVReader阅读器;
最终字符串ISO_8601_DATE_PATTERN=“yyyy-MM-dd HH:MM:ss,SSS”;
List logEntries=new ArrayList();
字符串[]tmp;
试一试{
int lineNumber=0;
最终字符DELIM=';';
reader=new CSVReader(new InputStreamReader(new FileInputStream(f)),DELIM);
while((tmp=reader.readNext())!=null){
lineNumber++;
if(tmp.length
用于填充地图的实用程序函数

private Map<String, String> parseOtherProperties(List<String> values) throws ParseException {
    HashMap<String, String> map = new HashMap<String, String>();

    String[] tmp;
    for (String s : values) {
        if (s.trim().isEmpty()) {
            continue;
        }

        tmp = s.split(":");
        if (tmp.length != 2) {
            throw new ParseException("Could not split string into key:value :\"" + s + "\"", s.length());
        }
        map.put(tmp[0], tmp[1]);
    }
    return map;
}
私有映射parseOtherProperties(列表值)引发ParseException{
HashMap=newHashMap();
字符串[]tmp;
用于(字符串s:值){
如果(s.trim().isEmpty()){
继续;
}
tmp=s.split(“:”);
如果(tmp.length!=2){
抛出新的ParseException(“无法将字符串拆分为键:value:\”“+s+”\”“,s.length());
}
map.put(tmp[0],tmp[1]);
}
返回图;
}

不要忘记,每个
字符串
对象都会占用实际
对象定义的空间(24字节),加上对字符数组的引用,偏移量(对于
子字符串()
用法)因此,将一行表示为“n”字符串将增加额外的存储需求。您可以在
LogEntry
类中惰性地评估这些吗


(关于字符串偏移量的使用-在Java 7b6之前
String.substring()
充当现有字符数组的窗口,因此您需要偏移量。这一点最近发生了变化,可能值得确定以后的JDK构建是否更节省内存)

那里还有一个映射,您可以在其中存储其他属性。您的代码不会显示此映射的填充方式,但请记住,与条目本身所需的内存相比,映射可能会有很大的内存开销


支持映射的数组大小(至少16个项*4字节)+每个项一个键/值对+数据本身的大小。两个映射项,每个项使用10个字符作为键,10个字符作为值,将消耗16*4+2*2*4+2*10*2+2*2*8=64+16+40+40+24=184字节(1个字符=2字节,字符串对象至少消耗8字节).仅此一项就几乎使整个日志字符串的空间需求翻了一番

此外,LogEntry包含12个对象,即至少96个字节。因此,日志对象本身就需要大约100个字节(给定或获取一些字节),没有映射,也没有实际的字符串数据。加上引用的所有指针(每个4B)。我用映射计算至少18个字节,即72个字节

添加数据(-上一段提到的对象引用和对象“标题”)
2长