我们知道,操作系统有32位和64位之分,当我们选择将硬件有32位升级至64位或直接选用64位硬件系统时,程序在硬件上的部署有两种选择:

1. 直接使用64位系统,并为JVM分配大内存

当Java堆较小时,好处是垃圾回收时间较短,但相应的代价是垃圾收集动作频繁,而垃圾回收动作又会导致程序停顿,因此容易想到通过扩大堆内存减少垃圾回收次数,从而避免程序频繁的停顿。

选用64位系统能有效扩大系统内存,相应地Java堆内存也能得到大幅提高。但问题随之而来:堆内存变大后,程序的垃圾收集次数确实能够减少,然而垃圾收集动作持续的时间变长了。实践表明,回收12G的堆,一次Full GC导致的停顿长达10多秒(注:数据来源于《深入理解Java虚拟机》)。

可以想象,频繁发生会导致系统停顿10多秒的Full GC是令人难以接受的,因此我们需要尽可能减少Full GC发生的次数,如若能把次数控制在每天1、2次,并且发生在深夜的话,那么也不是不能接受。这也就引出另外一个问题:如何减少Full GC的频率?

想要减少Full GC频率,就得避免对面过多进入老年代,操作的方式有以下两种:

  • 1.尽量保证新生的对象是“朝生夕死”的。这类对象在Minor GC后就会被清理,不会进入老年代。
  • 2.尽量避免产生过多的大对象。大对象是可以直接进入老年代的,因此要避免。若无法避免,可通过设置参数-XX:PretrnureSizeThreshold来提高大对象直接进入老年代的门槛。

使用64位JDK管理大内存带来的问题

●内存回收导致的长时间停顿。

●现阶段64位JDK性能较32位JDK低。

●相同的程序在64位JDK要比在32位JDK消耗更大的内存。

●堆溢出后几乎无法生存堆转储快照(产生的Dump文件高达10多G,并且即便生成快照也几乎无法进行分析)。

2. 使用若干虚拟机建立逻辑集群

由于64位JDK有不少问题存在,因此采用32位JDK建立逻辑集群的方式仍有不少人选择,具体做法是在机器上启动多个服务器应用进程,每个进程设置不同的端口,然后再启动一个负载均衡的进程,使用反向代理的方式将请求分发给各个服务器应用进程。

逻辑集群面临的问题

●存在全局资源的竞争,例如磁盘竞争。

●大量使用本地缓存的应用,每个应用都存储是类型的数据,造成大量内存的浪费,这时可以考虑集中式缓存。

●各个节点仍然不可避免得受到32位系统的限制。

直接内存可能导致内存溢出

直接内存并不是Java内存区域的一部分,但这部分内存的使用受Java程序的控制。直接内存不足时,并不能够通知虚拟机进行垃圾收集,只能等待Full GC时“顺便”清理下这部分内存,若等不到Full GC就只能抛出OOM异常,然后再catch该异常调用System.gc()方法,又由于该方法只是建议GC,虚拟机并不一定会进行GC,因此最后该OOM异常还是可能抛出,导致程序崩溃。