Java Scanner.findInLine()大量泄漏内存

Java Scanner.findInLine()大量泄漏内存,java,memory-leaks,java.util.scanner,Java,Memory Leaks,Java.util.scanner,我正在运行一个简单的扫描程序来解析一个字符串,但我发现如果调用的次数足够多,我就会从内存错误中解脱出来。此代码作为为字符串数组重复构建的对象构造函数的一部分调用: 编辑:这里是更多信息的构造器;除了关于扫描仪的try-catch之外,没有更多的事情发生 public Header(String headerText) { char[] charArr; charArr = headerText.toCharArray(); // Check

我正在运行一个简单的扫描程序来解析一个字符串,但我发现如果调用的次数足够多,我就会从内存错误中解脱出来。此代码作为为字符串数组重复构建的对象构造函数的一部分调用:

编辑:这里是更多信息的构造器;除了关于扫描仪的try-catch之外,没有更多的事情发生

   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()的读取(Char)方法,该方法在操作期间被分配大量内存,就像我扫描几十万个字符串一样;几秒钟后,它已经分配了超过38MB的内存

我认为在构造函数中使用scanner()后调用close()会标记要由GC清除的旧对象,但不知何故,它会保留下来,并且read方法会在填充堆之前积累千兆字节的数据


有人能给我指出正确的方向吗?

我想你的代码片段还没有满。我相信您正在循环中调用scanner.findInLine()。无论如何,请尝试调用
scanner.reset()
。我希望这能解决您的问题。

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

您尚未发布所有代码,但鉴于您正在重复扫描同一个正则表达式,事先编译一个静态
模式
并将其用于扫描器的查找将更有效率:

static Pattern p = Pattern.compile("(\\D*[a-zA-Z]+)(\\d*)(\\D*)");
在构造器中:

sc.findInLine(p);
这可能是OOM问题的根源,也可能不是,但它肯定会使您的解析速度加快一点

相关的:


更新:在您发布了更多代码后,我发现了一些其他问题。如果您反复调用此构造函数,这意味着您可能事先标记或分解了输入。为什么要创建一个新的
扫描器来解析每一行?它们很贵;如果可能的话,您应该使用相同的
扫描仪来解析整个文件。使用一个带有预编译模式的
扫描仪
将比您现在所做的要快得多,这将为您要分析的每一行创建一个新的
扫描器和一个新的
模式。

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

    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
,而不需要为每次尝试匹配创建一个新模式对象。这将修复您的漏洞。

正在填充内存的字符串是在
findInLine()
中创建的。因此,重复的
模式
创建不是问题

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

编辑:

这是你的问题:

mnemonic = res.group(1).toLowerCase();
您可能没有意识到的是,如果字符串中没有大写字母,则
toLowerCase()
将返回
this
。另外,
group(int)
返回一个
substring()
,它将创建一个新字符串,该字符串的后面是与完整字符串相同的
char[]
。因此,
mnemonic
实际上包含整行的
char[]

解决办法就是:

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

我已经找到了问题的根源,它不是扫描器,而是包含在构造函数中进行扫描的对象的列表

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


感谢大家的建议,我已经对扫描仪进行了一些性能方面的更改,并感谢@RobI为我指出了jvisualvm,这使我能够追踪到持有参考资料的确切罪犯。内存转储未显示引用链接。

此代码完成后,
MatchResult
会发生什么情况?您确定在GC发生时不会释放内存吗?我猜内存是可以GC'd的,但GC不会在您期望的时候发生-相反,在达到某个阈值后,下一次内存分配将导致GC发生,并导致大量释放的内存…@RobI:他得到了一个
OutOfMemoryException
,JVM保证在抛出OOM之前运行垃圾收集。
sc.findInLine(“(\\D*[a-zA-Z]+)(\\D*)(\\D*)”假设此行返回字符串。最有可能的是,每次运行时,您的程序都会在这一行挂起。@RussellZahniser is
OutOfMemoryError
构造函数是在循环中调用的,FindLine()本身在构造函数开始时会被调用一次,经过一些健全性检查后,请访问构造函数以获得更清晰的信息谢谢您的提示,我会尝试一下!)这些都是很好的建议,但是你在这里指出的问题都不会对我造成影响