1. 大内存硬件上的程序部署策略
这是笔者很久之前处理过的一个案例,但今天仍然具有代表性。一个15万PV/日左右的在线文档类型网站最近更换了硬件系统,服务器的硬件为四路志强处理器、16GB物理内存,操作系统为64位CentOS5.4,Resin作为Web服务器。整个服务器暂时没有部署别的应用,所有硬件资源都可以提供给这访问量并不算太大的文档网站使用。软件版本选用的是64位的JDK5,管理员启用了一个虛拟机实例,使用-Xmx和-Xms参数将Java堆大小固定在12GB。使用一段时间后发现服务器的运行效果十分不理想,网站经常不定期出现长时间失去响应。
原因:垃圾收集器停顿所导致的。默认使用的是吞吐量优先收集器,回收12GB的Java堆,一次FullGC的停顿时间就高达14秒。
解决方案:
目前单体应用在较大内存的硬件上主要有两种部署方式
-
通过一个单独的Java虚拟机实例来管理大量的Java堆内存。
-
同时使用若干个Java虚拟机,建立逻辑集群来利用硬件资源。
其次:可以在深夜执行定时任务的方式触发full GC甚至是重新启动应用服务器来保持内存可用空间在一个稳定水平。
控制Full GC频率的关键是老年代的相对稳定,这主要取决于应用中绝大多数对象能否符合“朝生夕灭”的原则,即大多数对象的生存时间不应当太长,尤其是不能有成批量的、长生存时间的大对象产生,这样才能保障老年代空间的稳定。
现实生活中:B/S形式的应用里,多数对象的生存周期都应该是请求级或者页面级的,会话级和全局级的长生命对象相对较少。
2. 集群间同步导致的内存溢出
集群的优点可以均衡并发处理,以及合理利用服务器资源。缺点就是会使用一部分开销,以及数据冗余。
3. 堆外内存导致的溢出错误
直接内存不能像新生代、老年代那样,发现空间不足了就主动通知收集器进行垃圾回收,它只能等待老年代满后Full GC出现后,“顺便”帮它清理掉内存的废弃对象。否则就不得不一直等到拋出内存溢出异常时,才进行清理。
从实践经验的角度出发,在处理小内存或者32位的应用问题时,除了Java堆和方法区之外,我们注意到下面这些区域还会占用较多的内存,这里所有的内存总和受到操作系统进程最大内存的限制:
直接内存:可通过-XX: M axDirectM emory Size调整大小,内存不足时拋出OutOf-M emory Error或者OutOfMemoryError: Direct buffer memory。
线程堆栈:可通过-Xss调整大小,内存不足时拋出StackOverflowError (如果线程请求的栈深度大于虚拟机所允许的深度)或者OutOfM emory Error (如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存)。
Socket缓存区:每个Socket连接都Receive和Send两个缓存区,分别占大约37KB和25KB内存,连接多的话这块内存占用也比较可观。如果无法分配,可能会抛出IOException: Too many open files 异常。
JNI代码:如果代码中使用了JNI调用本地库,那本地库使用的内存也不在堆中,而是占用Java虛拟机的本地方法栈和本地内存的。
虚拟机和垃圾收集器:虛拟机、垃圾收集器的工作也是要消耗一定数量的内存的。
4. 外部命令导致系统缓慢
用户根据建议去掉这个Shell脚本执行的语句,改为使用Java的API去获取这些信息后,系统很快恢复了正常。
5. 服务器虚拟机进程崩溃
原因:MIS系统的用户多,待办事项变化很快,为了不被0A系统速度拖累,使用了异步的方式调用Web服务,但由于两边服务速度的完全不对等,时间越长就累积了越多Web服务没有调用完成,导致在等待的线程和Socket连接越来越多,最终超过虚拟机的承受能力后导致虛拟机进程崩溃。
解决方案:通知OA门户方修复无法使用的集成接口,并将异步调用改为生产者/消费者模式的消息队列实现后,系统恢复正常。
6. 不恰当数据结构导致内存占用过大
我们具体分析一-下HashM ap空间效率,在HashM ap<Long,Long结 构中,只 有Key和Value所存放的两个长整型数据是有效数据,共16字节(2x8字节)。这两个长整型数据包装成java.langLong对象之后,就分别具有8字节的M ark Word、 8字节的Klass指针,再加8字节存储数据的long值。 然后这2个Long对象组成Map .Entry之后,又多了16字节的对象头,然后一个8字节的next字段和4字节的int型的hash字段,为了对齐,还必须添加4字节的空白填充,最后还有HashM ap中对这个Entry的8字节的引用,这样增加两个长整型数字,实际耗费的内存为( ong(24byte)x 2)+Fntry(32by te)+HashMap Ref(8byte)=88byte,空间效率为有效数据除以全部内存空间,即16字节/88字节=18%,这确实太低了。