64位JVM带来的问题及解决方案
这篇文章会检验你有关JVM的知识以及项目交付相关的技能;尤其是涉及到JVM升级的时候。期待你们的评论及回复,一起探讨下如何规避这类的项目可能产生的性能问题。
背景
最近碰到了一个影响到线上生产环境的问题,我们使用的是WebLogic 10以及32位的Hotspot JVM 1.6 。鉴于目前的一些问题以及未来负载上升的预测,我们决定将HotSpot JVM 1.6升级成64位的。
注意我们并没有修改JVM的启动参数。
经过几周的功能测试及规划,这次升级成功地部署到了线上环境。不过,技术支持团队发现第二天便出现了严重的性能下降,其中还有线程锁竞争的问题,迫使部署团队不得不回滚了这次升级。
最终我们找到了问题的原因,而这次升级也将在最近重新进行发布。
问题:
从上面这些信息来看,说一下你认为可能导致这次性能下降的原因。
说一下这次升级有什么好处,对于这类升级的如何进行管理以及降低风险,给出一些你的建议。
答案:
我经常听到有人说只要从32位JVM升级到64位就能自动获得性能的提升。这只说对了部分。有显著的性能提升的前提是在这之前你的系统存在内存占用的问题比如过度GC或者java.lang.outofmemoryerror,并且你也进行了适当的调优及堆大小的调整。
不幸的是,我们通常都忽略了一个事实,对于 64位的JVM来说,系统中的本地指针会占用8个字节,而不是4个。这会导致你的程序的内存占用量的增加,因此会带来更频繁的GC以及性能的下降。
下面是Oracle的官方解释:
64位的虚拟机和32位的比起来,性能上有什么不同?
一般来说,和32位的虚拟机相比,同样的程序在64位机上仅需花费很小的性能损失就能获得更大的寻址空间。这是由于系统的本地指针在64位虚拟机中会占用8个字节。这些额外字节的加载会对内存的使用带来影响,其结果就是程序执行的速度会变稍微的变慢,具体是多少取决于你的Java程序在执行的过程中需要加载多少指针。好消息是AMD64和EM64T平台在64位模式下运行的时候,Java虚拟机能获得额外的寄存器,来生成更高效的本地指令序列。这些额外寄存器带来的性能提升几乎能弥补64位虚拟机带来的执行速度的下降。SPARC平台上64位虚拟机和32位的相比,大概会有10%~20%的性能下降。AMD64和EM64T平台上则大概是0%~15%,这取决于你应用程序执行的时候有多少指针了。
现在回到我们开始说的那个问题上,内存占用量有了显著的增加,这就是导致性能问题的罪魁祸首。根据你选择的GC策略,GC的老生代回收会导致JVM和线程的暂停时间变得更长,这引发了线程锁竞争以及其它的问题。从下图可以看到,升级到64位JVM后应用程序的内存占用量(老生代)增加了45%。
Java堆的使用量 ~老年代回收后占用量大概是900MB(32位)
Java堆的使用量 ~老年代回收后占用量大概是1.3GB(64位)
应用程序的Java堆的内存占用量增加了45%。当然这是本地指针的大小膨胀了的缘故。
还有一个问题就是在项目支付前,只进行了功能测试,而没有进行性能测试及压力测试。并且也没有对JVM参数的进行修改或者调整,这自然就增长了旧生代回收的频率以及GC的暂停时间。最终的解决方案是将堆的大小从2GB增加到2.5GB,并且开启压缩指针的选项。
现在你已经明白问题是什么了,下面是我关于类似升级的一些建议:
- 进行性能压测,比较下应用程序在32位和64位时的内存占用量和GC的表现。
- 确保有足够的时间来进行GC参数及堆大小的调优,以便减少GC的暂停时间。
- 如果你用的是HotSpot JVM1.6 6u23以后的版本,可以启用指针压缩这个选项。这个选项使得JVM可以通过Java堆的某个64位的起始地址的32位的偏移量来表示大部分的指针;这样就减少了升级再来的内存占用量的增加。
- 正确地规划你的JVM进程所在的宿主机的容量。确保内存和CPU的能力满足这次升级带来的额外的内存和CPU的消耗。
- 采用一种低风险的升级策略,只升级线上环境的部分机器到64位JVM,比如说25%~50%。这样你还可以比较下现有的32位机器以及新升级的机器的表现,确保性能带来的收益及损失满足你的预期。
原创文章转载请注明出处:64位JVM带来的问题及解决方案