增加堆内存的大小 - 提防眼镜蛇效应
原文:http://plumbr.eu/blog/increasing-heap-size-beware-of-the-cobra-effect
"眼镜蛇效应"起源于当时英国统治印度殖民地的轶事。英国政府关心一些有毒的眼镜蛇.所以政府对每条死蛇提供奖励.最初这个方案很成功为了奖励大量的毒蛇被杀死.然而最终印度人为了收入开始养殖眼镜蛇.当意识到奖励取消了,养殖眼镜蛇和野生眼镜蛇成倍增加了. 明显的这个问题解决方案使这个情况更糟了.
如何调整Java堆内存的大小就像殖民地印度和毒蛇的关系.多包涵,我会引导你通过这个比喻用一个故事从现实生活中作为参考。
你已经创建了一个强大的程序. 如此强大使程序变的真正的流利和有巨大的流量,新的服务开始添加到你的程序中.通过挖掘你决定的性能指标,你程序的可用堆内存将不久成为瓶颈.
因此你部署了是原来6倍堆内存的基础设施. 你测试你的程序验证它可以工作.你部署它到新的硬件上.但是立即开始抱怨下面的问题 - 你的程序变的比以前2GB堆内存时响应要慢. 你的一些用户面临着分等待你的程序的响应延迟几分钟. 发生了什么?
可以有很多原因.不过,让我们关注最有可能发生的地方 - 堆内存大小的改变.这里有几种不好的效果像扩展缓存的预热时间,碎片问题等.但是从症状上来看你最有可能面临的是full GC期间的延迟问题.
这就意味着 - Java作为垃圾回收类型的语言 - 你的堆内存通过JVM内部的线程有规律的进行垃圾回收.正好你所预料的 - 如果你有一个较大的空间需要清理那么往往需要更多的时间. 同样适合于从内存中整理未使用的对象.
当运行的程序在一个小的堆上(低于4G)你不需要考虑GC.不过当堆内存增长到数十GB你绝对应该意识到潜在的full GC引起的stop-the-world暂停. 非常相信的暂停也存在在小的堆上, 但是他们的长度明显更短 - 你的暂停本来可能只有几百毫秒最后会超过一分钟.
所以当你真的需要给你的程序更多的堆内存时应该做些什么?
1. 第一个选择是考虑水平扩展代替垂直扩展. 对于当前的状况意味着 - 如果你的程序是无状态的或者是容易分区的那么只需要添加一个小的节点再对他们进行负载均衡. 这种情况下你可以坚持低内存占用的32位架构.
2. 如果水平扩展不可行你应该注意你的GC配置. 如果你追求的是延迟,你就应该忘掉面向吞吐量的stop-the-world GC并且开始寻找替代者. 你很快会发现仅限于并发标记及清扫(CMS)或者G1收集器.悲剧的消息是你最好的选择是这两种收集器之间以及其他只能通过尝试去发现的堆内存的配置参数.所以不要做出选择,去试一下你真实的生产环境负载.
此外意识到他们的局限性 - 这两种收集器在你的程序上造成吞吐量的系统消耗 - 特别是G1往往表现的比stop-the-world更糟的吞吐量. 并且当CMS垃圾收集器没有足够快的在完成收集前持久代满了,会回退到常规的stop-the-world GC. 所以你可能仍然面对30秒或更长的暂停对于16G或更大的堆.
3. 如果你不能水平扩展或者不能实现需要的延迟结果关于在Oracle's JVM上的垃圾回收, 那你也可以考虑通过Azul系统构建的Zing JVM.其中一个使Zing脱颖而出的特性是不停止垃圾回收(C4), 这个可能是你正在寻找的.虽然完全公开 - 不过我们还没有在练习中尝试C4.但是听起来确实很棒.
4. 最后一个选择是给那些真正的专家们. 你可以在堆外分配内存. 那些分配的内存显然对垃圾回收不可见因此不会被收集. 这个可能听起来很吓人, 其实在Java 1.4我们访问java.nio.ByteBuffer类,他已经提供给我们一个方法allocateDirect()去分配堆外内存.这允许我们创建很大的数据结构而不会造成多秒的GC暂停. 这个解决方案不太常见 - 很多BigMemory的实现是在底层使用ByteBuffers.比如Terracotta BigMemory和Apache DirectMemory.
来源:oschina
链接:https://my.oschina.net/u/592601/blog/177733