“眼镜蛇效应”一词源于英国殖民印度统治英国时所产生的轶事。 英国政府担心毒蛇眼镜蛇的数量。 因此,政府对每条死蛇给予悬赏。 最初,这是一个成功的策略,因为大量蛇被杀死以获取奖励。 最终,印度人开始养殖眼镜蛇以赚取收入。
当意识到这一点时,奖励就被取消了,但是眼镜蛇饲养者放开了蛇,因此野生眼镜蛇成倍增加。 解决问题的明显办法使情况更加恶化。
那么Java堆大小与殖民地印度和毒蛇有何关系? 忍受我,我将以现实生活中的故事为参考来指导您进行类比。 术语“眼镜蛇效应” 您已经创建了一个了不起的应用程序。 令人惊讶的是,它变得真正流行,并且新服务的庞大流量开始使您的应用程序屈服。 通过浏览性能指标,您可以确定可用于应用程序的堆数量很快将成为瓶颈。
因此,您需要花一些时间来启动具有原始堆六倍的新基础结构。 您测试您的应用程序以验证它是否有效。 然后,您可以在新的基础架构上启动它。 投诉立即涌入–您的应用程序变得比原始的2GB小堆响应速度慢。 您的某些用户在等待您的应用程序响应时会遇到几分钟的延迟。 刚刚发生了什么事?
当然有很多原因。 但是,让我们关注最可能的嫌疑犯–堆大小更改。 这有一些可能的副作用,例如延长缓存预热时间,碎片问题等。但是从出现的症状来看,您可能会在完整的GC运行期间在应用程序中遇到延迟问题 。
这意味着-因为Java是一种垃圾收集语言-JVM内部进程会定期对您使用的堆进行垃圾收集。 就像人们可能期望的那样-如果您有更大的房间要打扫,那么看门人通常会花费更多的时间来打扫房间。 这同样适用于从内存中清除未使用的对象。
在小堆(小于4GB)上运行应用程序时,通常不需要考虑GC内部。 但是,当将堆大小增加到数十GB时,您绝对应该意识到整个GC可能导致的世界停顿。 对于较小的堆大小,也确实存在相同的暂停,但是它们的长度明显缩短了–您现在暂停超过一分钟的暂停本来可能只跨越了几百毫秒。
那么,如果您确实需要更多应用程序堆,该怎么办?
- 第一种选择是考虑水平缩放而不是垂直缩放。 对于我们当前的情况,这意味着–如果您的应用程序是无状态的或易于分区的,则只需添加更多的小节点并平衡它们之间的负载即可。 在这种情况下,您可以坚持使用32位架构,这也会占用较小的内存 。
- 如果无法进行水平缩放,则应专注于GC配置。 如果您要等待的是延迟,那么您应该忘记面向吞吐量的世界一流GC,并开始寻找替代方案。 您很快会发现它们仅限于并发标记和扫描(CMS)或垃圾优先(G1)收集器。 最可悲的消息是,只有通过实验才能找到这两种收集器类型和其他堆配置参数之间的最佳选择。 因此,不要仅通过阅读一些东西,走出去然后尝试一下实际的生产负荷来做出选择。
但也要注意它们的局限性-这两个收集器都给您的应用程序带来了吞吐量开销-特别是G1的吞吐量数字往往比世界其他同类产品差。 而且,如果CMS垃圾收集器的速度不够快,无法在永久性生成器已满之前完成操作,则它会退回到标准的世界各地GC。 因此,对于大小为16 GB及以上的堆,您仍然可以面对30秒或更长时间的暂停。
- 如果您无法在Oracle JVM附带的垃圾收集器上进行水平扩展或无法达到所需的延迟结果,那么您也可以考虑Azul Systems构建的Zing JVM 。 使Zing脱颖而出的功能之一是无休止的垃圾收集器(C4) ,这可能正是您所需要的。 但是,要全面披露–我们尚未在实践中尝试过C4。 但这听起来很酷。
- 最后选择是真正的硬核家伙。 您可以在堆外部分配内存。 这些分配显然对垃圾收集器不可见,因此不会被收集。 这听起来可能很可怕,但是从Java 1.4开始,我们已经可以访问java.nio.ByteBuffer类,该类为我们提供了一种用于堆外内存分配的方法allocateDirect()。 这使我们可以创建非常大的数据结构,而不会遇到数秒的GC暂停。 这种解决方案并不太常见-许多BigMemory实现都在后台使用ByteBuffer。 例如, 兵马俑BigMemory和Apache DirectMemory 。
结论—即使有良好的意愿进行更改,也要注意替代方案和后果。 就像印度政府过去公布死眼镜蛇的报酬一样 。
参考: 增大堆大小–在Plumbr Blog博客上, 请注意我们JCG合作伙伴 Nikita Salnikov Tarnovski 的Cobra 。
翻译自: https://www.javacodegeeks.com/2012/12/increasing-heap-size-beware-of-the-cobra-effect.html