Java 创建数百万个小型临时对象的最佳实践

Java 创建数百万个小型临时对象的最佳实践,java,garbage-collection,Java,Garbage Collection,创建(和发布)数百万个小对象的“最佳实践”是什么 我正在用Java编写一个国际象棋程序,搜索算法为每一个可能的移动生成一个“移动”对象,一次名义搜索每秒可以轻松生成超过一百万个移动对象。JVM GC已经能够处理我的开发系统上的负载,但我对探索其他方法感兴趣,这些方法将: 最小化垃圾收集的开销,以及 降低低端系统的峰值内存占用 绝大多数对象都是非常短暂的,但生成的移动中约有1%是持久化的,并作为持久化值返回,因此任何池或缓存技术都必须提供排除特定对象重复使用的能力 我不希望示例代码完全充实,但我希

创建(和发布)数百万个小对象的“最佳实践”是什么

我正在用Java编写一个国际象棋程序,搜索算法为每一个可能的移动生成一个“移动”对象,一次名义搜索每秒可以轻松生成超过一百万个移动对象。JVM GC已经能够处理我的开发系统上的负载,但我对探索其他方法感兴趣,这些方法将:

  • 最小化垃圾收集的开销,以及
  • 降低低端系统的峰值内存占用 绝大多数对象都是非常短暂的,但生成的移动中约有1%是持久化的,并作为持久化值返回,因此任何池或缓存技术都必须提供排除特定对象重复使用的能力


    我不希望示例代码完全充实,但我希望能为进一步阅读/研究或类似性质的开源示例提供建议。

    使用详细垃圾收集运行应用程序:

    java -verbose:gc
    
    当它收集时,它会告诉你。将有两种类型的扫描,快速扫描和完全扫描

    [GC 325407K->83000K(776768K), 0.2300771 secs]
    [GC 325816K->83372K(776768K), 0.2454258 secs]
    [Full GC 267628K->83769K(776768K), 1.8479984 secs]
    
    箭头在大小之前和之后

    只要它只是做GC,而不是一个完整的GC,你就安全到家了。常规GC是“年轻一代”中的副本收集器,因此不再被引用的对象只是被遗忘,这正是您想要的


    阅读可能有帮助

    自版本6以来,JVM的服务器模式采用了一种技术。使用它,您可以同时避免GC。

    我遇到了类似的问题。首先,尽量缩小小物体的尺寸。我们在每个对象实例中引入了一些引用它们的默认字段值

    例如,MouseEvent有一个对Point类的引用。我们缓存点并引用它们,而不是创建新实例。例如,空字符串也是如此


    另一个来源是用一个int替换的多个boolean,对于每个boolean,我们只使用int的一个字节。

    假设您发现GC是一个问题(正如其他人指出的那样可能不是),您将为您的特殊情况实施自己的内存管理,即一个遭受大量搅动的类。尝试一下对象池,我见过它工作得很好的案例。实现对象池是一条很好的道路,因此无需再次访问此处,请注意:

    • 多线程:使用线程本地池可能适合您的情况
    • < LI>数据结构:考虑使用ADARYDEQE,在删除时执行良好,没有分配开销< /LI>
    • 限制池的大小:)

    在etc之前/之后进行度量,etc

    我不久前用一些XML处理代码处理了这个场景。我发现自己创建了数以百万计的XML标记对象,这些对象非常小(通常只是一个字符串),并且非常短(检查失败意味着没有匹配,所以放弃)

    我做了一些认真的测试,得出的结论是,使用丢弃的标签列表,而不是制作新标签,我的速度只能提高约7%。然而,一旦实现,我发现空闲队列需要添加一个机制,以便在它变得太大时对其进行修剪-这完全抵消了我的优化,因此我将其切换到一个选项


    总之,可能不值得这么做,但我很高兴看到您正在考虑它,这表明您很关心。

    如果您只有值对象(即,没有对其他对象的引用),但实际上我指的是非常多的值对象,您可以使用直接
    字节缓冲区
    和本机字节排序[后者很重要]您需要几百行代码来分配/重用+getter/setter。getter看起来类似于
    long getQuantity(int-tupleIndex){return buffer.getLong(tupleInex+QUANTITY_offset);}

    只要您只分配一次,也就是说,分配一个巨大的块,然后自己管理对象,这几乎可以完全解决GC问题。与引用不同,您只需将索引(即,
    int
    )放入必须传递的
    ByteBuffer
    。您可能还需要自己进行内存对齐

    这种技术就像使用了
    C和void*
    ,但是用一些包装是可以忍受的。如果编译器未能消除边界检查,则可能会导致性能下降。一个主要的优点是局部性。如果像向量一样处理元组,那么缺少对象头也会减少内存占用


    除此之外,您可能不需要这种方法,因为几乎所有JVM的年轻一代都会死掉,分配成本只是一个指针碰撞。如果使用
    final
    字段,分配成本可能会更高,因为在某些平台(即ARM/Power)上它们需要内存限制,但在x86上它是免费的。

    只需创建数百万个对象并以正确的方式编写代码:不要保留对这些对象的不必要引用。GC将为您完成肮脏的工作。您可以使用前面提到的详细GC,看看它们是否真的是GC。Java是关于创建和释放对象的。:)

    我在此类搜索算法中使用的一种解决方案是只创建一个移动对象,用新移动对其进行变异,然后在离开范围之前撤消移动。您可能一次只分析一个移动,然后将最佳移动存储在某个位置


    如果出于某种原因,这是不可行的,并且您希望减少峰值内存使用,那么这里有一篇关于内存效率的好文章:

    我不太喜欢GC,所以我总是尝试找到解决方法。在这种情况下,我建议使用:

    其思想是通过将新对象存储在堆栈中以避免创建新对象,以便以后可以重用

    Class MyPool
    {
       LinkedList<Objects> stack;
    
       Object getObject(); // takes from stack, if it's empty creates new one
       Object returnObject(); // adds to stack
    }
    
    classmypool
    {
    链接列表栈;
    对象getObject();//从堆栈中获取,如果堆栈为空,则创建新的堆栈
    返回对象
    
    for(int i=0, i<max, i++) {
      // stuff that implies i
    }