5

我正在运行一个简单的扫描程序来解析一个字符串,但是我发现如果经常调用我会得到 OutOfMemory 错误。这段代码被称为为字符串数组重复构建的对象的构造函数的一部分:

编辑:这是更多信息的构造函数;关于扫描仪的尝试捕获之外没有更多的事情发生

   public Header(String headerText) {
        char[] charArr;
        charArr = headerText.toCharArray();
        // Check that all characters are printable characters
        if (charArr.length > 0 && !commonMethods.isPrint(charArr)) {
            throw new IllegalArgumentException(headerText);
        }
        // Check for header suffix
        Scanner sc = new Scanner(headerText);
        MatchResult res;
        try {
            sc.findInLine("(\\D*[a-zA-Z]+)(\\d*)(\\D*)");
            res = sc.match();
        } finally {
            sc.close();
        }

        if (res.group(1) == null || res.group(1).isEmpty()) {
            throw new IllegalArgumentException("Missing header keyword found");     // Empty header to store
        } else {
            mnemonic = res.group(1).toLowerCase();                            // Store header
        }
        if (res.group(2) == null || res.group(2).isEmpty()) {
            suffix = -1;
        } else {
            try {
                suffix = Integer.parseInt(res.group(2));       // Store suffix if it exists
            }  catch (NumberFormatException e) {
                throw new NumberFormatException(headerText);
            }
        }
        if (res.group(3) == null || res.group(3).isEmpty()) {
            isQuery= false;
        } else {
            if (res.group(3).equals("?")) {
                isQuery = true;
            } else {
                throw new IllegalArgumentException(headerText);
            }
        }

        // If command was of the form *ABC, reject suffixes and prefixes
        if (mnemonic.contains("*") 
                && suffix != -1) {
            throw new IllegalArgumentException(headerText);
        }
    }

分析器内存快照显示了 Scanner.findInLine() 的 read(Char) 方法在操作期间分配大量内存,因为我扫描了几十万个字符串;几秒钟后,它已经分配了超过 38MB。

在此处输入图像描述

我认为在构造函数中使用它之后在扫描仪上调用 close() 会将旧对象标记为由 GC 清除,但不知何故它仍然存在,并且 read 方法在填充堆之前累积了千兆字节的数据。

谁能指出我正确的方向?

4

6 回答 6

2

您尚未发布所有代码,但鉴于您正在反复扫描相同的正则表达式,Pattern预先编译静态并将其用于扫描仪的查找会更有效:

static Pattern p = Pattern.compile("(\\D*[a-zA-Z]+)(\\d*)(\\D*)");

在构造函数中:

sc.findInLine(p);

这可能是也可能不是 OOM 问题的根源,但它肯定会让您的解析速度更快一些。

相关:java.util.regex - Pattern.compile() 的重要性?

更新:在您发布更多代码后,我看到了一些其他问题。如果您重复调用此构造函数,则意味着您可能事先对输入进行了标记或分解。为什么要创建一个新Scanner的来解析每一行?它们是昂贵的; 如果可能的话,您应该使用相同Scanner的方法来解析整个文件。使用Scanner预编译Pattern的将比您现在所做的要快得多,即为您正在解析的每一行创建一个新的Scanner和一个新的。Pattern

于 2013-03-21T17:54:13.480 回答
1

填满你记忆的字符串是在findInLine(). 因此,重复Pattern创建不是问题。

在不知道其余代码的作用的情况下,我的猜测是您从匹配器中获得的一组组被保存在对象的字段中。然后,该字符串将被分配到 中findInLine(),正如您在此处看到的那样,但它被保留的事实将是由于您的代码。

编辑:

这是你的问题:

mnemonic = res.group(1).toLowerCase();

您可能没有意识到,如果字符串中没有大写字母,则toLowerCase()返回。this此外,group(int)返回 a substring(),它创建一个与完整字符串相同的新char[]字符串。所以,mnemonic实际上包含char[]整行的。

解决方法是:

mnemonic = new String(res.group(1).toLowerCase());
于 2013-03-21T18:02:23.573 回答
0

我认为您的代码段不完整。我相信你scanner.findInLine()在循环调用。不管怎样,试着打电话scanner.reset()。我希望这能解决你的问题。

于 2013-03-21T17:44:59.983 回答
0

JVM 显然没有时间进行垃圾收集。可能是因为它重复使用相同的代码(构造函数)来创建同一类的多个实例。在运行时堆栈发生变化之前,JVM 可能不会对 GC 做任何事情——在这种情况下,这不会发生。过去我被警告过在构造函数中做“太多”,因为当调用其他方法时,一些内存管理行为并不完全相同。

于 2013-03-21T17:45:23.757 回答
0

您的问题是您正在扫描几十万个字符串,并且您将模式作为字符串传递,因此您对于循环的每次迭代都有一个新的模式对象。您可以将模式拉出循环,如下所示:

    Pattern toMatch = Pattern.compile("(\\D*[a-zA-Z]+)(\\d*)(\\D*)")

    Scanner sc = new Scanner(headerText);
    MatchResult res;

    try {
        sc.findInLine(toMatch);
        res = sc.match();
    } finally {
        sc.close();
    }

然后,您将只传递对象引用,toMatch而不是为每次匹配尝试创建一个新模式对象的开销。这将修复您的泄漏。

于 2013-03-21T17:55:19.937 回答
0

好吧,我找到了问题的根源,它不完全是 Scanner,而是包含在构造函数中进行扫描的对象的列表。

问题与保存对包含解析的对象的引用的列表溢出有关,基本上每单位时间收到的字符串多于可以处理的字符串,并且列表不断增长,直到没有更多的 RAM。现在将此列表限制为最大大小可以防止解析器使内存过载;我将在解析器和数据源之间添加一些同步,以避免将来出现这种溢出。

谢谢大家的建议,我已经对扫描仪的性能进行了一些更改,并感谢@RobI 将我指向 jvisualvm,这使我能够追溯到持有参考文献的确切罪魁祸首。内存转储未显示参考链接。

于 2013-03-22T17:40:49.680 回答