我正在构建一个百万行电子表格,在这个过程中完成的任何事情,一百万次,都可以大大增加。我遇到的一个问题是当我在单元格中处理公式时,我必须解析公式,调整引用,然后重新构建公式。在这个过程中,我创建了5到12个字符串(取决于标记化时有多少个对象),然后我就完成了。
我发现垃圾收集器在此处理过程中占用了70%的时间,并且创建的主要对象超出了要收集的范围,这些是字符串。
有没有减少GC命中的方法? (如果这是C ++,我只会创建一个字符串池来重用。)
细节:
用于报告程序。我们读取模板,合并数据以生成最终报告,对最终报告执行处理,然后将其写入磁盘。该报告作为文档对象保存,在这种情况下是99%的单个表,有100万行(当所有数据合并时),每行有6个单元格,每个单元格可选择:公式,值和/或一组格式化文本。
在处理过程中,为短期使用创造了大量的字符串。它杀死我的情况是调整细胞配方的地方。模板在几个单元格中有一个公式,类似于“= A5 + A6”,然后根据每行的位置进行调整。我解析出对象{“A5”,“+”,“A6”},调整它们现在所在的行,然后在StringBuilder中将所有这些放回到StringBuilder中,并将toString()分配回来到单元格中的公式String对象。
将大多数文档对象写入磁盘的困难在于文档对象不会被读取,操作和写出新文档对象。为了减少内存命中并处理我们需要遍历列而不是行的情况,我们处理对象,就像调整它一样。
问题是当我们内存不足时 - 整个事情都会超速运行直到我们达到这一点。我正在使用YourKit进行分析,并且它正在收集它正在收集String对象。传递StringBuilder对象可以帮助一点但不是很多,因为我将收集很多这些(更少,但仍然很多)。
答案 0 :(得分:3)
此命中使IMHO与处理数百万字符串无关。我刚刚测量到我可以创造每秒600万个字符串的持续速率,并且GC非常闲置。
问题似乎是你内存不足。这使得GC更频繁,更难以保持程序运行。
所以不要浪费时间试图降低分配率。
获得更多内存或减少消耗。获得更多内存通常是最便宜的方式。为减少内存消耗,请考虑:
char
占用2个字节,这意味着浪费了一半的内存(假设您主要使用ASCII)。没有您的计划,很难说更多。
使用-XX:+PrintGCDetails
和-XX:+PrintGCTimeStamps
。这就是我得到的 - nearly no GC overhead:
10.075: [GC [PSYoungGen: 442272K->896K(425472K)] 442852K->1476K(769024K), 0.0016600 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
10.323: [GC [PSYoungGen: 425344K->928K(409600K)] 425924K->1508K(753152K), 0.0017150 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
10.558: [GC [PSYoungGen: 409504K->928K(394240K)] 410084K->1508K(737792K), 0.0014760 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
10.791: [GC [PSYoungGen: 394144K->928K(379904K)] 394724K->1508K(723456K), 0.0017070 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
答案 1 :(得分:2)
内存中的对象太多,意味着垃圾收集器的清理工作太多了。我相信如果您使用StringBuilder
/ StringBuffer
而不是String
,可以减少创建的对象数量。无论何时操作String
对象,都会因为String的不可变特性而创建一个新对象。
但是如果使用StringBuilder
/ StringBuffer
来操作字符串,则不会创建新对象。 StringBuilder
/ StringBuffer
会动态调整大小,但如果您选择StringBuilder
/ StringBuffer
的初始大小,也可以限制它。
简而言之,垃圾收集器需要较少的对象,较少的工作。