1、高性能硬件上的程序部署策略
目前常用2种方式:
(1)通过64位JDK来使用大内存:
使用第一种方式关键:
<1>控制应用程序的Full GC频率。譬如10多个小时甚至一天才出现一次Full GC。
关键:大多数对象的生存时间不应该太长,保证老年代空间的稳定。
在大多数网站形势的应用里,主要对象的生存周期都是请求级或页面级的,会话级和全局级的长生命对象相对较少,控制住Full GC。
需要考虑的问题:
<1>内存回收导致的长时间停顿;
<2>现阶段,64位JDK的性能测试结果普遍低于32位JDK;
<3>需要保证程序稳定,因为这种方式产生堆溢出几乎就无法产生堆转储快照;
<4>相同程序在64位JDK中消耗的内存一般比32位JDK大,这是由于指针膨胀及数据类型对齐补白等因素导致。
(2)使用若干32位虚拟机逻辑集群来利用硬件资源:(现在阶段常采用的方案)
具体做法:
<1>启用多个应用服务器进程,给每个服务器进程分配不同的端口;
<2>前端搭建一个负载均衡器,利用反向代理的方式来分配访问请求。
可能会遇到的问题:
<1>尽量避免节点竞争全局的资源,最典型的就是磁盘竞争;
<2>很难最高效率地利用某些资源池,譬如连接池,一般都是在各个节点建立自己的独立的资源池,导致一些节点资源池满了而另外一些节点仍有比较多的空余。尽量使用集中式的JNDI。
<3>各个节点仍然不可避免的受到32位的内存限制;
<4>大量使用本地缓存(如大量使用HashMap作为K/V缓存)的应用,在逻辑集群中会造成较大的内存浪费,因为每个逻辑节点上都有一份缓存,这时可以考虑把本地缓存改为集中式缓存。
2、集群间同步导致的内存溢出
3、堆外内存导致的溢出错误
问题现象:
<1>不定时抛出内存溢出异常;
<2>加入:-XX:+HeapDumpOnOutOfMemoryError,没有任何反应,抛出内存溢出异常时什么文件都没有产生。
找出问题:
<1>使用jstat实时监控,仍然很正常;
<2>最后通过系统日志找到异常堆栈。
分析错误原因:
<1>32位windows平台,内存限制2GB,其中java堆分配了1.6GB,而Direct Memory(参见《深入理解JVM内存管理_1、运行时数据区域》中的直接内存部分的介绍)并不算在1.6GB的堆之内,只有去剩下的0.4GB中分配;
<2>垃圾收集进行时,虚拟机虽然会对Direct Memory继续回收,但是Direct Memory却不像新生代和老年代那样,发现空间不足就通知收集器进行垃圾回收,它只能等待老年代满了之后Full GC,然后随便帮它清理内存的废弃对象。否则,它只能等到抛出内存溢出异常时,先catch掉,再在catch块里面大喊一声“System.gc()”。要是虚拟机还是不听(譬如打开:-XX:+DisableExplicitGC开关),那就只能看着堆中还有许多空闲内存,自己却不得不抛出内存溢出异常了。
通过问题得到的启示:
<1>Direct Memory:可通过-XX:MaxDirectMemorySize调整大小,内存不足时抛出OutOfMemoryError或OutOfMemoryError:Direct buffer memory。
<2>线程堆栈:可通过-Xssz调整大小,内存不足时抛出StackOverFlowError(纵向无法分配,即无法分配新的栈帧)或者OutOfMemoryError:unable to create new native thread(横向无法分配,即无法建立新的线程。)
<3>Socket缓存区:每个Socket连接都Receive和Send两个缓存区,分别占大约37K和25K的内存,连接多的话这块内存占用也比较客观,如果无法分配,抛出:IOException:Too many open file异常。
<4>JNI代码:如果代码中使用JNI调用本地库,那本地库使用的内存也不在堆中。
<5>虚拟机和GC:虚拟机和GC的代码执行也要消耗一定的内存。
4、外部命令导致系统缓慢:
问题现象:
<1>CPU使用率很高,而且发现并不是user应用占用的高。
<2>发现是“fork”系统调用,“fork”系统调用时Linux用来产生新进程的。在java虚拟机中,java代码最多只产生线程,不应当有进程的产生。
找出问题:
<1>使用java的Runtime.getRuntime().exec()来调用shell脚本来获取系统信息,该方式非常消耗CPU和内存。
解决办法:
通过java的API来获取系统信息即可。
5、服务器JVM进程崩溃:
问题现象:
<1>集群节点的虚拟机进程自动关闭的现象。
<2>异常:java.net.SocketException:Connection reset
找出问题:
<1>两个系统之间需要同步信息,而两个系统的响应速度不对称(尽管使用了异步的机制),导致主动发起请求(且速度快的系统)时间越长就累计了越多的服务没有调用完成,在等待的线程和Socket连接越来越多,最终超过了虚拟机的承受能力后使虚拟机进程崩溃。
解决办法:
将异步调用模式改为生产者/消费者模式的消息队列实现后,系统恢复正常。