公司项目内存占用忽然飙升到90%
开始查询内存占用情况
1:进入服务器使用top查询那个内存占用比较大的进程(Shift + M按内存大小排序 Shift + P 按CPU使用情况排序)
我们发现PID=22977的这个进程占用10.5%的内存,RES(常驻内存)占用1.4G;但是这个程序启动命令是java -Xmx1024m -Xms1024m -jar test-0.1.0-SNAPSHOT.jar --spring.profiles.active=prod,
可以看出分给该程序使用的内存是1G,但是该程序已经超过了分给它的内存,并且其它7个程序的内存分配为512,应占用内存4.5G,但是使用使用7.2G,导致内存报警;
再次使用命令jmap -heap 22977查看该内存的使用情况(因为写文章是排查后的所以不是当时的PID)
可以看出该进程占用内存并未超过分配的1G内存:说明jvm运行良好,并未出现异常
由此分析是jvm使用了堆外内存导致内存占用过多
一、堆外内存的产生条件如下:
- 将长期存活的对象移入堆外内存,从而减少垃圾回收期管理的对象数量,为了降低FGC的次数和频率,打到提高系统响应速度的目的;
- 加快了复制的速度:堆内在 flush 到远程时,会先复制到直接内存,然后在发送;而堆外内存相当于省略掉了这个工作;
- 通过Unsafe开辟堆外内存;
4.通过NIO的ByteBuffer开辟堆外内存;
二、堆外内存是否可控
虽然还没有查清堆外内存保存了那些内容,但本次目的是解决在8G内存的服务器下,是否可以根据实际情况减少内存使用?JVM可以通过-XX:MaxDirectMemorySize来设置堆外内存的大小。若不对该参数进行设置,则会分配JVM堆同样大小的区域。所以说JVM分配给堆外的内存大小是可控的;
三、堆外内存分配小了是否会OOM
如果我们把堆外内存分配的小一些,那么内存的使用量就会下降,但当要使用堆外内存可已经没有空间的时候,是否会OOM?经过测试得出,堆外内存确实会发生OOM。但堆外内存会被回收,当JVM发现申请堆外内存空间不足时,会调用System.gc()来进行垃圾回收,JVM找到堆外内存是通过堆内的DirectByteBuffer对象,这个对象保存着堆外内存的引用。
四、测试堆外内存会不会造成内存崩溃
public static void main(String[] args) {
for(int i=0;i<10;i++){
System.out.println(i);
ByteBuffer byteBuffer=ByteBuffer.allocateDirect(1*1024*1024);
}
}
测试 可以发现内存溢出了
当设置了堆外内存大小为5M,关闭了System.gc()时使用命令-XX:+DisableExplicitGC,每次分配1M的堆外内存,当分配到第6次时,程序发生了OOM。
当打开System.gc()时,则程序可以正常分配10M的内存。
故而可见,控制堆外内存的大小后,当堆外内存不够分配时,会进行垃圾回收,不会发生OOM,除非堆外内存的所有对象都无法释放。
五、堆外内存排查
对系统进行快照:jmap -dump:format=b,file=/dumps/prosche.hprof 22977
使用MemoryAnalyzer将快照打开进行分析可以看出
JVM堆存中有160个DirectByteBuffer,这160个DirectByteBuffer对应的堆外内存对象是否就是那400M,还有待查验
六、解决办法
对所有Java程序生效:
- 添加-XX:MaxDirectMemorySize参数,降低堆外内存的使用;
- 添加-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath=/path/dump,在OOM时打印快照;
- 添加-XX:+PrintGC和-XX:+PrintGCDetails和-xloggc:/path/gc.log,在垃圾回收时记录日志