一、高性能硬件上的程序部署策略
1. 案例
一个网站服务器,原先配置为32位,给JVM堆内存大小为1.5GB,使用网站是比较缓慢但无明显卡顿。为了更好的用户体验,升级服务器为64位,16G内存;管理员选用64位jdk,设置JVM堆内存为12GB。但升级后的网站却不定期出现长时间无响应现象。
2. 原因
网站的无响应是由于GC停顿导致的,JVM在server模式,默认使用吞吐量优先的GC收集器,每次回收12GB的堆时,Full GC停顿时间长达12秒,导致卡顿无响应。因为程序设计,访问文档时,会将文档从磁盘提取到内存,这种大对象很多长时间存活进入了老年代,并没有在Minor GC中清理掉。这种情况哪怕有12GB内存也会很快被消耗掉,从而频繁的进行Full GC,使网站无响应。
3. 解决方案
在高性能硬件上部署程序,目前有两种方式:
1)通过64位JDK来使用大内存
使用这种方案,首先我们要清楚,JDK64位的性能经研究表明是赶不上32位JDK的,其次相同程序在64位JDK中消耗内存更大(由于指针膨胀、数据类型对齐不白等因素)。而且我们要把应用程序的Full GC频率降低,保证十几小时或一天才进行一次,这样我们可以在深夜定时执行一次,从而保证用户体验。
2)使用若干个32位虚拟机来建立逻辑集群共同利用硬件资源
通过搭建多个服务器构成集群,共同利用硬件资源,并在前端设置一个负载均衡器,通过反向代理的方式来分配访问请求。
这种方案需要注意:
## 避免节点竞争全局资源,如磁盘;如多节点同时访问磁盘,会造成IO异常
## 对于资源池的利用效率不高;每个节点多为独立建立资源池,会浪费一定资源
## 每个节点可能都会有一份本地缓存,造成内存浪费
4. 解决
采用方案二,搭建5个32位JDK逻辑集群,每个节点分配2GB内存(JVM堆1.5GB),并另外搭建一个Apache服务作为负载均衡代理;并将JVM的GC收集器改为CMS。
二、集群间同步导致的内存溢出
1. 概述
一个基于B/S的MIS系统,硬件是两台2个CPU、8GB内存的HP小型机,服务器是WebLogic9.2,每台机器启动3个WebLogic实例,这样就构成了6个节点的亲和式集群。
由于是亲和式集群,没有进行Session同步,而是将部分共享数据存入了由JBossCache构建的全局缓存,服务启用后,正常服务了较长时间,但最近不定期的出现内存溢出问题。
2. 原因
JBossCache基于自己的JGroups进行集群间的数据通信,JGroups使用协议栈的方式来实现收发数据包的各种所需特性的自由组合。由于信息有传输失败需要重发的可能性,在确认所有在GMS(Group Membership Service)注册的节点都收到信息前,发送的信息都需要在内存中保留;而MIS服务端有个负责安全校验的全局Filter,每次收到请求都需要在各个节点更新操作时间。所以当网络不能满足传输要求时,重发数据在内存中不断堆积,很快产生了内存溢出。
3. 解决
使用JBossCache这种集群缓冲来同步的话,要尽可能减少写操作,减少网络的开销
三、堆外内存导致的溢出错误
基于B/S的电子考试系统,为实现客户端能实时从服务端接收考试数据,系统使用逆向AJAX技术,选用CometD1.1.1作为服务端推送框架,服务器是Jetty7.1.4,硬件为一台普通PC机(Core i5、4G内存、32位系统)。
1. 问题
不定时抛出内存溢出异常,使用jstat命令行工具发现JVM堆内存并未使用完,但依然抛出内存溢出异常
2. 原因
32为Windows系统平台限制为2GB,给了Java堆1.6GB,剩余空间只有0.4GB,而Direct Memory需要使用剩余空间。当进行GC时,虽然会对Direct Memory进行回收,但是当它空间不足时,却不会像新生代或者老年代一样通知JVM进行GC,只会等待老年代满后的Full GC。所以哪怕堆中还有空闲,依然会抛出内存溢出异常。本案中的CometD框架,正好有大量的NIO操作要使用Direct Memory。
3. 总结
除了Java堆和永久代,还有以下区域会占用较多内存,受到总内存限制:
1)Direct Memory 2)线程堆栈 3)Socket缓存区 4)JNI代码 5)虚拟机和GC
四、外部命令导致系统缓慢
1. 概述
一个数字校园应用系统,运行在一天4个CPU的Solaris10系统上,中间件为GlassFish服务器。系统在进行大并发压力测试时,发现请求响应缓慢;而且CPU使用率很高,且大部分并非应用本身占用。
2. 原因
每个用户请求处理都要执行一个外部shell脚本来获得系统的一些信息,执行shell脚本通过Java的Runtime.getRuntime().exec()方法来调用的。这种命令非常耗费资源,需要先克隆一个与当前JVM一样环境变量的进程,再用新进程执行外部命令。
3. 解决
去掉这个shell脚本,改为使用Java API去获取这些信息
五、服务器JVM进程崩溃
系统参数与案例二一致,正常运行一段时间后,发现在运行期间频繁出现集群节点的虚拟机进程自动关闭的现象,并且日志显示远端断开连接异常。
1. 原因
通过了解,系统最近与一个OA门户进行了集成,在MIS系统工作流的待办事项变化时,要通过Web服务器通知OA门户系统,把待办事项的变化同步到OA门户之中。测试发现同步事项的Web服务响应缓慢,且响应结果皆为连接中断。
由于MIS系统用户多,待办事项变化很快,为了不被OA系统拖累,采用了异步的方式调用Web服务,但由于两边服务速度的完全不对等,随着时间增长,等待线程和Socket连接也越来越多,最终超过虚拟机承受能力而崩溃。
2. 解决
通知OA门户修复无法使用的集成接口,并将异步调用改为生产者/消费者模式的消息队列实现