Java中大数组为什么速度慢

6
我创建了一个由大量元素组成的类数组,约有150M个元素,按照关键字排序(如下所述)。然后我建立了一个简单的http服务器,将每个请求反馈作为数组上的二分查找函数。 (我确定服务器的工作正常)。
数据初始化很好(当然非常缓慢)。预期的二分查找函数很快。
问题是:响应只是快了一段时间(10分钟,1小时……许多种时间范围),然后服务器需要相当长的时间(几分钟)才能对一个请求执行二分查找函数,然后它会迅速回来,一段时间后变慢......在它变慢时,我检查服务器状态(htop),似乎jvm正在进行GC。
当我将大型数组分割成较小的数组时,问题就不会发生,例如:10个包含15M元素的数组,我在搜索之前找到目标数组。因此,我猜测当我创建太大的数组时,JVM中发生了一些事情。
(编辑:我没有将大型数组拆分成片段的问题,因为我将“SiteInfo”对象实现为本机,从而减少了JVM中的大量对象。因此,问题是由我创建的太多对象引起的,请参见下面的答复,谢谢)
各位,你们对我的问题有什么想法吗?
(我发布了我的代码,其中有一些我认为并不重要的伪代码)
public static class Token2TopSite implements Comparable<Token2TopSite> {

    public final String token; // this is key for binary search
    public final SiteInfo[] topSites; // just data, not important at this question, I think

    public Token2TopSite(String token, SiteInfo[] topSites) {
        this.token = token;
        this.topSites = topSites;
    }

    @Override
    public int compareTo(Token2TopSite o) {
        return token.compareTo(o.token);
    }

    public static void main(String[] args) {
        Token2TopSite[] array = new Token2TopSite[150 * 1000000];
        ...; // init data for array, this runs properly
        Arrays.sort(array);
        startServerOnArray(array); // each request is a element search on the array
    }
} 

2
这是一个棘手的领域。 数组通常非常快,但有些边缘情况需要注意。 例如,当JVM决定在堆内重定位大数组时会发生什么? 添加-XX:+PrintGCApplicationStoppedTime-XX:+PrintSafepointStatistics-XX:PrintSafepointStatisticsCount = 1-XX:+PrintGCDetails。 这将帮助您排除正在进行的停顿全球/ GC问题。 让我们知道它的输出内容。 - Chris K
2个回答

4
我认为Omry Yadan的诊断可能是正确的。这些听起来像是GC暂停,并且如果堆中有大量长寿命可达对象,它们很可能会特别糟糕。在进行“完整”收集时,GC必须遍历所有活动对象。
(您可以通过启用GC日志记录并将服务器性能缓慢时的时间与GC事件进行比较,以确认这确实是与GC相关的问题。)
然而,我不同意他提出的解决方案。
与其重写应用程序,不如配置JVM使用“并发”或“低暂停”垃圾收集器。只需要在启动Web服务器的JVM的命令上设置一些参数即可。
以下是一些Oracle参考资料:

这绝不解释了为什么将其分成10个大小为15M的数组有所帮助。 - maaartinus
Stephen C 是对的,我想。我检查了一下,那是 Gc。因为我在 Jvm 中创建了太多的对象。对 Gc 进行一些调整帮助了我很多,但问题有时仍会发生。我尽可能在每个对象上进行本地化编码,然后它就可以平稳运行。当然,采用本地化方法太费时间了 :(@maaartinus:我编辑了我的帖子,我已经本地实现了 SiteInfo 对象,所以减少了大量对象,这样它就可以工作了,但有时仍会偶尔挂起,我通过自动请求检测到这一点。 - linkinsteps

1
JVM在存在1000万以上的对象时,性能会受到影响。原因是当垃圾回收器在寻找空闲内存时,如果找不到可用的内存,需要遍历所有对象。其中一个解决方案是使用更少的对象。我编写了一个名为Banana的基本集合库,它可以自行管理内存。它基本上创建一个(或多个)int数组,并允许您在其上构建动态数据结构(已实现几种,如HashTable、LinkedList、LRUCache等)。

你说得对,本地化非常好。我没有尝试过Banana。我使用了快速实用库(来自Apache),并进行了一些GC参数调整,这对我很有效。 - linkinsteps
我猜你还处于问题的低端。 测试你的解决方案的可扩展性,看看当你将数据大小加倍时它的表现如何(如果你有足够的RAM)。 Banana实际上是纯Java,没有本地代码(虽然我正在考虑添加对Unsafe BlockAllocator的支持)。 - Omry Yadan
也许我没有很清楚地表达“本地”的术语。我的意思是关于数据结构,快速工具库帮助了我。我的问题是创建太多对象,例如太多的A对象{field1,...,fieldN}。实际上,字段号i有值的限制,因此我将List<A>转换为N个数组。 数组号i是本地类型(int,long ...),保存id,指向字段i的真实对象。 最后,我创建的总对象数是sum(字段i的可能值)+ N个数组引用,比生成大量A类的数量少得多。 - linkinsteps

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接