java扫描器。findInLine()大量泄漏内存
我正在运行一个简单的扫描程序来解析一个字符串,但我发现如果调用的次数足够多,我就会从内存错误中解脱出来。此代码作为为字符串数组重复构建的对象构造函数的一部分调用:
编辑:这里是更多信息的构造器;除了关于扫描仪的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);
}
}
探查器内存快照显示扫描仪的读取(Char)方法。FindLine()在操作过程中被分配大量内存,就像我扫描几十万个字符串一样;几秒钟后,它已经分配了超过38MB的内存
我认为在构造函数中使用scanner()后调用close()会标记要由GC清除的旧对象,但不知何故,它会保留下来,并且read方法会在填充堆之前积累千兆字节的数据
谁能给我指出正确的方向吗
# 1 楼答案
正在填充内存的字符串是在
findInLine()
中创建的。因此,重复的Pattern
创建不是问题在不知道其余代码的作用的情况下,我猜想从matcher中得到的一个组被保存在对象的一个字段中。然后,正如您在这里看到的,该字符串将在
findInLine()
中被分配,但它被保留的事实可能是由于您的代码编辑:
这是你的问题:
您可能没有意识到的是,如果字符串中没有大写字母,则
toLowerCase()
返回this
。另外,group(int)
返回一个substring()
,它将创建一个新字符串,该字符串由与完整字符串相同的char[]
支持。因此,mnemonic
实际上包含整行的char[]
解决办法就是:
# 2 楼答案
我已经找到了问题的根源,它不是扫描器,而是包含在构造函数中进行扫描的对象的列表
这个问题与保存对包含解析的对象的引用的列表的溢出有关,基本上每单位时间接收的字符串比可以处理的要多,并且列表不断增长,直到没有更多的RAM为止。将此列表限制为最大大小现在可以防止解析器过载内存;我将在解析器和数据源之间添加一些同步,以避免将来出现这种溢出
感谢大家的建议,我已经对扫描仪进行了一些性能方面的更改,并感谢@RobI为我指出了jvisualvm,这使我能够追踪到持有参考资料的确切罪犯。内存转储未显示引用链接
# 3 楼答案
您尚未发布所有代码,但鉴于您正在重复扫描同一个正则表达式,因此事先编译静态
Pattern
并将其用于扫描程序的查找将更加有效:在构造器中:
这可能是OOM问题的根源,也可能不是,但它肯定会使您的解析速度加快一点
相关:java.util.regex - importance of Pattern.compile()?
更新:在您发布了更多代码后,我发现了一些其他问题。如果您反复调用此构造函数,这意味着您可能事先标记或分解了输入。为什么要创建一个新的
Scanner
来解析每一行?它们很贵;如果可能的话,您应该使用相同的Scanner
来解析整个文件。使用一个带有预编译的Pattern
的Scanner
将比您现在所做的要快得多,这将为您正在解析的每一行创建一个新的Scanner
和一个新的Pattern
# 4 楼答案
我认为您的代码片段不完整。我相信你是在循环调用
scanner.findInLine()
。无论如何,请尝试调用scanner.reset()
。我希望这能解决你的问题# 5 楼答案
JVM显然没有时间进行垃圾收集。可能是因为它重复使用相同的代码(构造函数)来创建同一类的多个实例。JVM可能不会对GC做任何事情,直到运行时堆栈发生变化——在本例中,这种情况不会发生。我曾经被警告过在构造函数中做“太多”,因为在调用其他方法时,一些内存管理行为并不完全相同
# 6 楼答案
您的问题是,您正在扫描数十万个字符串,并将模式作为字符串传入,因此循环的每一次迭代都有一个新的模式对象。您可以将图案从循环中拉出,如下所示:
然后,您将只将对象引用传递给
toMatch
,而不是为每次尝试匹配创建一个新模式对象的开销。这会修补你的漏洞