【分析】
如果机器比较紧缺,第一时间要恢复应用,可以直接先将该节点下线,保存线程栈快照,和堆内存快照。然后进行重启。
生产机器一般都是集群部署,如果只是某一台出现这种情况,可以不着急立即重启,保存现场,
1.看看最近有什么上线,分析改动代码,
2. 如果没有找到问题使用top 命令找出比较消耗资源的进程,使用top hp命令打印该进程下面比较消耗资源的线程
3. 使用jstack 打印出线程信息,看看是哪个类。或者是什么类型的线程。
4. 再结合使用jmap -histo找出内存的大对象,按照对象大小排序。如果需要根据对象来源的话,还需要导出dump日志,使用分析工具,找出对象的来源。
dump操作会暂停其他用户线程,所以只能在服务不可用的时候使用。可以先将该节点摘除
首先看最近有么有上线,分析最近改动代码,如果没有找到问题,线上可以使用jmap -histo 查看内存中的对象,按照所占内存大小排序,
这里基本可以锁定大对象是什么,如果这里还是看不出大对象来源,就需要分析一下dump 文件。 由于dump是一个耗时很长操作,会暂停用户线程,
所以可以将该节点摘除,然后分析大对象来源。
【总结】
-
定位进程pid
-
使用top -Hp [进程pid] 显示进程下面各个线程的占用情况,找出比较消耗资源的线程pid(shift+p 按cpu排序,shift+m 按内存排序)
-
通过jstack [进程pid] | grep -A 20 [线程pid的16进制] 打印出该线程的情况使用情况,分析是是什么线程,执行的是哪个类 (用printf ‘%x\n’ pid 转换为16进制)
-
使用jstack 保存栈的快照信息。jstack -l pid >> thread.txt
-
使用jstate -gc [pid]查看jvm gc情况,查看full gc 次数和时间,是否出现异常
命令:jstat -gc [pid] [intervel] -
通过jmap -histo [pid] 查看内存中的对象,按照所占内存大小排序。这里可以基本锁定异常大对象。
注意: jmap使用的时候jvm是处在停顿状态的,只能在服务不可用的时候为了解决问题来使用,否则会造成服务中断。所以先从服务列表摘除该节点。然后使用工具分析具体的对象来源。 -
如果需要跟踪对象来源,需要dump文件,但是dump是一个很耗时的操作,会造成用户线程停顿。需要先从服务列表摘除该节点。然后使用工具分析具体的对象来源。
jmap -dump:format=b,file=文件名 [pid]
解决:保存线程栈快照,和堆内存快照。进行重启
定位具体原因,利用jmap -heap 查看堆内存分配情况,如果是分代年龄设置太小的话,可以稍微设置大点。
如果是年轻代,设置太小,可以适当增加,避免回收频繁
打印堆内存信息:
jmap -heap [pid]
内存溢出问题该如何解决
内存溢出,简单地说内存溢出就是指程序运行过程中申请的内存大于系统能够提供的内存,导致无法申请到足够的内存,于是就发生了内存溢出。引起内存溢出的原因有很多种,常见的有以下几种:
-
内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
-
集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
-
代码中存在死循环或循环产生过多重复的对象实体;
-
使用的第三方软件中的BUG;
-
启动参数内存值设定的过小。
内存溢出的解决方案:
· 第一步,修改JVM启动参数,直接增加内存。
· 第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。
· 第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。
· 第四步,使用内存查看工具动态查看内存使用情况。
加分回答
除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OOM异常的可能。
- Java堆溢出
Java堆用于储存对象实例,我们只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么随着对象数量的增加,总容量触及最大堆的容量限制后就会产生内存溢出异常。
- 虚拟机栈和本地方法栈溢出
HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出OutOfMemoryError异常。
- 方法区和运行时常量池溢出
方法区溢出也是一种常见的内存溢出异常,在经常运行时生成大量动态类的应用场景里,就应该特别关注这些类的回收状况。这类场景常见的包括:程序使用了CGLib字节码增强和动态语言、大量JSP或动态产生JSP文件的应用、基于OSGi的应用等。
在JDK 6或更早之前的HotSpot虚拟机中,常量池都是分配在永久代中,即常量池是方法去的一部分,所以上述问题在常量池中也同样会出现。而HotSpot从JDK 7开始逐步“去永久代”的计划,并在JDK 8中完全使用元空间来代替永久代,所以上述问题在JDK 8中会得到避免。
- 本地直接内存溢出
直接内存的容量大小可通过-XX:MaxDirectMemorySize参数来指定,如果不去指定,则默认与Java堆最大值一致。如果直接通过反射获取Unsafe实例进行内存分配,并超出了上述的限制时,将会引发OOM异常。
重点排查以下几点:
1.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
2.检查代码中是否有死循环或递归调用。
3.检查是否有大循环重复产生新对象实体。
4.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
5.检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。