Java之JVM调优案例分析与实战(3) - 堆外内存导致的溢出错误

环境:基于B\S的点子考试系统,为了发现客户端能实时地从服务端接收考试数据,系统使用了逆向AJAX技术(也称Comet或Server Side Push),选用CometD1.1.1作为服务端推送框架,服务器是Jetty7.1.4,硬件为一台普通PC机,Core i5 CPU,

        4G内存,运行32位Windows操作系统。

说明:测试期间发现服务端不定时抛出内存溢出异常,服务器不一定每次都会出现异常,但是假如正式考试时奔溃一次,那估计整场考试都会全乱套,网站管理员尝试过把堆开到最大,32位系统最多到1.6GB基本无法再加大了,而且开大量也基本没效果,抛出

        内存溢出异常好像更加繁琐了。加入-XX:+HeapDumpOnOutOfMemoryError,居然也没有任何反应,抛出内存溢出异常时什么文件都没产生。无奈之下只好挂着jstat使劲盯屏幕,发现GC并不频繁,Eden区,Survivor区,老年代及拥挤代内存全部

       表示"情绪稳定,压力不大",但是照样不停的抛出内存溢出异常,管理员鸭梨很大。最后,在内存溢出后从系统日志中找到异常堆栈。

分析:大家都知道操作系统对每个进程能管理的内存是有限的,这台服务器使用的32位Windows平台的限制是2GB,其中给了Java堆1.6GB,而Direct Memory 并不算在1.6GB的堆之内,因此它只能在剩余的0.4GB空间分出一部分。在此应用中导致内 

        存溢出的关键是:垃圾收集进行时,虚拟机虽然会对Direct Memory进行回收,但是Direct Memory 却不能像新生代和老年代那样,发现空间不足了就通知收集器进行垃圾回收,他只能等到抛出内存溢出异常时,先catch掉,再在catch块里面“大喊”

        “System.gc”.要是虚拟机还是不听(如:打开了-XX:+DisableExplicitGC开关),那就只能眼睁睁地看着堆中还有许多空闲内存,自己却不得不抛出内存异常了。而本案例中使用的Comet1.1.1框架,正好有大量的NIO操作需要用到Direct Memory。

总结:从实践经验来看,除了java堆和永久代之外,我们注意到下面这些区域也会占用较多的内存,这里所有的内存总和会受到操作系统进程最大内存的限制:

        1.Direct Memory:可以通过-XX:MaxDirectMemorySize调整大小,内存不足时抛出OutOfMemoryError或OutOfMemoryError:Direct buffer memory。

        2.线程堆栈:可通过-Xss调整大小内存不足时抛出StackoverflowErroe(纵向无法分配,即无法分配新的栈帧)或OutOfMemoryError:unable to create new native thread(横向无法分配,即无法建立新的线程)。

        3.Socket缓存区:每个Socket连接都Receive和Send两个缓存区,分别占大约37KB和25KB的内存,连接多的话这块内存占用也比较可观。如果无法分配,则可能会抛出IOException:Too many open files异常。

        4.JNI代码:如果代码中使用JNI调用本地库,那么本地库使用内存也不在堆中

        5.虚拟机和GC:虚拟机和GC的代码执行也要消耗一定的内存。

       

 
posted on 2014-01-17 10:46  洛易  阅读(5143)  评论(2编辑  收藏  举报