Java Scanner.findInLine()大量泄漏内存
我正在运行一个简单的扫描程序来解析一个字符串,但我发现如果调用的次数足够多,我就会从内存错误中解脱出来。此代码作为为字符串数组重复构建的对象构造函数的一部分调用: 编辑:这里是更多信息的构造器;除了关于扫描仪的try-catch之外,没有更多的事情发生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
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 isOutOfMemoryError
构造函数是在循环中调用的,FindLine()本身在构造函数开始时会被调用一次,经过一些健全性检查后,请访问构造函数以获得更清晰的信息谢谢您的提示,我会尝试一下!)这些都是很好的建议,但是你在这里指出的问题都不会对我造成影响