Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/389.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java String.split()临时对象和垃圾回收_Java_String_Performance_Split_Garbage Collection - Fatal编程技术网

Java String.split()临时对象和垃圾回收

Java String.split()临时对象和垃圾回收,java,string,performance,split,garbage-collection,Java,String,Performance,Split,Garbage Collection,在我的项目中,我们需要读取一个非常大的文件,其中每行都有由一个特殊字符(“|”)分隔的标识符。不幸的是,我不能使用并行性,因为有必要在一行的最后一个字符和下一行的第一个字符之间进行验证,以决定是否提取它。无论如何,要求非常简单:将行分解为标记,分析它们并仅将其中的一部分存储在内存中。代码非常简单,如下所示: final LineIterator iterator = FileUtils.lineIterator(file) while(iterator.hasNext()){ final

在我的项目中,我们需要读取一个非常大的文件,其中每行都有由一个特殊字符(“|”)分隔的标识符。不幸的是,我不能使用并行性,因为有必要在一行的最后一个字符和下一行的第一个字符之间进行验证,以决定是否提取它。无论如何,要求非常简单:将行分解为标记,分析它们并仅将其中的一部分存储在内存中。代码非常简单,如下所示:

final LineIterator iterator = FileUtils.lineIterator(file)
while(iterator.hasNext()){
   final String[] tokens = iterator.nextLine().split("\\|");
   //process
}
但是这段代码非常非常低效。方法split()生成了太多未收集的临时对象(此处对此进行了最好的解释:

出于比较目的:一个5mb的文件在文件处理结束时使用了大约35MB的内存

我测试了一些替代方案,如:

  • 使用预编译模式()
  • 用番石榴汁()
  • 优化字符串存储()
  • 使用优化的集合()
使用JProfiler,我可以看到临时对象使用的内存量太高(使用了35MB,但有效对象实际使用的内存只有15MB)

然后我决定做一个简单的测试:读取50000行之后,显式调用System.gc()。然后,在进程结束时,内存使用量从35 mb减少到16mb。我测试了很多次,总是得到相同的结果

我知道调用System.gc()是一种不好的做法(如中所示),但是在cenario中还有其他方法可以调用split()方法数百万次吗

[更新]
我使用5MB文件只是为了测试,但是系统应该处理更大的文件(500Mb~1Gb)

这里要说的第一件也是最重要的一件事是,不要担心。JVM消耗了35MB的RAM,因为它的配置表明这是一个足够低的数量。当它的高效GC算法决定时间时,它会将所有这些对象扫走,没有问题

如果您真的愿意,可以使用内存管理选项调用Java(例如,
Java-Xmxn=…
)——我建议,除非您在非常有限的硬件上运行,否则不值得这么做

但是,如果您确实希望避免每次处理一行时分配一个
字符串数组,那么有很多方法可以做到这一点

一种方法是使用
StringTokenizer

    StringTokenizer st = new StringTokenizer(line,"|");

    while (st.hasMoreElements()) {
        process(st.nextElement());
    }
您还可以避免一次使用一行。将文件作为流,使用
StreamTokenizer
,并以这种方式一次使用一个令牌

阅读
扫描仪
缓冲数据流
阅读器
的API文档——在这方面有很多选择,因为您正在做一些基本的事情

<>但是,这些都不会导致java更快或更具侵略性。如果JRE不认为内存不足,它就不会收集任何垃圾。 试着这样写:

public static void main(String[] args) {
    Random r = new Random();
    Integer x;
    while(true) {
        x = Integer.valueof(r.nextInt());
    }
}

运行它,并在运行时观察JVM的堆大小(如果使用率上升太快而看不见,请休眠)。每次循环中,Java都会创建一个您称之为Integer类型的“临时对象”。所有这些对象都会留在堆中,直到GC决定需要清除它们为止。您会看到,在达到某个级别之前,Java不会这样做。但当它达到该级别时,它会很好地确保永远不会超过其限制。

您应该调整分析情况的方式。虽然关于hood下的regex编译的文章总体上是正确的,但它不适用于这里。当您查看时,您会发现它只是委托给一个特殊的代码路径,用于仅由一个文字字符组成的模式,包括转义字符,如
\|

在该代码路径中创建的唯一临时对象是
ArrayList
。根本不涉及regex包;这一事实可能有助于您理解为什么预编译regex模式不能提高这里的性能

当您使用探查器得出对象太多的结论时,还应该使用它来找出对象的类型及其来源,而不是胡乱猜测


但不清楚你为什么抱怨。你可以将JVM配置为使用某个最大内存。只要没有达到该最大内存,JVM就会按照你说的做,使用该内存,而不是浪费CPU周期而不使用可用内存。不使用可用内存有何意义

“方法split()生成了太多未收集的临时对象(此处最好解释为:。"遗憾的是,它没有解释您的声明。也不清楚您为什么要拆分字符串,而不是解析字符串。您接受或拒绝
标记元素的标准是什么?
?另一个明显的解决方案是不拆分字符串,而是就地扫描/解析/处理字符串。即使使用了35mb,这真的很重要吗?如果你的JVM没有那么多内存,它会尝试在两者之间收集,如果有的话,为什么还要麻烦呢?最终它会收集。35 MB和16 MB之间的差异大约值10美分。你花多少时间来节省10美分的内存?最低工资大约是1分钟。一般来说,不要给Syste打电话m、 gc(),让JVM在需要时进行测试。感谢您的answear Holger,但事实上,当我检查探查器中可用的对象列表时,每次split()调用后,都会有大量对象[]增长。我尝试使用小文件进行测试,因为它很快(我可以运行20个测试,并获得平均时间和内存使用情况).另一件有趣的事情:在本例中,我知道32mb应该足以处理该文件,但当我使用-Xms16m-Xmx32m运行测试时,它会导致“GC开销lim”