堆外内存导致的溢出错误(三)

1、案例介绍

一个学校的小型项目:基于B/S的电子考试系统,为了实现客户端能实时地从服务器端接收考试数据,系统使用了逆向AJAX技术,选用CometD1.1.1作为服务端推送框架,服务器是Jetty 7.1.4,硬件为一台普通的PC机,Core i5 CPU,4GB内存,运行32位的Windows操作系统,

2、出现的问题

测试期间发现服务不定时的抛出内存溢出的异常,服务器不一定每次都出现异常,但是如果假设在考试时出现崩溃一次,那估计整个电子考试系统都会乱套,虽然管理员把堆开到最大,那32位的windows最大也就1.6G,并且还基本没效果,说明不是堆的大小的问题,而且抛出内存溢出异常好像更加频繁了,加入 -XX:+HeapDumpOnOutOfMemoryError (堆转储文件dump),居然没有任何反应,更加确定不是堆的问题,之后挂着jstat(虚拟机统计信息监视工具) 运行服务,发现GC不频繁,Eden区,Survivor区,老年代,永久代都没问题,但是还是不停的抛出内存溢出的异常,最后在内存溢出后从系统日志找到了异常的堆栈,
其中错误是这样的:

[.....] handle failed java.lang.OutOfMemoryError:null
at sun.misc.Unsafe.allocateMemory(Native Method)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:99)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
at org.eclipse.jetty.io.nio.DirectNIOBuffer.<init>

3、解析上面的错误:

OutOfMemoryError:null 肯定不是JVM运行时数据结构中的内存溢出,因为他们都有专属的,而不是null,而又属于内存溢出,那可以想一想除了那五种结构,还有哪种可以内存溢出?
就是本机直接内存,也叫堆外内存,可以自己上网查查,自己总结的不是很好,就不贴上去了
在这个错误中看到了
Unsafe.allocateMemory
nio.DirectByteBuffer
nio.ByteBuffer.allocateDirect
io.nio.DirectNIOBuffer
这些都是堆外内存出现溢出时可能会报的错误,再考虑dump文件没有任何异常,那就更加符合这个观点

4、分析这个案例中堆外溢出

大家都知道操作系统对每个进程能管理的内存都是有限制的,对于32位的而言,Linux最大可以提升到接近4G,而Windows也就2G,而堆的实际内存也就1.6G,那留给堆外内存的大小也就0.4G
这个问题的关键: 垃圾回收进行时,虚拟机虽然会对Direct Memory进行回收,但是Direct Memory却不能像新生代和老年代那样,发现空间不足就通知收集器进行回收,它只能等待老年代满了后Full GC,然后"顺便地"帮他清理掉内存中废弃的对象,否则它只能等内存溢出后异常后,先catch掉,再在catch块里"大喊"一声:“System.gc()”,要是虚拟机还是不听,(譬如打开了 -XX:+DisableExplicitGC 开关),就只能眼睁睁的看着堆中还有很多空闲的内存,而堆外内存不得不抛出内存溢出异常,而本案例中使用的CometD1.1.1框架,正好有大量的NIO操作需要使用到Direct Memory 内存,
所以这里既有框架CometD1.1.1的缺陷(大量的NIO用到了Direct Memory 内存),也有硬件的缺陷(32位windows系统最大内存为2.0,堆最大时,堆外内存0.4G),

当然这里也可以自己设置堆外内存的大小,但是堆外内存大了,那堆内存就会减小(因为这个32位的Windos系统一个进程管理的最大内存为2G(包括堆内存和堆外内存))

自我感觉要不换系统,换成Linux或者换掉CometD1.1.1框架

5、总结

做项目肯定是软件适应硬件,而不是硬件适应软件,要考虑到每个框架它如果存储数据数据存储在JVM的哪里,会不会出现像上面那样容易出现内存溢出,根据硬件合理选择框架,

6、还有一些需要耗费内存的地方

内存总和总是受到操作系统进程最大内存的限制

除了永久代需要耗费内存,还有很多需要耗费内存。

  1. Direct Memory:和案例中一样,可以通过 -XX:MaxDirectMemorySize调整大小
  2. 线程堆栈:可以通过**-Xss** 调整大小,内存不足时抛出StackOverflowError或者OutOfMemoryError:unable to create new native thread
  3. Socket缓存区:每个Socket连接都有Receive和Send两个缓存区,连接多的话占用内存也挺多的,内存不足就抛:IOException:Too many open files异常
  4. JNI代码:如果代码中使用JNI调用本地库,那本地库使用的内存也不在堆中
  5. 虚拟机和GC:虚拟机,GC的代码执行也要消耗一定的内存。

关于JNI可以看百度百科JNI

发布了213 篇原创文章 · 获赞 22 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/weixin_43113679/article/details/100749177
今日推荐