作为记忆分析的一部分,我们发现了以下内容:
percent live alloc'ed stack class rank self accum bytes objs bytes objs trace name 3 3.98% 19.85% 24259392 808 3849949016 1129587 359697 byte[] 4 3.98% 23.83% 24259392 808 3849949016 1129587 359698 byte[]
你会注意到分配了很多对象,但很少有对象存活。这是一个简单的原因 - 为生成的“客户端”的每个实例分配两个字节数组。客户端不可重用 - 每个客户端只能处理一个请求,然后被丢弃。字节数组总是具有相同的大小(30000)。
我们正在考虑迁移到字节数组的池(apache的GenericObjectPool),因为通常在任何给定时刻都有已知数量的活动客户端(因此池大小不应该波动很大)。这样,我们就可以节省内存分配和垃圾回收。问题是,池会导致严重的CPU损失吗?这个想法是个好主意吗?
感谢您的帮助!
答案 0 :(得分:2)
如果可能的话,汇总对你来说可能没什么帮助 - 可能会让事情变得更糟,虽然这取决于很多因素(你使用什么GC,对象存在多长时间,有多少可用内存等等)。 ):
GC的时间主要取决于活动对象的数量。收集器(我假设你运行一个vanilla Java JRE)不访问死对象,也不会逐个解除它们。它在复制了活动对象后释放了整个内存区域(这使内存保持整洁和紧凑)。 100个死对象可以快速收集到100000.另一方面,所有活动对象都必须被复制 - 所以如果你有一个包含100个对象的池,并且在给定时间只使用了50个,那么保留未使用的对象就是会花你的钱。
如果您的阵列目前的寿命比获得终身所需的时间短(复制到旧代空间),那么还有另一个问题:您的池阵列肯定会活得足够长。这将产生一种情况,其中存在从老一代到年轻人的大量参考 - 并且GC在考虑相反情况的情况下被优化。
实际上,池化数组很可能会使GC SLOWER比创建新的更快;这通常是廉价物品的情况。
池的另一个成本来自于跨线程同步对象并在使用后清理它们。两者都比听起来更棘手。
总结一下,除非你很清楚GC的内部结构,并了解它的工作原理,并且得到了一个分析器的结果,它表明管理所有数组是一个瓶颈 - 请勿使用。在大多数情况下,这是一个坏主意。
答案 1 :(得分:2)
我认为有很好的gc相关理由来避免这种分配行为。取决于堆的大小&分配时eden中的自由空间,简单地分配一个30000元素字节[]可能是一个严重的性能命中,因为它可能很容易大于TLAB(因此分配不是指针事件的碰撞)&甚至可能没有足够的空间可用,因此直接分配给终身,由于全gc活动增加(特别是如果由于碎片使用cms),本身可能会导致另一次击中线。
话虽如此,fdreger的评论也完全有效。多线程对象池有点令人头疼。你提到他们只处理一个请求,如果这个请求只由一个线程提供服务,那么在请求结束时擦除的ThreadLocal byte []可能是个不错的选择。如果请求相对于您典型的年轻gc时段而言是短暂的,则年轻 - >旧的参考问题可能不是一个大问题(因为即使您保证在gc期间处理任何给定请求的概率很小定期得到这个。)
答案 2 :(得分:1)
如果您的情况下的垃圾收集确实是性能损失(如果没有很多对象存活,通常清理eden空间不会花费太多时间),并且很容易插入对象池,尝试并测量它
这当然取决于您的应用程序的需要。
答案 3 :(得分:0)
只要你总是引用它,池就可以运行得更好,这样垃圾收集器就会忽略池并且只会被声明一次(你总是可以声明它是静态的,以保证安全) 。虽然这将是持久性记忆,但我怀疑这对你的应用程序来说是一个问题。