ReplaceAll正则表达式导致的Java OutOfMemoryError

时间:2017-01-12 10:50:05

标签: java regex replace out-of-memory heap

我正在进行相当大规模的分析(1000个项目),我从源代码中提取测试框架使用情况(例如,检测assertEquals以测量断言密度)。为此,我不想考虑任何已被注释掉的陈述。为此,我有以下方法:

public static CharSequence replaceAllRegexInFile(CharSequence input, String regex) {
    if (regex == null || input == null) {
        return input;
    }
    Pattern pattern = Pattern.compile(regex);
    return pattern.matcher(input).replaceAll("");
}

我使用以下正则表达式运行此方法来替换Java注释:

(\/\*([\S\s]+?)\*\/|(?s)/\*.*?\*/)". 

我很清楚replaceAll在聚合并返回最终结果的同时分配了大量的中间结果。当然,我可以使用替换,但这不允许我使用正则表达式来替换注释。

我明白了为什么会抛出堆空间错误,特别是因为我在整个机器上同时传输所有文件和所有项目。当然这是在使用大量资源,但我无法为我的问题找到替代解决方案,因为正则表达式替换是一项要求。

任何建议都将不胜感激。

您可以在下面找到堆栈跟踪:

Exception in thread "main" java.lang.OutOfMemoryError
  at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
  at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
  at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
  at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
  at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598)
  at java.util.concurrent.ForkJoinTask.reportException(ForkJoinTask.java:677)
  at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:735)
  at java.util.stream.ReduceOps$ReduceOp.evaluateParallel(ReduceOps.java:714)
  at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
  at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
  at AnalysisRunner.startAnalysis(AnalysisRunner.java:33)
  at AnalysisRunner.main(AnalysisRunner.java:26) 
Caused by: java.lang.OutOfMemoryError: Java heap space
  at java.util.Arrays.copyOf(Arrays.java:3332)
  at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
  at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:541)
  at java.lang.StringBuffer.append(StringBuffer.java:350)
  at java.util.regex.Matcher.appendReplacement(Matcher.java:888)
  at java.util.regex.Matcher.replaceAll(Matcher.java:955)
  at Business.RegexService.replaceAllRegexInFile(RegexService.java:64)
  at Business.FrameWorkDetectionService.extractAllResultsForFile(FrameWorkDetectionService.java:58)
  at Business.FrameWorkDetectionService.lambda$extractFrameworkDependencies$0(FrameWorkDetectionService.java:39)
  at Business.FrameWorkDetectionService$$Lambda$19/1175339539.apply(Unknown Source)
  at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
  at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
  at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
  at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
  at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:747)
  at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:721)
  at java.util.stream.AbstractTask.compute(AbstractTask.java:316)
  at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
  at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
  at java.util.concurrent.ForkJoinPool.helpComplete(ForkJoinPool.java:1870)
  at java.util.concurrent.ForkJoinPool.awaitJoin(ForkJoinPool.java:2045)
  at java.util.concurrent.ForkJoinTask.doInvoke(ForkJoinTask.java:404)
  at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:734)
  at java.util.stream.ReduceOps$ReduceOp.evaluateParallel(ReduceOps.java:714)
  at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
  at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
  at Business.FrameWorkDetectionService.extractFrameworkDependencies(FrameWorkDetectionService.java:39)
  at Business.FrameWorkDetectionService.detectFrameworks(FrameWorkDetectionService.java:26)
  at Business.FrameworkService.projectResults(FrameworkService.java:59)
  at AnalysisRunner$$Lambda$13/1712669532.apply(Unknown Source)
  at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
  at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)

是否有另一种解决方案不会分配这么多的堆空间,这仍然允许我同时替换大量文件中的所有注释?

非常感谢任何帮助!

2 个答案:

答案 0 :(得分:2)

可靠的是,Java没有为您的应用分配足够的内存。您可以尝试使用-Xmx-Xms标志来增加分配的初始和最大内存,例如:

java -Xmx2048m -Xms512m yourApp

调整这些参数,以免应用程序崩溃。

您可以通过运行java -X

查看所有可能的参数

如果更改已分配的内存无助于在应用程序运行时尝试使用jmap -heap:format=b <process-id>创建堆转储,则在某种内存分析器中打开它(例如http://www.eclipse.org/mat/)。也许在代码的其他部分有一些内存泄漏,这将检测它们

答案 1 :(得分:1)

我认为这更像是一个重要的评论,而不是一个答案,但发布的答案是因为它的格式更丰富。

你的正则表达式没有一个可能导致如此大的内存错误的良好性能。例如,这是你的正则表达式的图表:

enter image description here

我从中理解的是,您只想摆脱阻止评论。所以,你的正则表达式中存在不同的问题,最重要的是你有不同的模式来完全相同,因此你应该只使用其中一个,通过这样做,你可以摆脱捕获组和交替,只是使用他们喜欢:

\/\*[\S\s]+?\*\/".   <--- I removed the capturing group to make it more efficient, since you didn't need it
or 
(?s)/\*.*?\*/".

enter image description here

正如您所看到的,正则表达式模式效率更高,它没有2个模式,也没有2个捕获组,也没有非常昂贵的交替。

无论如何,如果你不需要java,那么我认为有更好的工具来执行这些替换,例如sed带有-i标志(替换就位)

但是,如果您仍想使用正则表达式,那么您可以通过删除不需要的捕获组并将捕获组转换为非捕获组来改进,如下所示:

(?:\/\*[\S\s]+?\*\/|(?s)/\*.*?\*/)".